def generate_templates(self): def get_name_from_key(key): return key.lower().replace('-', '_').replace('.', '_') def assign_dict_nested_path(dict, path, value): def get(d, keys): for key in keys: if key not in d: d[key] = OrderedDict() # TODO Investigate? Might need to instance another object # # print("Assigned empty value to path %s" % path) d = d[key] return d def set(d, keys, value): d = get(d, keys[:-1]) d[keys[-1]] = value if '.' in path: set(dict, path.split('.'), value) else: dict[path] = value def recursive_dictionary_collect(template_dict, parent_key, data): for key, value in data.items(): new_key = key if parent_key is None else "%s.%s" % (parent_key, key) if isinstance(value, dict) or isinstance(value, OrderedDict): recursive_dictionary_collect(template_dict, new_key, value) else: if isinstance(value, list): depth = len(new_key.split('.')) * 2 value.append('mcresolverdepth=%s' % depth) template_dict[new_key] = value flat_key_template = OrderedDict() default_pluginfile_data = yamlbro.load_yaml(self.generate_base_config_file) for key, value in default_pluginfile_data.items(): if isinstance(value, dict) or isinstance(value, OrderedDict): # replica_dict[key] = value recursive_dictionary_collect(flat_key_template, key, value) else: if isinstance(value, list): value.append( 'mcresolverdepth=1') # Hack around the template, and add the depth to prepend to the node flat_key_template[key] = value template_default_node_values = OrderedDict() node_value_types = {} for key, value in flat_key_template.items(): node_name = key if '.' in key: node_split = key.split('.') if len(node_split) >= 2: node_name = ".".join(node_split[-2:]) else: node_name = node_split[-1] node_name = get_name_from_key(node_name) # print("Key [%s] Node-Name %s" % (key, node_name)) # node_name = get_name_from_key(node_name) value_type = value.__class__.__name__ try: tdval = type(value)(value) if value_type == "bool": tdval = str(tdval).lower() template_default_node_values[node_name] = tdval node_value_types[node_name] = value_type node_type = tdval.__class__.__name__ except: template_default_node_values[node_name] = value node_value_types[node_name] = value_type node_type = value_type.__class__.__name__ flat_key_template[key] = "{{{{{node}}}}}".format(node=node_name) expanded_key_config_template = OrderedDict() for key, value in flat_key_template.items(): assign_dict_nested_path(expanded_key_config_template, key, value) config_template = yaml.dump(expanded_key_config_template, default_flow_style=False, indent=2, width=1000) with open(self.generate_base_config_file, 'r') as default_configuration_data: default_plugin_config = default_configuration_data.read() config_template = restore_yaml_comments(config_template, default_plugin_config) # Loop through all the nodes and their values, then change the jinja2 template # Variable to follow the format required for that item.. # The reason we do this is to re-assign types to values (via their template-names) # As by default they all are quoted and are treated as strings. # We don't want this! for node, type in node_value_types.items(): if type == "bool" or type == "int" or type == "float": config_template = config_template.replace("'{{{{{node}}}}}'".format(node=node), "{{{{{node}}}}}".format(node=node)) elif type == "str": continue elif type == "list": # If the value of the item is a list # Then there's quite a few things we have to do in order to preserve the format of the file! # First of all involves getting the depth (via a default value generated and added into the list) # Though this won't wind up in the final template. We do this because we need to have # The valid number of spaces before the list items, otherwise YAML Will break. node_list_values = template_default_node_values[node].copy() depth = 0 for line in node_list_values: if 'mcresolverdepth' in line: # The depth of the item will be indexed by this! # Depth is determined by how many parent keys, are above the child node # Multiplied by 2, for yaml conventions. # Example: Top-Level lists only have 2 spaces # Whereas a list under top.level.list would have 4 depth = int(line.split('=')[1]) break # Collect all the nodes list items that dont have a depth attribute node_list_values = [line for line in node_list_values if 'mcresolverdepth' not in line] # Then reassign this value to the template defaults, # as we don't want the depth to be inside the users template_default_node_values[node] = node_list_values # Next we generate a loop statement for inside the template # On our node, to assure all the items in the list are processed loop_statement = "\n{{% for {node}_item in {node} %}}{depth}- {{{{list_item}}}}\n{{% endfor %}}".format( depth=' ' * depth, node=node ) # Lastly, for this part, replace the previous {{node}} item with the new list-generating statement # That was created above. config_template = config_template.replace(" '{{{{{node}}}}}'".format(node=node), loop_statement) # Get the file contents for the default template nodes and their values in a yaml output! defaults_file_contents = yaml.dump(template_default_node_values, default_flow_style=False) # Next up, we need to remove all the 'none' values inside of this file, as it's really not a good idea # to have yaml parse "none" values... It'd return a string. So we make them blank instead. defaults_file_contents = defaults_file_contents.replace(": none", ": ") write_file(os.path.join(self.output_folder, '%s-template.yml' % self.generate_plugin_name), config_template) write_file(os.path.join(self.output_folder, '%s-defaults.yml' % self.generate_plugin_name), defaults_file_contents)
def test_template_generation(): def get_name_from_key(key): return key.lower().replace('-', '_').replace('.', '_') __dirname, __init_python_script = os.path.split(os.path.abspath(__file__)) ess_file = os.path.join(__dirname, 'essentials.yml') with open(ess_file, 'r') as yaml_file: default_essentials_config = yaml.safe_load(yaml_file) essentials_config = default_essentials_config.copy() def assign_dict_nested_path(dict, path, value): def get(d, keys): for key in keys: if key not in d: d[key] = OrderedDict() # TODO Investigate? Might need to instance another object # # print("Assigned empty value to path %s" % path) d = d[key] return d def set(d, keys, value): d = get(d, keys[:-1]) d[keys[-1]] = value if '.' in path: set(dict, path.split('.'), value) else: dict[path] = value flat_dictionary_template = OrderedDict() def recursive_dictionary_collect(template_dict, parent_key, data): for key, value in data.items(): new_key = key if parent_key is None else "%s.%s" % (parent_key, key) if isinstance(value, dict) or isinstance(value, OrderedDict): recursive_dictionary_collect(template_dict, new_key, value) else: if isinstance(value, list): depth = len(new_key.split('.')) * 2 value.append('mcresolverdepth=%s' % depth) template_dict[new_key] = value for key, value in default_essentials_config.items(): if isinstance(value, dict) or isinstance(value, OrderedDict): # replica_dict[key] = value recursive_dictionary_collect(flat_dictionary_template, key, value) else: if isinstance(value, list): value.append('mcresolverdepth=1') # Hack around the template, and add the depth to prepend to the node flat_dictionary_template[key] = value template_defaults = OrderedDict() node_types = {} for key, value in flat_dictionary_template.items(): node_name = key if '.' in key: node_split = key.split('.') if len(node_split) >= 2: node_name = ".".join(node_split[-2:]) else: node_name = node_split[-1] node_name = get_name_from_key(node_name) # print("Key [%s] Node-Name %s" % (key, node_name)) # node_name = get_name_from_key(node_name) value_type = value.__class__.__name__ try: tdval = type(value)(value) if value_type == "bool": tdval = str(tdval).lower() template_defaults[node_name] = tdval node_types[node_name] = value_type node_type = tdval.__class__.__name_ except: template_defaults[node_name] = value node_types[node_name] = value_type node_type = value_type.__class__.__name__ flat_dictionary_template[key] = "{{{{{node}}}}}".format(node=node_name) essentials_template = OrderedDict() for key, value in flat_dictionary_template.items(): # print("Key %s=%s (%s)" % (key, value, type(value).__name__)) assign_dict_nested_path(essentials_template, key, value) # print("%s=%s" % (node_name, value)) # print(essentials_template) # config_yaml = default_essentials_config.dump(default_flow_style=False) # print(config_yaml) # essentials_template_dump = yaml.dump(essentials_template, default_flow_style=False, indent=2, width=1000) with open(ess_file, 'r') as essentials_default_data: default_essentials_data = essentials_default_data.read() essentials_template_dump = restore_yaml_comments(essentials_template_dump, default_essentials_data) for node, type in node_types.items(): if type == "bool" or type == "int" or type == "float": essentials_template_dump = essentials_template_dump.replace("'{{{{{node}}}}}'".format(node=node), "{{{{{node}}}}}".format(node=node)) elif type == "str": continue elif type == "list": item_list = template_defaults[node] depth = 2 list_items = [] for line in item_list: if 'mcresolverdepth' in line: depth = int(line.split('=')[1]) else: list_items.append(line) depth = depth * 2 item_depth = depth + 2 # item_list = [line for line in item_list if 'mcresolverdepth' not in line] template_defaults[node] = list_items loop_statement = "\n{{% for {node}_item in {node} %}}{depth}- {{{{{node}_item}}}}\n{{% endfor %}}" loop_statement = loop_statement.format(depth=' ' * depth, node=node) essentials_template_dump = essentials_template_dump.replace(" '{{{{{node}}}}}'".format(node=node), loop_statement) # print(essentials_template_dump) essentials_final_template = "" for line in essentials_template_dump.splitlines(): if 'mcresolverdepth' in line: print("Found depth line in %s" % line) else: essentials_final_template += line + "\n" defaults_dump = yaml.dump(template_defaults, default_flow_style=False) defaults_dump = defaults_dump.replace(": none", ": ") for key, value in template_defaults.items(): assert key in defaults_dump assert key in essentials_template_dump