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

Unified 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: Removed 'HOST' option when looking for chroot specs. 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 side-by-side diff with in-line comments
Download patch
Index: chromite/lib/text_menu.py
diff --git a/chromite/lib/text_menu.py b/chromite/lib/text_menu.py
new file mode 100644
index 0000000000000000000000000000000000000000..9e034c7ebbc6e17f9296474ae506bce59bfb7d86
--- /dev/null
+++ b/chromite/lib/text_menu.py
@@ -0,0 +1,194 @@
+#!/usr/bin/python
+# Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""A module containing a function for presenting a text menu to the user."""
+
+import sys
+
+
+def _CeilDiv(numerator, denominator):
+ """Do integer division, rounding up.
+
+ >>> _CeilDiv(-1, 2)
+ 0
+ >>> _CeilDiv(0, 2)
+ 0
+ >>> _CeilDiv(1, 2)
+ 1
+ >>> _CeilDiv(2, 2)
+ 1
+ >>> _CeilDiv(3, 2)
+ 2
+
+ Args:
+ numerator: The number to divide.
+ denominator: The number to divide by.
+
+ Returns:
+ (numberator) / denominator, rounded up.
+ """
+ return (numerator + denominator - 1) // denominator
+
+
+def _BuildMenuStr(items, title, prompt, menu_width, spacing, add_quit):
+ """Build the menu string for TextMenu.
+
+ See TextMenu for a description. This function mostly exists to simplify
+ testing.
+
+ >>> _BuildMenuStr(['A'], "Choose", "Choice", 76, 2, True)
+ 'Choose:\\n\\n1. A\\n\\nChoice (q to quit): '
+
+ >>> _BuildMenuStr(['B', 'A'], "Choose", "Choice", 76, 2, False)
+ 'Choose:\\n\\n1. B 2. A\\n\\nChoice: '
+
+ >>> _BuildMenuStr(['A', 'B', 'C', 'D', 'E'], "Choose", "Choice", 10, 2, False)
+ 'Choose:\\n\\n1. A 4. D\\n2. B 5. E\\n3. C\\n\\nChoice: '
+
+ >>> _BuildMenuStr(['A', 'B', 'C', 'D', 'E'], "Choose", "Choice", 11, 2, False)
+ 'Choose:\\n\\n1. A 4. D\\n2. B 5. E\\n3. C\\n\\nChoice: '
+
+ >>> _BuildMenuStr(['A', 'B', 'C'], "Choose", "Choice", 9, 2, False)
+ 'Choose:\\n\\n1. A\\n2. B\\n3. C\\n\\nChoice: '
+
+ >>> _BuildMenuStr(['A'*10, 'B'*10], "Choose", "Choice", 0, 2, False)
+ 'Choose:\\n\\n1. AAAAAAAAAA\\n2. BBBBBBBBBB\\n\\nChoice: '
+
+ >>> _BuildMenuStr([], "Choose", "Choice", 76, 2, False)
+ Traceback (most recent call last):
+ ...
+ ValueError: Can't build a menu of empty choices.
+
+ Args:
+ items: See TextMenu().
+ title: See TextMenu().
+ prompt: See TextMenu().
+ menu_width: See TextMenu().
+ spacing: See TextMenu().
+ add_quit: See TextMenu().
+
+ Returns:
+ See TextMenu().
+
+ Raises:
+ ValueError: If no items.
+ """
+ if not items:
+ raise ValueError("Can't build a menu of empty choices.")
+
+ # Figure out some basic stats about the items.
+ num_items = len(items)
+ longest_num = len(str(num_items))
+ longest_item = longest_num + len(". ") + max(len(item) for item in items)
+
+ # Figure out number of rows / cols.
+ num_cols = max(1, (menu_width + spacing) // (longest_item + spacing))
+ num_rows = _CeilDiv(num_items, num_cols)
+
+ # Construct "2D array" of lines. Remember that we go down first, then
+ # right. This seems to mimic "ls" behavior. Note that, unlike "ls", we
+ # currently make all columns have the same width. Shrinking small columns
+ # would be a nice optimization, but complicates the algorithm a bit.
+ lines = [[] for _ in xrange(num_rows)]
+ for item_num, item in enumerate(items):
+ row = item_num % num_rows
+ item_str = "%*d. %s" % (longest_num, item_num + 1, item)
+ lines[row].append("%-*s" % (longest_item, item_str))
+
+ # Change lines from 2D array into 1D array (1 entry per row) by joining
+ # columns with spaces.
+ spaces = " " * spacing
+ lines = [spaces.join(line) for line in lines]
+
+ # Add '(q to quit)' string to prompt if requested...
+ if add_quit:
+ prompt = "%s (q to quit)" % prompt
+
+ # Make the final menu string by adding some return and the prompt.
+ menu_str = "%s:\n\n%s\n\n%s: " % (title, "\n".join(lines), prompt)
+ return menu_str
+
+
+def TextMenu(items, title="Choose one", prompt="Choice",
+ menu_width=76, spacing=4, add_quit=True):
+ """Display text-based menu to the user and get back a response.
+
+ The menu will be printed to sys.stderr and input will be read from sys.stdin.
+
+ If the user doesn't want to choose something, he/she can use the 'q' to quit
+ or press Ctrl-C (which will be caught and treated the same).
+
+ The menu will look something like this:
+ 1. __init__.py 3. chromite 5. lib 7. tests
+ 2. bin 4. chroot_specs 6. specs
+
+ Choice (q to quit):
+
+ Args:
+ items: The strings to show in the menu. These should be sorted in whatever
+ order you want to show to the user.
+ title: The title of the menu.
+ prompt: The prompt to show to the user.
+ menu_width: The maximum width to use for the menu; 0 forces things to single
+ column.
+ spacing: The spacing between items.
+ add_quit: Let the user type 'q' to quit the menu (we'll return None).
+
+ Returns:
+ The index of the item chosen by the user. Note that this is a 0-based
+ index, even though the user is presented with the menu in 1-based format.
+ Will be None if the user hits Ctrl-C, or chooses q to quit.
+
+ Raises:
+ ValueError: If no items.
+ """
+ # Call the helper to build the actual menu string.
+ menu_str = _BuildMenuStr(items, title, prompt, menu_width, spacing,
+ add_quit)
+
+ # Loop until we get a valid input from the user (or they hit Ctrl-C, which
+ # will throw and exception).
+ while True:
+ # Write the menu to stderr, which makes it possible to use this with
+ # commands where you want the output redirected.
+ sys.stderr.write(menu_str)
+
+ # Don't use a prompt with raw_input(), since that would go to stdout.
+ try:
+ result = raw_input()
+ except KeyboardInterrupt:
+ # Consider this a quit.
+ return None
+
+ # Check for quit request
+ if add_quit and result.lower() in ("q", "quit"):
+ return None
+
+ # Parse into a number and do error checking. If all good, return.
+ try:
+ result_int = int(result)
+ if 1 <= result_int <= len(items):
+ # Convert from 1-based to 0-based index!
+ return result_int - 1
+ else:
+ print >>sys.stderr, "\nERROR: %d out of range.\n\n" % result_int
+ except ValueError:
+ print >>sys.stderr, "\nERROR: '%s' is not a valid choice.\n\n" % result
+
+
+def _Test():
+ """Run any built-in tests."""
+ import doctest
+ doctest.testmod(verbose=True)
+
+
+# For testing purposes, you can run this on the command line...
+if __name__ == "__main__":
+ # If first argument is --test, run testing code.
+ # ...otherwise, pass all arguments as the menu to show.
+ if sys.argv[1:2] == ["--test"]:
+ _Test(*sys.argv[2:])
+ else:
+ TextMenu(sys.argv[1:])

Powered by Google App Engine
This is Rietveld 408576698