Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1046)

Side by Side Diff: lib/text_menu.py

Issue 6334003: Created a 'TextMenu' utility function. (Closed) Base URL: ssh://git@gitrw.chromium.org:9222/chromite.git@master
Patch Set: Created 9 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 #!/usr/bin/python
2 # Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
5
6 """A module containing a function for presenting a text menu to the user."""
7
8 import sys
9
10
11 def _CeilDiv(numerator, denominator):
12 """Do integer division, rounding up.
13
14 >>> _CeilDiv(-1, 2)
15 0
16 >>> _CeilDiv(0, 2)
17 0
18 >>> _CeilDiv(1, 2)
19 1
20 >>> _CeilDiv(2, 2)
21 1
22 >>> _CeilDiv(3, 2)
23 2
24
25 Args:
26 numerator: The number to divide.
27 denominator: The number to divide by.
28
29 Returns:
30 (numberator) / denominator, rounded up.
31 """
32 return (numerator + denominator - 1) // denominator
33
34
35 def _BuildMenuStr(items, title, prompt, menu_width, spacing, add_quit):
36 """Build the menu string for TextMenu.
37
38 See TextMenu for a description. This function mostly exists to simplify
39 testing.
40
41 >>> _BuildMenuStr(['A'], "Choose", "Choice", 76, 2, True)
42 'Choose:\\n\\n1. A\\n\\nChoice (q to quit): '
43
44 >>> _BuildMenuStr(['B', 'A'], "Choose", "Choice", 76, 2, False)
45 'Choose:\\n\\n1. B 2. A\\n\\nChoice: '
46
47 >>> _BuildMenuStr(['A', 'B', 'C', 'D', 'E'], "Choose", "Choice", 10, 2, False)
48 'Choose:\\n\\n1. A 4. D\\n2. B 5. E\\n3. C\\n\\nChoice: '
49
50 >>> _BuildMenuStr(['A', 'B', 'C', 'D', 'E'], "Choose", "Choice", 11, 2, False)
51 'Choose:\\n\\n1. A 4. D\\n2. B 5. E\\n3. C\\n\\nChoice: '
52
53 >>> _BuildMenuStr(['A', 'B', 'C'], "Choose", "Choice", 9, 2, False)
54 'Choose:\\n\\n1. A\\n2. B\\n3. C\\n\\nChoice: '
55
56 >>> _BuildMenuStr(['A'*10, 'B'*10], "Choose", "Choice", 0, 2, False)
57 'Choose:\\n\\n1. AAAAAAAAAA\\n2. BBBBBBBBBB\\n\\nChoice: '
58
59 >>> _BuildMenuStr([], "Choose", "Choice", 76, 2, False)
60 Traceback (most recent call last):
61 ...
62 ValueError: Can't build a menu of empty choices.
63
64 Args:
65 items: See TextMenu().
66 title: See TextMenu().
67 prompt: See TextMenu().
68 menu_width: See TextMenu().
69 spacing: See TextMenu().
70 add_quit: See TextMenu().
71
72 Returns:
73 See TextMenu().
74
75 Raises:
76 ValueError: If no items.
77 """
78 if not items:
79 raise ValueError("Can't build a menu of empty choices.")
80
81 # Figure out some basic stats about the items.
82 num_items = len(items)
83 longest_num = len(str(num_items))
84 longest_item = longest_num + len(". ") + max(len(item) for item in items)
85
86 # Figure out number of rows / cols.
87 num_cols = max(1, (menu_width + spacing) // (longest_item + spacing))
88 num_rows = _CeilDiv(num_items, num_cols)
89
90 # Construct "2D array" of lines. Remember that we go down first, then
91 # right. This seems to mimic "ls" behavior. Note that, unlike "ls", we
92 # currently make all columns have the same width. Shrinking small columns
93 # would be a nice optimization, but complicates the algorithm a bit.
94 lines = [[] for _ in xrange(num_rows)]
95 for item_num, item in enumerate(items):
96 row = item_num % num_rows
97 item_str = "%*d. %s" % (longest_num, item_num + 1, item)
98 lines[row].append("%-*s" % (longest_item, item_str))
99
100 # Change lines from 2D array into 1D array (1 entry per row) by joining
101 # columns with spaces.
102 spaces = " " * spacing
103 lines = [spaces.join(line) for line in lines]
104
105 # Add '(q to quit)' string to prompt if requested...
106 if add_quit:
107 prompt = "%s (q to quit)" % prompt
108
109 # Make the final menu string by adding some return and the prompt.
110 menu_str = "%s:\n\n%s\n\n%s: " % (title, "\n".join(lines), prompt)
111 return menu_str
112
113
114 def TextMenu(items, title="Choose one", prompt="Choice",
115 menu_width=76, spacing=4, add_quit=True):
116 """Display text-based menu to the user and get back a response.
117
118 The menu will be printed to sys.stderr and input will be read from sys.stdin.
119
120 If the user doesn't want to choose something, he/she can use the 'q' to quit
121 or press Ctrl-C (which will be caught and treated the same).
122
123 The menu will look something like this:
124 1. __init__.py 3. chromite 5. lib 7. tests
125 2. bin 4. chroot_specs 6. specs
126
127 Choice (q to quit):
128
129 Args:
130 items: The strings to show in the menu. These should be sorted in whatever
131 order you want to show to the user.
132 title: The title of the menu.
133 prompt: The prompt to show to the user.
134 menu_width: The maximum width to use for the menu; 0 forces things to single
135 column.
136 spacing: The spacing between items.
137 add_quit: Let the user type 'q' to quit the menu (we'll return None).
138
139 Returns:
140 The index of the item chosen by the user. Note that this is a 0-based
141 index, even though the user is presented with the menu in 1-based format.
142 Will be None if the user hits Ctrl-C, or chooses q to quit.
143
144 Raises:
145 ValueError: If no items.
146 """
147 # Call the helper to build the actual menu string.
148 menu_str = _BuildMenuStr(items, title, prompt, menu_width, spacing,
149 add_quit)
150
151 # Loop until we get a valid input from the user (or they hit Ctrl-C, which
152 # will throw and exception).
153 while True:
154 # Write the menu to stderr, which makes it possible to use this with
155 # commands where you want the output redirected.
156 sys.stderr.write(menu_str)
157
158 # Don't use a prompt with raw_input(), since that would go to stdout.
159 try:
160 result = raw_input()
161 except KeyboardInterrupt:
162 # Consider this a quit.
163 return None
164
165 # Check for quit request
166 if add_quit and result.lower() in ("q", "quit"):
167 return None
168
169 # Parse into a number and do error checking. If all good, return.
170 try:
171 result_int = int(result)
172 if 1 <= result_int <= len(items):
173 # Convert from 1-based to 0-based index!
174 return result_int - 1
175 else:
176 print >>sys.stderr, "\nERROR: %d out of range.\n\n" % result_int
177 except ValueError:
178 print >>sys.stderr, "\nERROR: '%s' is not a valid choice.\n\n" % result
179
180
181 def _Test():
182 """Run any built-in tests."""
183 import doctest
184 doctest.testmod(verbose=True)
185
186
187 # For testing purposes, you can run this on the command line...
188 if __name__ == "__main__":
189 # If first argument is --test, run testing code.
190 # ...otherwise, pass all arguments as the menu to show.
191 if sys.argv[1:2] == ["--test"]:
192 _Test(*sys.argv[2:])
193 else:
194 if not sys.argv[1:]:
195 print "ERROR: Need params to display as menu items"
196 else:
197 result = TextMenu(sys.argv[1:])
198 print "You chose: '%s'" % result
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698