1 """Framework for specifying multiple options to a module on the command line.
2
3 For modules like taggers and parsers, the options available will vary
4 depending on what component is selected. This framework allows a
5 specific component to list its available options and how they should
6 be interpreted.
7
8 This is one of my greater works of genius to be found in this codebase.
9 It's incredibly useful so often.
10
11 """
12 """
13 ============================== License ========================================
14 Copyright (C) 2008, 2010-12 University of Edinburgh, Mark Granroth-Wilding
15
16 This file is part of The Jazz Parser.
17
18 The Jazz Parser is free software: you can redistribute it and/or modify
19 it under the terms of the GNU General Public License as published by
20 the Free Software Foundation, either version 3 of the License, or
21 (at your option) any later version.
22
23 The Jazz Parser is distributed in the hope that it will be useful,
24 but WITHOUT ANY WARRANTY; without even the implied warranty of
25 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 GNU General Public License for more details.
27
28 You should have received a copy of the GNU General Public License
29 along with The Jazz Parser. If not, see <http://www.gnu.org/licenses/>.
30
31 ============================ End license ======================================
32
33 """
34 __author__ = "Mark Granroth-Wilding <mark.granroth-wilding@ed.ac.uk>"
38 """
39 Simple wrapper for strings to mark that they haven't yet been
40 processed as option values.
41 """
44
46
48 """
49 An option that can be specified on the command line and that is
50 specific to a certain modular component (e.g. parser, tagger).
51
52 Example use of a ModuleOption::
53 ModuleOption('test',
54 lambda x: int(x),
55 help_text="A test option",
56 default=2,
57 usage="test=X, where X is an int")
58
59 This will accept integer values for the option called "test". If no
60 value is given, it will default to 2.
61
62 A filter function may be given which will be applied to the value during
63 option processing. If the filter raises an exception, the value will be
64 reported as invalid.
65
66 You may also specify multiple filters as a tuple of functions. Each will
67 be applied in order and the first value successfully returned will be
68 used.
69
70 """
71 - def __init__(self, name, filter=None,
72 help_text="No help text available", default=None,
73 usage=None, required=False):
74 self.name = name
75 if filter is not None:
76 self.filter = filter
77 else:
78 self.filter = None
79 self.help_text = help_text
80 self.default = default
81 self._usage = usage
82 self.required = required
83
85 if self._usage is not None:
86 return self._usage
87 else:
88 return "%s=X" % self.name
89 usage = property(_get_usage)
90
92 """
93 Pulls the appropriate value out of a dictionary of options and
94 return it.
95 """
96 if self.name not in options:
97 if self.required:
98 raise ModuleOptionError, "option '%s' is required, but was not specified" % self.name
99
100 return self.default
101 if type(options[self.name]) == UnprocessedOptionValue:
102
103 if self.filter is None:
104
105 return options[self.name].value
106 elif type(self.filter) == tuple:
107
108 errs = []
109 for filt in self.filter:
110 try:
111 return filt(options[self.name].value)
112 except Exception, err:
113 errs.append(err)
114
115 raise ModuleOptionError, "invalid value for option '%s': "\
116 "%s" % (self.name, "; ".join([str(err) for err in errs]))
117 else:
118 try:
119 return self.filter(options[self.name].value)
120 except Exception, err:
121 raise ModuleOptionError, "invalid value for option "\
122 "'%s': %s" % (self.name, err)
123 else:
124
125 return options[self.name]
126
127 @staticmethod
129 """
130 Takes an option string in the format in which options should
131 be specified on the command line and returns a dictionary
132 ready to pass to the options themselves.
133
134 Module options must be in the format opt1=val1:opt2=val2:etc.
135 Later options override earlier ones.
136
137 C{optstr} may also be a list of strings. In this case, the
138 options in each string will be concatenated. This allows you
139 to take options from multiple CL options using optparse's
140 C{append} action.
141
142 If the optstr is None, or an empty list, returns an empty dict.
143 This allows you to pass in a value directly from an optparse
144 parser.
145
146 """
147 if type(optstr) == list:
148 optstr = ":".join(optstr)
149 options = {}
150 if optstr is not None and len(optstr.strip()):
151 for optval in optstr.split(":"):
152 opt, __, val = optval.partition("=")
153 if len(val) == 0:
154 raise ModuleOptionError, "Module options must be in the "\
155 "format opt1=val1:opt2=val2:etc. Got: %s" % optval
156
157
158 options[opt] = UnprocessedOptionValue(val)
159 return options
160
161 @staticmethod
163 """
164 Takes a dictionary of options, which may be from command-line
165 strings (via process_option_string) or internal, and a list
166 of allowed options and returns a dictionary of all the option
167 values.
168 """
169 used_opts = []
170 options = {}
171 for option in available_opts:
172
173 options[option.name] = option.get_value(optdict)
174 used_opts.append(option.name)
175
176 for key in optdict.keys():
177 if key not in used_opts:
178 raise ModuleOptionError, "'%s' is not a valid option." \
179 % key
180 return options
181
182 -def options_help_text(options, intro=None):
183 """
184 Produces a load of help text to output to the command line to
185 display the usage of all of the options in the list.
186 """
187 if len(options) == 0:
188 return "This module has no options"
189 from jazzparser.utils.tableprint import pprint_table
190 from StringIO import StringIO
191 rows = []
192
193 for opt in [o for o in options if o.required]:
194 rows.append([opt.name, "%s (REQUIRED)" % opt.usage, opt.help_text])
195 for opt in [o for o in options if not o.required]:
196 rows.append([opt.name, opt.usage, opt.help_text])
197 output = StringIO()
198
199 pprint_table(output, rows, separator="",
200 justs=[True,True,True],
201 widths=[None,35,40],
202 blank_row=True)
203 strout = output.getvalue()
204 output.close()
205 if intro is not None:
206 strout = "%s\n%s\n%s" % (intro, "="*len(intro), strout)
207 return strout
208
211
215 """
216 A filter function for filenames of existing files.
217 Errors if the file doesn't exist.
218
219 """
220 if value is None:
221 return None
222 import os
223 filename = os.path.abspath(value)
224 if not os.path.exists(filename):
225 raise ModuleOptionError, "the file %s does not exist" % filename
226 else:
227 return filename
228
230 """
231 A filter function for a new filename.
232 Doesn't require the file to exist, but errors if the directory
233 doesn't exist.
234
235 """
236 if value is None:
237 return None
238 import os
239 filename = os.path.abspath(value)
240 dirname = os.path.dirname(filename)
241 if not os.path.exists(dirname):
242 raise ModuleOptionError, "the directory %s does not exist" % dirname
243 else:
244 return filename
245
247 """
248 A filter function for floats that should lie between 0.0 and 1.0.
249
250 Accepts the range ends (0.0 and 1.0). Raises an errror for any incorrectly
251 formatted numbers or numbers outside this range.
252
253 This is useful for probabilities or ratios.
254
255 """
256 if value is None:
257 return None
258 try:
259 value = float(value)
260 except ValueError:
261 raise ModuleOptionError, "not a float: %s" % value
262 if value < 0.0 or value > 1.0:
263 raise ModuleOptionError, "float not in range 0.0 - 1.0: %s" % value
264 return value
265
267 """
268 Filter function constructor. Returns a filter function that will verify
269 that the filtered value is among the C{dic}'s keys and return the
270 corresponding value if it is.
271
272 """
273 def _filter(value):
274 if value not in dic:
275 raise ModuleOptionError, "invalid option value: %s. Possible values "\
276 "are: %s" % (value, ", ".join(dic.keys()))
277 return dic[value]
278 return _filter
279
281 """
282 Filter function constructor.
283
284 Returns a filter function that will verify that the value is one of those
285 in the list and then just return that value if it is.
286
287 """
288 def _filter(value):
289 if value not in lst:
290 raise ModuleOptionError, "invalid option value: %s. Possible values "\
291 "are: %s" % (value, ", ".join(lst))
292 return value
293 return _filter
294