|
|
|
@ -1,8 +1,11 @@ |
|
|
|
|
import io |
|
|
|
|
import os |
|
|
|
|
import re |
|
|
|
|
|
|
|
|
|
from ruamel import yaml |
|
|
|
|
|
|
|
|
|
INDENT = 4 |
|
|
|
|
SEQUENCE_INDENT = 2 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _insert_newline_before_comment(config, field_name): |
|
|
|
@ -15,7 +18,7 @@ def _insert_newline_before_comment(config, field_name): |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _schema_to_sample_configuration(schema, level=0): |
|
|
|
|
def _schema_to_sample_configuration(schema, level=0, parent_is_sequence=False): |
|
|
|
|
''' |
|
|
|
|
Given a loaded configuration schema, generate and return sample config for it. Include comments |
|
|
|
|
for each section based on the schema "desc" description. |
|
|
|
@ -24,14 +27,29 @@ def _schema_to_sample_configuration(schema, level=0): |
|
|
|
|
if example is not None: |
|
|
|
|
return example |
|
|
|
|
|
|
|
|
|
config = yaml.comments.CommentedMap( |
|
|
|
|
[ |
|
|
|
|
(section_name, _schema_to_sample_configuration(section_schema, level + 1)) |
|
|
|
|
for section_name, section_schema in schema['map'].items() |
|
|
|
|
] |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
add_comments_to_configuration(config, schema, indent=(level * INDENT)) |
|
|
|
|
if 'seq' in schema: |
|
|
|
|
config = yaml.comments.CommentedSeq( |
|
|
|
|
[ |
|
|
|
|
_schema_to_sample_configuration(item_schema, level, parent_is_sequence=True) |
|
|
|
|
for item_schema in schema['seq'] |
|
|
|
|
] |
|
|
|
|
) |
|
|
|
|
add_comments_to_configuration_sequence( |
|
|
|
|
config, schema, indent=(level * INDENT) + SEQUENCE_INDENT |
|
|
|
|
) |
|
|
|
|
elif 'map' in schema: |
|
|
|
|
config = yaml.comments.CommentedMap( |
|
|
|
|
[ |
|
|
|
|
(section_name, _schema_to_sample_configuration(section_schema, level + 1)) |
|
|
|
|
for section_name, section_schema in schema['map'].items() |
|
|
|
|
] |
|
|
|
|
) |
|
|
|
|
indent = (level * INDENT) + (SEQUENCE_INDENT if parent_is_sequence else 0) |
|
|
|
|
add_comments_to_configuration_map( |
|
|
|
|
config, schema, indent=indent, skip_first=parent_is_sequence |
|
|
|
|
) |
|
|
|
|
else: |
|
|
|
|
raise ValueError('Schema at level {} is unsupported: {}'.format(level, schema)) |
|
|
|
|
|
|
|
|
|
return config |
|
|
|
|
|
|
|
|
@ -42,13 +60,12 @@ def _comment_out_line(line): |
|
|
|
|
if not stripped_line or stripped_line.startswith('#'): |
|
|
|
|
return line |
|
|
|
|
|
|
|
|
|
# Comment out the names of optional sections. |
|
|
|
|
one_indent = ' ' * INDENT |
|
|
|
|
if not line.startswith(one_indent): |
|
|
|
|
return '# ' + line |
|
|
|
|
# Comment out the names of optional sections, inserting the '#' after any indent for aesthetics. |
|
|
|
|
matches = re.match(r'(\s*)', line) |
|
|
|
|
indent_spaces = matches.group(0) if matches else '' |
|
|
|
|
count_indent_spaces = len(indent_spaces) |
|
|
|
|
|
|
|
|
|
# Otherwise, comment out the line, but insert the "#" after the first indent for aesthetics. |
|
|
|
|
return '# '.join((one_indent, line[INDENT:])) |
|
|
|
|
return '# '.join((indent_spaces, line[count_indent_spaces:])) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
REQUIRED_KEYS = {'source_directories', 'repositories', 'keep_daily'} |
|
|
|
@ -90,7 +107,12 @@ def _render_configuration(config): |
|
|
|
|
''' |
|
|
|
|
Given a config data structure of nested OrderedDicts, render the config as YAML and return it. |
|
|
|
|
''' |
|
|
|
|
return yaml.round_trip_dump(config, indent=INDENT, block_seq_indent=INDENT) |
|
|
|
|
dumper = yaml.YAML() |
|
|
|
|
dumper.indent(mapping=INDENT, sequence=INDENT + SEQUENCE_INDENT, offset=INDENT) |
|
|
|
|
rendered = io.StringIO() |
|
|
|
|
dumper.dump(config, rendered) |
|
|
|
|
|
|
|
|
|
return rendered.getvalue() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def write_configuration(config_filename, rendered_config, mode=0o600): |
|
|
|
@ -112,13 +134,49 @@ def write_configuration(config_filename, rendered_config, mode=0o600): |
|
|
|
|
os.chmod(config_filename, mode) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def add_comments_to_configuration(config, schema, indent=0): |
|
|
|
|
def add_comments_to_configuration_sequence(config, schema, indent=0): |
|
|
|
|
''' |
|
|
|
|
If the given config sequence's items are maps, then mine the schema for the description of the |
|
|
|
|
map's first item, and slap that atop the sequence. Indent the comment the given number of |
|
|
|
|
characters. |
|
|
|
|
|
|
|
|
|
Doing this for sequences of maps results in nice comments that look like: |
|
|
|
|
|
|
|
|
|
``` |
|
|
|
|
things: |
|
|
|
|
# First key description. Added by this function. |
|
|
|
|
- key: foo |
|
|
|
|
# Second key description. Added by add_comments_to_configuration_map(). |
|
|
|
|
other: bar |
|
|
|
|
``` |
|
|
|
|
''' |
|
|
|
|
if 'map' not in schema['seq'][0]: |
|
|
|
|
return |
|
|
|
|
|
|
|
|
|
for field_name in config[0].keys(): |
|
|
|
|
field_schema = schema['seq'][0]['map'].get(field_name, {}) |
|
|
|
|
description = field_schema.get('desc') |
|
|
|
|
|
|
|
|
|
# No description to use? Skip it. |
|
|
|
|
if not field_schema or not description: |
|
|
|
|
return |
|
|
|
|
|
|
|
|
|
config[0].yaml_set_start_comment(description, indent=indent) |
|
|
|
|
|
|
|
|
|
# We only want the first key's description here, as the rest of the keys get commented by |
|
|
|
|
# add_comments_to_configuration_map(). |
|
|
|
|
return |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def add_comments_to_configuration_map(config, schema, indent=0, skip_first=False): |
|
|
|
|
''' |
|
|
|
|
Using descriptions from a schema as a source, add those descriptions as comments to the given |
|
|
|
|
config before each field. This function only adds comments for the top-most config map level. |
|
|
|
|
Indent the comment the given number of characters. |
|
|
|
|
config mapping, before each field. Indent the comment the given number of characters. |
|
|
|
|
''' |
|
|
|
|
for index, field_name in enumerate(config.keys()): |
|
|
|
|
if skip_first and index == 0: |
|
|
|
|
continue |
|
|
|
|
|
|
|
|
|
field_schema = schema['map'].get(field_name, {}) |
|
|
|
|
description = field_schema.get('desc') |
|
|
|
|
|
|
|
|
@ -127,6 +185,7 @@ def add_comments_to_configuration(config, schema, indent=0): |
|
|
|
|
continue |
|
|
|
|
|
|
|
|
|
config.yaml_set_comment_before_after_key(key=field_name, before=description, indent=indent) |
|
|
|
|
|
|
|
|
|
if index > 0: |
|
|
|
|
_insert_newline_before_comment(config, field_name) |
|
|
|
|
|
|
|
|
|