12 return f
'Option::TYPE_{t.upper()}'
16 return f
'Option::LEVEL_{lv.upper()}'
22 v
= v
.strip('"').replace('"', '\\"')
26 def eval_value(v
, typ
):
32 if typ
in ('uint', 'int', 'size', 'secs', 'millisecs'):
35 return 'true' if v
else 'false'
46 for unit
, m
in times
.items():
49 # user defined literals
51 raise ValueError(f
'unknown value: {v}')
54 def set_default(default
, typ
):
57 v
= eval_value(default
, typ
)
58 return f
'.set_default({v})\n'
61 def set_daemon_default(default
, typ
):
64 v
= eval_value(default
, typ
)
65 return f
'.set_daemon_default({v})\n'
74 cxx
+= f
'.add_tag({v})\n'
78 def add_services(services
):
81 if len(services
) == 1:
82 return f
'.add_service("{services[0]}")\n'
84 param
= ', '.join(f
'"{s}"' for s
in services
)
85 return f
'.add_service({{{param}}})\n'
88 def add_see_also(see_also
):
91 param
= ', '.join(f
'"{v}"' for v
in see_also
)
92 return f
'.add_see_also({{{param}}})\n'
99 return f
'.set_description({v})\n'
102 def set_long_desc(desc
):
106 return f
'.set_long_description({v})\n'
109 def set_min_max(mi
, ma
, typ
):
110 if mi
is None and ma
is None:
112 if mi
is not None and ma
is not None:
113 min_v
= eval_value(mi
, typ
)
114 max_v
= eval_value(ma
, typ
)
115 if isinstance(min_v
, str) and isinstance(max_v
, int):
116 return f
'.set_min_max({min_v}, {max_v}ULL)\n'
117 elif isinstance(min_v
, int) and isinstance(max_v
, str):
118 return f
'.set_min_max({min_v}ULL, {max_v})\n'
120 return f
'.set_min_max({min_v}, {max_v})\n'
122 min_v
= eval_value(mi
, typ
)
123 return f
'.set_min({min_v})\n'
124 raise ValueError('set_max() is not implemented')
127 def set_enum_allowed(values
):
130 param
= ', '.join(f
'"{v}"' for v
in values
)
131 return f
'.set_enum_allowed({{{param}}})\n'
134 def add_flags(flags
):
139 cxx
+= f
'.set_flag(Option::FLAG_{flag.upper()})\n'
143 def set_validator(validator
):
144 if validator
is None:
146 validator
= validator
.rstrip()
147 return f
'.set_validator({validator})\n'
150 def add_verbatim(verbatim
):
153 return verbatim
+ '\n'
156 def yaml_to_cxx(opt
, indent
):
159 ctyp
= type_to_cxx(typ
)
160 level
= level_to_cxx(opt
['level'])
161 cxx
= f
'Option("{name}", {ctyp}, {level})\n'
162 cxx
+= set_desc(opt
.get('desc'))
163 cxx
+= set_long_desc(opt
.get('long_desc'))
164 cxx
+= set_default(opt
.get('default'), typ
)
165 cxx
+= set_daemon_default(opt
.get('daemon_default'), typ
)
166 cxx
+= set_min_max(opt
.get('min'), opt
.get('max'), typ
)
167 cxx
+= set_enum_allowed(opt
.get('enum_values'))
168 cxx
+= set_validator(opt
.get('validator'))
169 cxx
+= add_flags(opt
.get('flags'))
170 cxx
+= add_services(opt
.get('services'))
171 cxx
+= add_tags(opt
.get('tags'))
172 cxx
+= add_see_also(opt
.get('see_also'))
173 verbatim
= add_verbatim(opt
.get('verbatim'))
182 for line
in cxx
.split('\n'):
184 indented
.append(' ' * indent
+ line
+ '\n')
185 cxx
= ''.join(indented
)
192 return f
'OPT_{t.upper()}'
196 if opt
.get('with_legacy', False):
199 htyp
= type_to_h(typ
)
200 return f
'OPTION({name}, {htyp})'
205 TEMPLATE_CC
= '''#include "common/options.h"
208 std::vector<Option> get_{name}_options() {{
209 return std::vector<Option>({{
216 # PyYAML doesn't check for duplicates even though the YAML spec says
217 # that mapping keys must be unique and that duplicates must be treated
218 # as an error. See https://github.com/yaml/pyyaml/issues/165.
220 # This workaround breaks merge keys -- in "<<: *xyz", duplicate keys
221 # from xyz mapping raise an error instead of being discarded.
222 class UniqueKeySafeLoader(yaml
.SafeLoader
):
223 def construct_mapping(self
, node
, deep
=False):
224 mapping
= super().construct_mapping(node
, deep
)
226 for key_node
, _
in node
.value
:
227 key
= self
.construct_object(key_node
, deep
=deep
)
229 raise yaml
.constructor
.ConstructorError(None, None,
230 "found duplicate key",
238 prelude
, epilogue
= '', ''
240 prelude
, epilogue
= TEMPLATE_CC
.split('@body@')
245 name
= os
.path
.split(opts
.input)[-1]
246 name
= name
.rsplit('.', 1)[0]
247 name
= name
.replace('-', '_')
249 with
open(opts
.input) as infile
, \
250 open(opts
.output
, 'w') as cc_file
, \
251 open(opts
.legacy
, 'w') as h_file
:
252 yml
= yaml
.load(infile
, Loader
=UniqueKeySafeLoader
)
253 headers
= yml
.get('headers', '')
254 cc_file
.write(prelude
.format(name
=name
, headers
=headers
))
255 options
= yml
['options']
256 for option
in options
:
258 cc_file
.write(yaml_to_cxx(option
, opts
.indent
) + '\n')
259 if option
.get('with_legacy', False):
260 h_file
.write(yaml_to_h(option
) + '\n')
261 except ValueError as e
:
262 print(f
'failed to translate option "{name}": {e}',
265 cc_file
.write(epilogue
.replace("}}", "}"))
268 def readable_size(value
, typ
):
269 times
= dict(T
=1 << 40,
273 if isinstance(value
, str):
274 value
= value
.strip('"')
279 for unit
, m
in times
.items():
288 def readable_duration(value
, typ
):
289 times
= dict(day
=24*60*60,
292 if isinstance(value
, str):
293 value
= value
.strip('"')
296 if math
.floor(v
) != v
:
301 for unit
, m
in times
.items():
310 def readable_millisecs(value
, typ
):
315 with
open(opts
.input) as infile
, open(opts
.output
, 'w') as outfile
:
316 yml
= yaml
.load(infile
, Loader
=UniqueKeySafeLoader
)
317 options
= yml
['options']
318 for option
in options
:
320 if typ
in ('size', 'uint'):
321 do_readable
= readable_size
322 elif typ
in ('float', 'int', 'secs'):
323 do_readable
= readable_duration
324 elif typ
== 'millisecs':
325 do_readable
= readable_millisecs
328 for field
in ['default', 'min', 'max', 'daemon_default']:
329 v
= option
.get(field
)
331 option
[field
] = do_readable(v
, typ
)
332 yml
['options'] = options
333 yaml
.dump(yml
, outfile
, sort_keys
=False, indent
=2)
337 parser
= argparse
.ArgumentParser(
338 formatter_class
=argparse
.ArgumentDefaultsHelpFormatter
)
339 parser
.add_argument('-i', '--input', dest
='input',
340 default
='options.yaml',
341 help='the YAML file to be processed')
342 parser
.add_argument('-o', '--output', dest
='output',
344 help='the path to the generated .cc file')
345 parser
.add_argument('--legacy', dest
='legacy',
346 default
='legacy_options',
347 help='the path to the generated legacy .h file')
348 parser
.add_argument('--indent', type=int,
350 help='the number of spaces added before each line')
351 parser
.add_argument('--name',
352 help='the name of the option group')
353 parser
.add_argument('--raw', action
='store_true',
354 help='output the array without the full function')
355 parser
.add_argument('--op', choices
=('readable', 'translate'),
357 help='operation to perform.')
358 opts
= parser
.parse_args(sys
.argv
[1:])
359 if opts
.op
== 'translate':
365 if __name__
== '__main__':