8000 Print out predefined summary and descriptions for primitive types ins… · shader/python-fire@420ced0 · GitHub
[go: up one dir, main page]

Skip to content

Commit 420ced0

Browse files
joejoevictorcopybara-github
authored andcommitted
Print out predefined summary and descriptions for primitive types instead of the real docstring in the object since the docstring of the builtin objects are usually not very useful.
PiperOrigin-RevId: 291428068 Change-Id: Ica21b25aff6a8f95fc1ec7bd2fae8672fcf13ebb
1 parent 074f4b2 commit 420ced0

File tree

7 files changed

+236
-15
lines changed

7 files changed

+236
-15
lines changed

fire/custom_descriptions.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,12 @@
4040
from __future__ import division
4141
from __future__ import print_function
4242

43+
from fire import formatting
4344
import six
4445

46+
TWO_DOUBLE_QUOTES = '""'
47+
STRING_DESC_PREFIX = 'The string '
48+
4549

4650
def NeedsCustomDescription(component):
4751
"""Whether the component should use a custom description and summary.
@@ -69,3 +73,79 @@ def NeedsCustomDescription(component):
6973
):
7074
return True
7175
return False
76+
77+
78+
def GetStringTypeSummary(obj, available_space, line_length):
79+
"""Returns a custom summary for string type objects.
80+
81+
This function constructs a summary for string type objects by double quoting
82+
the string value. The double quoted string value will be potentially truncated
83+
with ellipsis depending on whether it has enough space available to show the
84+
full string value.
85+
86+
Args:
87+
obj: The object to generate summary for.
88+
available_space: Number of character spaces available.
89+
line_length: The full width of the terminal, default is 80.
90+
91+
Returns:
92+
A summary for the input object.
93+
"""
94+
if len(obj) + len(TWO_DOUBLE_QUOTES) <= available_space:
95+
content = obj
96+
else:
97+
additional_len_needed = len(TWO_DOUBLE_QUOTES) + len(formatting.ELLIPSIS)
98+
if available_space < additional_len_needed:
99+
available_space = line_length
100+
content = formatting.EllipsisTruncate(
101+
obj, available_space - len(TWO_DOUBLE_QUOTES), line_length)
102+
return formatting.DoubleQuote(content)
103+
104+
105+
def GetStringTypeDescription(obj, available_space, line_length):
106+
"""Returns the predefined description for string obj.
107+
108+
This function constructs a description for string type objects in the format
109+
of 'The string "<string_value>"'. <string_value> could be potentially
110+
truncated depending on whether it has enough space available to show the full
111+
string value.
112+
113+
Args:
114+
obj: The object to generate description for.
115+
available_space: Number of character spaces available.
116+
line_length: The full width of the terminal, default if 80.
117+
118+
Returns:
119+
A description for input object.
120+
"""
121+
additional_len_needed = len(STRING_DESC_PREFIX) + len(
122+
TWO_DOUBLE_QUOTES) + len(formatting.ELLIPSIS)
123+
if available_space < additional_len_needed:
124+
available_space = line_length
125+
126+
return STRING_DESC_PREFIX + formatting.DoubleQuote(
127+
formatting.EllipsisTruncate(
128+
obj, available_space - len(STRING_DESC_PREFIX) -
129+
len(TWO_DOUBLE_QUOTES), line_length))
130+
131+
132+
CUSTOM_DESC_SUM_FN_DICT = {
133+
'str': (GetStringTypeSummary, GetStringTypeDescription),
134+
'unicode': (GetStringTypeSummary, GetStringTypeDescription),
135+
}
136+
137+
138+
def GetSummary(obj, available_space, line_length):
139+
obj_type_name = type(obj).__name__
140+
if obj_type_name in CUSTOM_DESC_SUM_FN_DICT.keys():
141+
return CUSTOM_DESC_SUM_FN_DICT.get(obj_type_name)[0](obj, available_space,
142+
line_length)
143+
return None
144+
145+
146+
def GetDescription(obj, available_space, line_length):
147+
obj_type_name = type(obj).__name__
148+
if obj_type_name in CUSTOM_DESC_SUM_FN_DICT.keys():
149+
return CUSTOM_DESC_SUM_FN_DICT.get(obj_type_name)[1](obj, available_space,
150+
line_length)
151+
return None

fire/custom_descriptions_test.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# Copyright (C) 2018 Google Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Tests for custom description module."""
16+
17+
from __future__ import absolute_import
18+
from __future__ import division
19+
from __future__ import print_function
20+
21+
from fire import custom_descriptions
22+
from fire import testutils
23+
24+
LINE_LENGTH = 80
25+
26+
27+
class CustomDescriptionTest(testutils.BaseTestCase):
28+
29+
def test_string_type_summary_enough_space(self):
30+
component = 'Test'
31+
summary = custom_descriptions.GetSummary(
32+
obj=component, available_space=80, line_length=LINE_LENGTH)
33+
self.assertEqual(summary, '"Test"')
34+
35+
def test_string_type_summary_not_enough_space_truncated(self):
36+
component = 'Test'
37+
summary = custom_descriptions.GetSummary(
38+
obj=component, available_space=5, line_length=LINE_LENGTH)
39+
self.assertEqual(summary, '"..."')
40+
41+
def test_string_type_summary_not_enough_space_new_line(self):
42+
component = 'Test'
43+
summary = custom_descriptions.GetSummary(
44+
obj=component, available_space=4, line_length=LINE_LENGTH)
45+
self.assertEqual(summary, '"Test"')
46+
47+
def test_string_type_summary_not_enough_space_long_truncated(self):
48+
component = 'Lorem ipsum dolor sit amet'
49+
summary = custom_descriptions.GetSummary(
50+
obj=component, available_space=10, line_length=LINE_LENGTH)
51+
self.assertEqual(summary, '"Lorem..."')
52+
53+
def test_string_type_description_enough_space(self):
54+
component = 'Test'
55+
description = custom_descriptions.GetDescription(
56+
obj=component, available_space=80, line_length=LINE_LENGTH)
57+
self.assertEqual(description, 'The string "Test"')
58+
59+
def test_string_type_description_not_enough_space_truncated(self):
60+
component = 'Lorem ipsum dolor sit amet'
10000 61+
description = custom_descriptions.GetDescription(
62+
obj=component, available_space=20, line_length=LINE_LENGTH)
63+
self.assertEqual(description, 'The string "Lore..."')
64+
65+
def test_string_type_description_not_enough_space_new_line(self):
66+
component = 'Lorem ipsum dolor sit amet'
67+
description = custom_descriptions.GetDescription(
68+
obj=component, available_space=10, line_length=LINE_LENGTH)
69+
self.assertEqual(description, 'The string "Lorem ipsum dolor sit amet"')
70+
71+
72+
if __name__ == '__main__':
73+
testutils.main()

fire/docstrings.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ def parse(docstring):
149149
150150
Args:
151151
docstring: The docstring to parse.
152+
152153
Returns:
153154
A DocstringInfo containing information about the docstring.
154155
"""

fire/formatting.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020

2121
import termcolor
2222

23+
ELLIPSIS = '...'
24+
2325

2426
def Indent(text, spaces=2):
2527
lines = text.split('\n')
@@ -65,3 +67,29 @@ def WrappedJoin(items, separator=' | ', width=80):
6567

6668
def Error(text):
6769
return termcolor.colored(text, color='red', attrs=['bold'])
70+
71+
72+
def EllipsisTruncate(text, available_space, line_length):
73+
"""Truncate text from the end with ellipsis."""
74+
if available_space < len(ELLIPSIS):
75+
available_space = line_length
76+
# No need to truncate
77+
if len(text) <= available_space:
78+
return text
79+
return text[:available_space - len(ELLIPSIS)] + ELLIPSIS
80+
81+
82+
def EllipsisMiddleTruncate(text, available_space, line_length):
83+
"""Truncates text from the middle with ellipsis."""
84+
if available_space < len(ELLIPSIS):
85+
available_space = line_length
86+
if len(text) < available_space:
87+
return text
88+
available_string_len = available_space - len(ELLIPSIS)
89+
first_half_len = int(available_string_len / 2) # start from middle
90+
second_half_len = available_string_len - first_half_len
91+
return text[:first_half_len] + ELLIPSIS + text[-second_half_len:]
92+
93+
94+
def DoubleQuote(text):
95+
return '"%s"' % text

fire/formatting_test.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
from fire import formatting
2222
from fire import testutils
2323

24+
LINE_LENGTH = 80
25+
2426

2527
class FormattingTest(testutils.BaseTestCase):
2628

@@ -51,6 +53,30 @@ def test_wrap_multiple_items(self):
5153
'chicken |',
5254
'cheese'], lines)
5355

56+
def test_ellipsis_truncate(self):
57+
text = 'This is a string'
58+
truncated_text = formatting.EllipsisTruncate(
59+
text=text, available_space=10, line_length=LINE_LENGTH)
60+
self.assertEqual('This is...', truncated_text)
61+
62+
def test_ellipsis_truncate_not_enough_space(self):
63+
text = 'This is a string'
64+
truncated_text = formatting.EllipsisTruncate(
65+
text=text, available_space=2, line_length=LINE_LENGTH)
66+
self.assertEqual('This is a string', truncated_text)
67+
68+
def test_ellipsis_middle_truncate(self):
69+
text = '1000000000L'
70+
truncated_text = formatting.EllipsisMiddleTruncate(
71+
text=text, available_space=7, line_length=LINE_LENGTH)
72+
self.assertEqual('10...0L', truncated_text)
73+
74+
def test_ellipsis_middle_truncate_not_enough_space(self):
75+
text = '1000000000L'
76+
truncated_text = formatting.EllipsisMiddleTruncate(
77+
text=text, available_space=2, line_length=LINE_LENGTH)
78+
self.assertEqual('1000000000L', truncated_text)
79+
5480

5581
if __name__ == '__main__':
5682
testutils.main()

fire/helptext.py

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
from fire import value_types
4242

4343
LINE_LENGTH = 80
44+
SECTION_INDENTATION = 4
4445

4546

4647
def HelpText(component, trace=None, verbose=False):
@@ -96,10 +97,12 @@ def _NameSection(component, info, trace=None, verbose=False):
9697
current_command = _GetCurrentCommand(trace, include_separators=verbose)
9798
summary = _GetSummary(info)
9899

99-
# If the docstring is one of the messy builtin docstrings, don't show summary.
100-
# TODO(dbieber): In follow up commits we can add in replacement summaries.
100+
# If the docstring is one of the messy builtin docstrings, show custom one.
101101
if custom_descriptions.NeedsCustomDescription(component):
102-
summary = None
102+
available_space = LINE_LENGTH - SECTION_INDENTATION - len(current_command +
103+
' - ')
104+
summary = custom_descriptions.GetSummary(component, available_space,
105+
LINE_LENGTH)
103106

104107
if summary:
105108
text = current_command + ' - ' + summary
@@ -147,15 +150,17 @@ def _DescriptionSection(component, info):
147150
Returns the description if available. If not, returns the summary.
148151
If neither are available, returns None.
149152
"""
150-
# If the docstring is one of the messy builtin docstrings, set it to None.
151-
# TODO(dbieber): In follow up commits we can add in replacement docstrings.
152153
if custom_descriptions.NeedsCustomDescription(component):
153-
return None
154-
155-
summary = _GetSummary(info)
156-
description = _GetDescription(info)
154+
available_space = LINE_LENGTH - SECTION_INDENTATION
155+
description = custom_descriptions.GetDescription(component, available_space,
156+
LINE_LENGTH)
157+
summary = custom_descriptions.GetSummary(component, available_space,
158+
LINE_LENGTH)
159+
else:
160+
description = _GetDescription(info)
161+
summary = _GetSummary(info)
162+
# Fall back to summary if description is not available.
157163
text = description or summary or None
158-
159164
if text:
160165
return ('DESCRIPTION', text)
161166
else:
@@ -348,8 +353,9 @@ def _GetCurrentCommand(trace=None, include_separators=True):
348353

349354
def _CreateOutputSection(name, content):
350355
return """{name}
351-
{content}""".format(name=formatting.Bold(name),
352-
content=formatting.Indent(content, 4))
356+
{content}""".format(
357+
name=formatting.Bold(name),
358+
content=formatting.Indent(content, SECTION_INDENTATION))
353359

354360

355361
def _CreateArgItem(arg, docstring_info):
@@ -359,6 +365,7 @@ def _CreateArgItem(arg, docstring_info):
359365
arg: The name of the positional argument.
360366
docstring_info: A docstrings.DocstringInfo namedtuple with information about
361367
the containing function's docstring.
368+
362369
Returns:
363370
A string to be used in constructing the help screen for the function.
364371
"""
@@ -418,6 +425,9 @@ def _MakeUsageDetailsSection(action_group):
418425
if (docstring_info
419426
and not custom_descriptions.NeedsCustomDescription(member)):
420427
summary = docstring_info.summary
428+
elif custom_descriptions.NeedsCustomDescription(member):
429+
summary = custom_descriptions.GetSummary(
430+
member, LINE_LENGTH - SECTION_INDENTATION, LINE_LENGTH)
421431
else:
422432
summary = None
423433
item = _CreateItem(name, summary)

fire/helptext_test.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,8 @@ def testHelpTextEmptyList(self):
111111
trace=trace.FireTrace(component, 'list'))
112112
self.assertIn('NAME\n list', help_screen)
113113
self.assertIn('SYNOPSIS\n list COMMAND', help_screen)
114-
# The list docstring is messy, so it is not shown.
114+
# TODO(zuhaochen): Change assertion after custom description is
115+
# implemented for list type.
115116
self.assertNotIn('DESCRIPTION', help_screen)
116117
# We don't check the listed commands either since the list API could
117118
# potentially change between Python versions.
@@ -125,7 +126,8 @@ def testHelpTextShortList(self):
125126
trace=trace.FireTrace(component, 'list'))
126127
self.assertIn('NAME\n list', help_screen)
127128
self.assertIn('SYNOPSIS\n list COMMAND', help_screen)
128-
# The list docstring is messy, so it is not shown.
129+
# TODO(zuhaochen): Change assertion after custom description is
130+
# implemented for list type.
129131
self.assertNotIn('DESCRIPTION', help_screen)
130132

131133
# We don't check the listed commands comprehensively since the list API
@@ -141,7 +143,8 @@ def testHelpTextInt(self):
141143
comp 6504 onent=component, trace=trace.FireTrace(component, '7'))
142144
self.assertIn('NAME\n 7', help_screen)
143145
self.assertIn('SYNOPSIS\n 7 COMMAND | VALUE', help_screen)
144-
# The int docstring is messy, so it is not shown.
146+
# TODO(zuhaochen): Change assertion after implementing custom
147+
# description for int.
145148
self.assertNotIn('DESCRIPTION', help_screen)
146149
self.assertIn('COMMANDS\n COMMAND is one of the following:\n',
147150
help_screen)

0 commit comments

Comments
 (0)
0