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

Side by Side Diff: chromite/lib/text_menu.py

Issue 6005004: WIP Chromite supporting "LEGACY" mode, as requested by Anush. (Closed) Base URL: ssh://git@gitrw.chromium.org:9222/crosutils.git@master
Patch Set: Created 9 years, 12 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
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 __ceil_div(numerator, denominator):
12 """Do integer division, rounding up.
13
14 >>> __ceil_div(-1, 2)
15 0
16 >>> __ceil_div(0, 2)
17 0
18 >>> __ceil_div(1, 2)
19 1
20 >>> __ceil_div(2, 2)
21 1
22 >>> __ceil_div(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 __build_menu_str(items, title, prompt, menu_width, spacing):
36 """Build the menu string for TextMenu.
37
38 See TextMenu for a description. This function mostly exists to simplify
39 testing.
40
41 >>> __build_menu_str(['A'], "Choose:", "Choice: ", 76, 2)
42 'Choose:\\n\\n1. A\\n\\nChoice: '
43
44 >>> __build_menu_str(['B', 'A'], "Choose:", "Choice: ", 76, 2)
45 'Choose:\\n\\n1. B 2. A\\n\\nChoice: '
46
47 >>> __build_menu_str(['A', 'B', 'C', 'D', 'E'], "Choose:", "Choice: ", 10, 2)
48 'Choose:\\n\\n1. A 4. D\\n2. B 5. E\\n3. C\\n\\nChoice: '
49
50 >>> __build_menu_str(['A', 'B', 'C', 'D', 'E'], "Choose:", "Choice: ", 11, 2)
51 'Choose:\\n\\n1. A 4. D\\n2. B 5. E\\n3. C\\n\\nChoice: '
52
53 >>> __build_menu_str(['A', 'B', 'C'], "Choose:", "Choice: ", 9, 2)
54 'Choose:\\n\\n1. A\\n2. B\\n3. C\\n\\nChoice: '
55
56 >>> __build_menu_str(['A'*10, 'B'*10], "Choose:", "Choice: ", 0, 2)
57 'Choose:\\n\\n1. AAAAAAAAAA\\n2. BBBBBBBBBB\\n\\nChoice: '
58
59 >>> __build_menu_str([], "Choose:", "Choice: ", 76, 2)
60 Traceback (most recent call last):
61 ...
62 ValueError: Can't build a menu of empty choices.
63 """
64 if not items:
65 raise ValueError("Can't build a menu of empty choices.")
66
67 # Figure out some basic stats about the items.
68 num_items = len(items)
69 longest_num = len(str(num_items))
70 longest_item = longest_num + len(". ") + max(len(item) for item in items)
71
72 # Figure out number of rows / cols.
73 num_cols = max(1, (menu_width + spacing) // (longest_item + spacing))
74 num_rows = __ceil_div(num_items, num_cols)
75
76 # Construct "2D array" of lines. Remember that we go down first, then
77 # right. This seems to mimic "ls" behavior. Note that, unlike "ls", we
78 # currently make all columns have the same width. Shrinking small columns
79 # would be a nice optimization, but complicates the algorithm a bit.
80 lines = [[] for _ in xrange(num_rows)]
81 for item_num, item in enumerate(items):
82 col, row = divmod(item_num, num_rows)
83 item_str = "%*d. %s" % (longest_num, item_num + 1, item)
84 lines[row].append("%-*s" % (longest_item, item_str))
85
86 # Change lines from 2D array into 1D array (1 entry per row) by joining
87 # columns with spaces.
88 spaces = ' ' * spacing
89 lines = [spaces.join(line) for line in lines]
90
91 # Make the final menu string by adding some return and the prompt.
92 menu_str = "%s\n\n%s\n\n%s" % (title, '\n'.join(lines), prompt)
93 return menu_str
94
95
96 def TextMenu(items, title="Choose one:", prompt="Choice: ",
97 menu_width=76, spacing=4):
98 """Display text-based menu to the user and get back a response.
99
100 The menu will be printed to sys.stderr and input will be read from sys.stdin.
101
102 If the user doesn't want to choose something, it is expected that he/she
103 will press Ctrl-C to exit.
104
105 The menu will look something like this:
106 1. __init__.py 3. chromite 5. lib 7. tests
107 2. bin 4. chroot_specs 6. specs
108
109 Choice:
110
111 Args:
112 items: The strings to show in the menu. These should be sorted in whatever
113 order you want to show to the user.
114 prompt: The prompt to show to the user.
115 menu_width: The maximum width to use for the menu; 0 forces things to single
116 column.
117 spacing: The spacing between items.
118
119 Returns:
120 The index of the item chosen by the user. Note that this is a 0-based
121 index, even though the user is presented with the menu in 1-based format.
122 """
123 # Call the helper to build the actual menu string.
124 menu_str = __build_menu_str(items, title, prompt, menu_width, spacing)
125
126 # Loop until we get a valid input from the user (or they hit Ctrl-C, which
127 # will throw and exception).
128 while True:
129 # Write the menu to stderr, which makes it possible to use this with
130 # commands where you want the output redirected.
131 sys.stderr.write(menu_str)
132
133 # Don't use a prompt with raw_input(), since that would go to stdout.
134 result = raw_input()
135
136 # Parse into a number and do error checking. If all good, return.
137 try:
138 result_int = int(result)
139 if 1 <= result_int <= len(items):
140 # Convert from 1-based to 0-based index!
141 return result_int - 1
142 else:
143 print >>sys.stderr, "\nERROR: %d out of range\n" % result_int
144 except ValueError:
145 print >>sys.stderr, "\nERROR: '%s' is not a valid choice\n" % result
146
147
148 def __test():
149 """Run any built-in tests."""
150 import doctest
151 doctest.testmod(verbose=True)
152
153
154 # For testing purposes, you can run this on the command line...
155 if __name__ == '__main__':
156 # If first argument is --test, run testing code.
157 # ...otherwise, pass all arguments as the menu to show.
158 if sys.argv[1:2] == ['--test']:
159 __test(*sys.argv[2:])
160 else:
161 TextMenu(sys.argv[1:])
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698