|
1 """optik.help |
|
2 |
|
3 Provides HelpFormatter and subclasses -- used by OptionParser |
|
4 to generate formatted help text. |
|
5 """ |
|
6 |
|
7 # Copyright (c) 2001-2004 Gregory P. Ward. All rights reserved. |
|
8 # See the README.txt distributed with Optik for licensing terms. |
|
9 |
|
10 import os |
|
11 import string |
|
12 import textwrap |
|
13 from optik.option import NO_DEFAULT |
|
14 from optik.errors import gettext |
|
15 _ = gettext |
|
16 |
|
17 __revision__ = "$Id: help.py,v 1.1 2009/02/05 23:03:30 stechong Exp $" |
|
18 |
|
19 __all__ = ['HelpFormatter', 'IndentedHelpFormatter', 'TitledHelpFormatter'] |
|
20 |
|
21 class HelpFormatter: |
|
22 |
|
23 """ |
|
24 Abstract base class for formatting option help. OptionParser |
|
25 instances should use one of the HelpFormatter subclasses for |
|
26 formatting help; by default IndentedHelpFormatter is used. |
|
27 |
|
28 Instance attributes: |
|
29 parser : OptionParser |
|
30 the controlling OptionParser instance |
|
31 indent_increment : int |
|
32 the number of columns to indent per nesting level |
|
33 max_help_position : int |
|
34 the maximum starting column for option help text |
|
35 help_position : int |
|
36 the calculated starting column for option help text; |
|
37 initially the same as the maximum |
|
38 width : int |
|
39 total number of columns for output (pass None to constructor for |
|
40 this value to be taken from the $COLUMNS environment variable) |
|
41 level : int |
|
42 current indentation level |
|
43 current_indent : int |
|
44 current indentation level (in columns) |
|
45 help_width : int |
|
46 number of columns available for option help text (calculated) |
|
47 default_tag : str |
|
48 text to replace with each option's default value, "%default" |
|
49 by default. Set to false value to disable default value expansion. |
|
50 option_strings : { Option : str } |
|
51 maps Option instances to the snippet of help text explaining |
|
52 the syntax of that option, e.g. "-h, --help" or |
|
53 "-fFILE, --file=FILE" |
|
54 _short_opt_fmt : str |
|
55 format string controlling how short options with values are |
|
56 printed in help text. Must be either "%s%s" ("-fFILE") or |
|
57 "%s %s" ("-f FILE"), because those are the two syntaxes that |
|
58 Optik supports. |
|
59 _long_opt_fmt : str |
|
60 similar but for long options; must be either "%s %s" ("--file FILE") |
|
61 or "%s=%s" ("--file=FILE"). |
|
62 """ |
|
63 |
|
64 NO_DEFAULT_VALUE = "none" |
|
65 |
|
66 def __init__(self, |
|
67 indent_increment, |
|
68 max_help_position, |
|
69 width, |
|
70 short_first): |
|
71 self.parser = None |
|
72 self.indent_increment = indent_increment |
|
73 self.help_position = self.max_help_position = max_help_position |
|
74 if width is None: |
|
75 try: |
|
76 width = int(os.environ['COLUMNS']) |
|
77 except (KeyError, ValueError): |
|
78 width = 80 |
|
79 width = width - 2 |
|
80 self.width = width |
|
81 self.current_indent = 0 |
|
82 self.level = 0 |
|
83 self.help_width = None # computed later |
|
84 self.short_first = short_first |
|
85 self.default_tag = "%default" |
|
86 self.option_strings = {} |
|
87 self._short_opt_fmt = "%s %s" |
|
88 self._long_opt_fmt = "%s=%s" |
|
89 |
|
90 def set_parser(self, parser): |
|
91 self.parser = parser |
|
92 |
|
93 def set_short_opt_delimiter(self, delim): |
|
94 if delim not in ("", " "): |
|
95 raise ValueError( |
|
96 "invalid metavar delimiter for short options: %r" % delim) |
|
97 self._short_opt_fmt = "%s" + delim + "%s" |
|
98 |
|
99 def set_long_opt_delimiter(self, delim): |
|
100 if delim not in ("=", " "): |
|
101 raise ValueError( |
|
102 "invalid metavar delimiter for long options: %r" % delim) |
|
103 self._long_opt_fmt = "%s" + delim + "%s" |
|
104 |
|
105 def indent(self): |
|
106 self.current_indent = self.current_indent + self.indent_increment |
|
107 self.level = self.level + 1 |
|
108 |
|
109 def dedent(self): |
|
110 self.current_indent = self.current_indent - self.indent_increment |
|
111 assert self.current_indent >= 0, "Indent decreased below 0." |
|
112 self.level = self.level - 1 |
|
113 |
|
114 def format_usage(self, usage): |
|
115 raise NotImplementedError, "subclasses must implement" |
|
116 |
|
117 def format_heading(self, heading): |
|
118 raise NotImplementedError, "subclasses must implement" |
|
119 |
|
120 def format_description(self, description): |
|
121 if not description: |
|
122 return "" |
|
123 desc_width = self.width - self.current_indent |
|
124 indent = " "*self.current_indent |
|
125 return textwrap.fill(description, |
|
126 desc_width, |
|
127 initial_indent=indent, |
|
128 subsequent_indent=indent) + "\n" |
|
129 |
|
130 def expand_default(self, option): |
|
131 if self.parser is None or not self.default_tag: |
|
132 return option.help |
|
133 |
|
134 default_value = self.parser.defaults.get(option.dest) |
|
135 if default_value is NO_DEFAULT or default_value is None: |
|
136 default_value = self.NO_DEFAULT_VALUE |
|
137 |
|
138 return string.replace(option.help, self.default_tag, str(default_value)) |
|
139 |
|
140 def format_option(self, option): |
|
141 # The help for each option consists of two parts: |
|
142 # * the opt strings and metavars |
|
143 # eg. ("-x", or "-fFILENAME, --file=FILENAME") |
|
144 # * the user-supplied help string |
|
145 # eg. ("turn on expert mode", "read data from FILENAME") |
|
146 # |
|
147 # If possible, we write both of these on the same line: |
|
148 # -x turn on expert mode |
|
149 # |
|
150 # But if the opt string list is too long, we put the help |
|
151 # string on a second line, indented to the same column it would |
|
152 # start in if it fit on the first line. |
|
153 # -fFILENAME, --file=FILENAME |
|
154 # read data from FILENAME |
|
155 result = [] |
|
156 opts = self.option_strings[option] |
|
157 opt_width = self.help_position - self.current_indent - 2 |
|
158 if len(opts) > opt_width: |
|
159 opts = "%*s%s\n" % (self.current_indent, "", opts) |
|
160 indent_first = self.help_position |
|
161 else: # start help on same line as opts |
|
162 opts = "%*s%-*s " % (self.current_indent, "", opt_width, opts) |
|
163 indent_first = 0 |
|
164 result.append(opts) |
|
165 if option.help: |
|
166 help_text = self.expand_default(option) |
|
167 help_lines = textwrap.wrap(help_text, self.help_width) |
|
168 result.append("%*s%s\n" % (indent_first, "", help_lines[0])) |
|
169 for line in help_lines[1:]: |
|
170 result.append("%*s%s\n" % (self.help_position, "", line)) |
|
171 elif opts[-1] != "\n": |
|
172 result.append("\n") |
|
173 return string.join(result, "") |
|
174 |
|
175 def store_option_strings(self, parser): |
|
176 self.indent() |
|
177 max_len = 0 |
|
178 for opt in parser.option_list: |
|
179 strings = self.format_option_strings(opt) |
|
180 self.option_strings[opt] = strings |
|
181 max_len = max(max_len, len(strings) + self.current_indent) |
|
182 self.indent() |
|
183 for group in parser.option_groups: |
|
184 for opt in group.option_list: |
|
185 strings = self.format_option_strings(opt) |
|
186 self.option_strings[opt] = strings |
|
187 max_len = max(max_len, len(strings) + self.current_indent) |
|
188 self.dedent() |
|
189 self.dedent() |
|
190 self.help_position = min(max_len + 2, self.max_help_position) |
|
191 self.help_width = self.width - self.help_position |
|
192 |
|
193 def format_option_strings(self, option): |
|
194 """Return a comma-separated list of option strings & metavariables.""" |
|
195 if option.takes_value(): |
|
196 metavar = option.metavar or string.upper(option.dest) |
|
197 short_opts = [] |
|
198 for sopt in option._short_opts: |
|
199 short_opts.append(self._short_opt_fmt % (sopt, metavar)) |
|
200 long_opts = [] |
|
201 for lopt in option._long_opts: |
|
202 long_opts.append(self._long_opt_fmt % (lopt, metavar)) |
|
203 else: |
|
204 short_opts = option._short_opts |
|
205 long_opts = option._long_opts |
|
206 |
|
207 if self.short_first: |
|
208 opts = short_opts + long_opts |
|
209 else: |
|
210 opts = long_opts + short_opts |
|
211 |
|
212 return string.join(opts, ", ") |
|
213 |
|
214 class IndentedHelpFormatter (HelpFormatter): |
|
215 """Format help with indented section bodies. |
|
216 """ |
|
217 |
|
218 def __init__(self, |
|
219 indent_increment=2, |
|
220 max_help_position=24, |
|
221 width=None, |
|
222 short_first=1): |
|
223 HelpFormatter.__init__( |
|
224 self, indent_increment, max_help_position, width, short_first) |
|
225 |
|
226 def format_usage(self, usage): |
|
227 return _("usage: %s\n") % usage |
|
228 |
|
229 def format_heading(self, heading): |
|
230 return "%*s%s:\n" % (self.current_indent, "", heading) |
|
231 |
|
232 |
|
233 class TitledHelpFormatter (HelpFormatter): |
|
234 """Format help with underlined section headers. |
|
235 """ |
|
236 |
|
237 def __init__(self, |
|
238 indent_increment=0, |
|
239 max_help_position=24, |
|
240 width=None, |
|
241 short_first=0): |
|
242 HelpFormatter.__init__ ( |
|
243 self, indent_increment, max_help_position, width, short_first) |
|
244 |
|
245 def format_usage(self, usage): |
|
246 return "%s %s\n" % (self.format_heading(_("Usage")), usage) |
|
247 |
|
248 def format_heading(self, heading): |
|
249 return "%s\n%s\n" % (heading, "=-"[self.level] * len(heading)) |