Skip to content

Commit 23eee4b

Browse files
committed
Get tags from keyword docs. robotframework#925
1 parent b497bd7 commit 23eee4b

9 files changed

Lines changed: 110 additions & 15 deletions

File tree

atest/robot/keywords/keyword_tags.robot

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@ Library keyword tags with `keyword` decorator
1212
2 one
1313

1414
Library keyword tags with documentation
15-
[Tags] not ready
1615
one two words
1716

17+
Library keyword tags with documentation and attribute
18+
2 one two words
19+
1820
Invalid library keyword tags
1921
[Tags] not ready
2022
[Template] NONE
@@ -27,9 +29,14 @@ User keyword tags with `[Tags]` setting
2729
2 first
2830

2931
User keyword tags with documentation
30-
[Tags] not ready
3132
3 one two words
3233

34+
User keyword tags with documentation and setting
35+
2 3 one two words
36+
37+
Dynamic library keyword with tags
38+
bar foo
39+
3340
*** Keywords ***
3441
Keyword tags should be
3542
[Arguments] @{tags}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
class DynamicLibraryWithKeywordTags(object):
2+
3+
def get_keyword_names(self):
4+
return ['dynamic_library_keyword_with_tags']
5+
6+
def run_keyword(self, name, *args):
7+
return None
8+
9+
def get_keyword_documentation(self, name):
10+
return 'Summary line\nTags: foo, bar'

atest/testdata/keywords/LibraryWithKeywordTags.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,16 @@ def library_keyword_tags_with_decorator():
1313

1414

1515
def library_keyword_tags_with_documentation():
16+
"""Summary line
17+
18+
Tags: are read only from the last line
19+
20+
Tags: one, two words"""
21+
pass
22+
23+
24+
@keyword(tags=['one', 2])
25+
def library_keyword_tags_with_documentation_and_attribute():
1626
"""Tags: one, two words"""
1727
pass
1828

atest/testdata/keywords/keyword_tags.robot

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
*** Settings ***
2-
Library LibraryWithKeywordTags.py
2+
Library LibraryWithKeywordTags.py
3+
Library DynamicLibraryWithKeywordTags.py
34

45
*** Test Cases ***
56
Library keyword tags with `robot_tags` attribute
@@ -11,6 +12,9 @@ Library keyword tags with `keyword` decorator
1112
Library keyword tags with documentation
1213
Library keyword tags with documentation
1314

15+
Library keyword tags with documentation and attribute
16+
Library keyword tags with documentation and attribute
17+
1418
Invalid library keyword tags
1519
[Documentation] FAIL No keyword with name 'Invalid library keyword tags' found.
1620
Invalid library keyword tags
@@ -21,6 +25,12 @@ User keyword tags with `[Tags]` setting
2125
User keyword tags with documentation
2226
User keyword tags with documentation
2327

28+
User keyword tags with documentation and setting
29+
User keyword tags with documentation and setting
30+
31+
Dynamic library keyword with tags
32+
Dynamic library keyword with tags
33+
2434
*** Keywords ***
2535
User keyword tags with setting
2636
[Tags] first ${2}
@@ -31,3 +41,8 @@ User keyword tags with documentation
3141
... Tags: are ignored also here
3242
... Tags: one, two words, ${3}
3343
No Operation
44+
45+
User keyword tags with documentation and setting
46+
[Documentation] Tags: one, two words, ${3}
47+
[Tags] one ${2}
48+
No Operation

src/robot/running/handlers.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -51,26 +51,27 @@ def InitHandler(library, method, docgetter=None):
5151

5252
class _RunnableHandler(object):
5353
type = 'library'
54-
_doc = ''
5554
_executed_in_dry_run = ('BuiltIn.Import Library',
5655
'BuiltIn.Set Library Search Order')
5756

58-
def __init__(self, library, handler_name, handler_method):
57+
def __init__(self, library, handler_name, handler_method, doc=''):
5958
self.library = library
6059
name = getattr(handler_method, 'robot_name', None) or handler_name
6160
self.name = utils.printable_name(name, code_style=True)
6261
self.arguments = self._parse_arguments(handler_method)
63-
self.tags = self._get_tags(handler_method)
6462
self.pre_run_messages = None
6563
self._handler_name = handler_name
6664
self._method = self._get_initial_handler(library, handler_name,
6765
handler_method)
6866
self._argument_resolver = self._get_argument_resolver(self.arguments)
67+
doc, tags = utils.split_tags_from_doc(doc)
68+
self._doc = doc
69+
self.tags = self._get_tags_from_attribute(handler_method) + tags
6970

7071
def _parse_arguments(self, handler_method):
7172
raise NotImplementedError
7273

73-
def _get_tags(self, handler_method):
74+
def _get_tags_from_attribute(self, handler_method):
7475
return Tags(getattr(handler_method, 'robot_tags', ()))
7576

7677
def _get_argument_resolver(self, argspec):
@@ -173,8 +174,8 @@ def _get_timeouts(self, context):
173174
class _PythonHandler(_RunnableHandler):
174175

175176
def __init__(self, library, handler_name, handler_method):
176-
_RunnableHandler.__init__(self, library, handler_name, handler_method)
177-
self._doc = utils.getdoc(handler_method)
177+
_RunnableHandler.__init__(self, library, handler_name, handler_method,
178+
utils.getdoc(handler_method))
178179

179180
def _parse_arguments(self, handler_method):
180181
return PythonArgumentParser().parse(handler_method, self.longname)
@@ -211,9 +212,8 @@ def __init__(self, library, handler_name, dynamic_method, doc='',
211212
argspec=None):
212213
self._argspec = argspec
213214
_RunnableHandler.__init__(self, library, handler_name,
214-
dynamic_method.method)
215+
dynamic_method.method, utils.unic(doc or ''))
215216
self._run_keyword_method_name = dynamic_method.name
216-
self._doc = doc is not None and utils.unic(doc) or ''
217217
self._supports_kwargs = dynamic_method.supports_kwargs
218218
if argspec and argspec[-1].startswith('**'):
219219
if not self._supports_kwargs:

src/robot/running/userkeyword.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,11 @@ def shortdoc(self):
8787
def init_keyword(self, variables):
8888
# TODO: Should use runner and not change internal state like this.
8989
# Timeouts should also be cleaned up in general.
90-
self.doc = variables.replace_string(self._doc, ignore_errors=True)
90+
doc = variables.replace_string(self._doc, ignore_errors=True)
91+
doc, tags = utils.split_tags_from_doc(doc)
92+
self.doc = doc
9193
self.tags = [variables.replace_string(tag, ignore_errors=True)
92-
for tag in self._tags]
94+
for tag in self._tags] + tags
9395
if self._timeout:
9496
self.timeout = KeywordTimeout(self._timeout.value,
9597
self._timeout.message,

src/robot/utils/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@
6363
from .robottypes import is_dict_like, is_list_like, is_str_like, type_name
6464
from .setter import setter
6565
from .text import (cut_long_message, format_assign_message,
66-
pad_console_length, get_console_length)
66+
pad_console_length, get_console_length, split_tags_from_doc)
6767
from .unic import prepr, unic
6868
from .utf8reader import Utf8Reader
6969

src/robot/utils/text.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
import re
16+
1517
from .charwidth import get_char_width
1618
from .misc import seq2str2
1719
from .unic import unic
@@ -21,6 +23,7 @@
2123
_MAX_ERROR_LINES = 40
2224
_MAX_ERROR_LINE_LENGTH = 78
2325
_ERROR_CUT_EXPLN = ' [ Message content over the limit has been removed. ]'
26+
_TAGS_RE = re.compile(r'\s*tags:(.*)', re.IGNORECASE)
2427

2528

2629
def cut_long_message(msg):
@@ -105,3 +108,16 @@ def _lose_width(text, diff):
105108
lost += get_console_length(text[-1])
106109
text = text[:-1]
107110
return text
111+
112+
113+
def split_tags_from_doc(doc):
114+
doc = doc.rstrip()
115+
tags = []
116+
if not doc:
117+
return doc, tags
118+
lines = doc.splitlines()
119+
match = _TAGS_RE.match(lines[-1])
120+
if match:
121+
doc = '\n'.join(lines[:-1]).rstrip()
122+
tags = [tag.strip() for tag in match.group(1).split(',')]
123+
return doc, tags

utest/utils/test_text.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from robot.utils.text import cut_long_message, _count_line_lengths, \
66
_MAX_ERROR_LINES, _MAX_ERROR_LINE_LENGTH, _ERROR_CUT_EXPLN,\
7-
get_console_length, pad_console_length
7+
get_console_length, pad_console_length, split_tags_from_doc
88

99

1010
class NoCutting(unittest.TestCase):
@@ -161,5 +161,40 @@ def test_cut_east_asian(self):
161161
assert_equal(pad_console_length(self.mixed_26, 11), u'012345\u6c49...')
162162

163163

164+
class TestDocSplitter(unittest.TestCase):
165+
166+
def test_doc_without_tags(self):
167+
docs = ["Single doc line.",
168+
"""Hello, we dont have tags here.
169+
170+
No sir. No tags.""",
171+
"Now Tags: must, start from beginning of the row",
172+
" We strip the trailing whitespace \n \n"]
173+
for doc in docs:
174+
self._assert_doc_and_tags(doc, doc.rstrip(), [])
175+
176+
def _assert_doc_and_tags(self, original, expected_doc, expected_tags):
177+
doc, tags = split_tags_from_doc(original)
178+
assert_equal(doc, expected_doc)
179+
assert_equal(tags, expected_tags)
180+
181+
def test_doc_with_tags(self):
182+
sets = [
183+
('Tags: foo, bar', '', ['foo', 'bar']),
184+
(' Tags: foo ', '', ['foo']),
185+
('Hello\nTags: foo, bar', 'Hello', ['foo', 'bar']),
186+
('Tags: bar\n Tags: foo ', 'Tags: bar', ['foo']),
187+
('Tags: bar, Tags:, foo ', '', ['bar', 'Tags:', 'foo']),
188+
('tags: foo', '', ['foo']),
189+
(' tags: foo , bar ', '', ['foo', 'bar']),
190+
('Hello\n taGS: foo, bar', 'Hello', ['foo', 'bar']),
191+
(' Hello\n taGS: f, b \n\n \n', ' Hello', ['f', 'b']),
192+
('Hello\nNl \n \nTags: foo', 'Hello\nNl', ['foo']),
193+
]
194+
for original, exp_doc, exp_tags in sets:
195+
self._assert_doc_and_tags(original, exp_doc, exp_tags)
196+
self._assert_doc_and_tags(original+'\n', exp_doc, exp_tags)
197+
198+
164199
if __name__ == '__main__':
165200
unittest.main()

0 commit comments

Comments
 (0)