def parse_config_file(self): config = yamlbro.load_yaml(self.requirements_file) if 'target-folder' in config.keys() and self.output_folder is None: print("Info: Files and Configuration will be stored in %s" % self.output_folder) self.output_folder = os.path.expanduser(config['target-folder']) if not os.path.exists(self.output_folder): os.makedirs(self.output_folder) if 'Bukkit' in config.keys(): bukkit_data = config['Bukkit'] for plugin_name in bukkit_data.keys(): data = bukkit_data[plugin_name] version = data['version'] configure_after_download = False configure_options = {} configure_script = None template_file = None defaults_file = None plugin_folder = None kwargs = {} if 'configure' in data.keys(): if 'options' in data['configure']: configure_after_download = True for option, value in data['configure']['options'].items(): configure_options[option] = value if 'args' in data['configure'].keys(): for key, value in data['configure']['args'].items(): kwargs[key] = value if 'script' in data['configure'].keys(): configure_script = data['configure']['script'] print("Script found: %s" % configure_script) if 'template' in data['configure'].keys(): template_file = data['configure']['template'] if 'defaults' in data['configure'].keys(): defaults_file = data['configure']['defaults'] if 'plugin-data-folder' in data['configure'].keys(): plugin_folder = data['configure']['plugin-data-folder'] if (template_file is not None and defaults_file is None) or ( defaults_file is not None and template_file is None): template_file = None defaults_file = None configure_after_download = False print(textwrap.dedent("""\n +==================================================================+ Configuration Error for {name} ({version}) +==================================================================+ Generating a plugins configuration file via template & default configuration requires the 'template', 'plugin-data-folder', and 'defaults' node to be present, and valid inside your requirements file ({configuration}). Using one without the others nulls the functionality, as a template has placeholders for your config data, and the defaults file fills in the blanks where you have not specified values. Using either without a plugin data folder doesn't make sense, as you'd want to keep the template and defaults once it's generated. McResolver will continue to download these plugins, though configuration cannot happen unless you provide a template, plugin data folder, and defaults file, or a script that handles the configuration. +==================================================================+ """).format(name=plugin_name, version=version, configuration=self.requirements_file)) if isinstance(plugin_name, int) or plugin_name.isdigit(): print("Invalid Bukkit plugin '%s', plugin name or slug (in plugins url) is required") continue try: bukkit_resource = BukkitResource.from_name(plugin_name) except ValueError: print("Unable to retrieve Bukkit plugin %s (v. %s)" % (plugin_name, version)) if not bukkit_resource.has_version(version=version): if not self.retrieve_latest_on_version_error: print("Unable to retrieve version %s for %s" % (version, plugin_name)) continue else: self.bukkit_resources[plugin_name] = { 'version': 'latest', 'name': plugin_name, 'resource': bukkit_resource, 'configure': configure_after_download, 'script': configure_script, 'configure-options': configure_options, 'kwargs': kwargs, 'template': template_file, 'defaults': defaults_file, 'plugin-folder': plugin_folder, } else: self.bukkit_resources[plugin_name] = { 'version': version, 'name': plugin_name, 'resource': bukkit_resource, 'configure': configure_after_download, 'script': configure_script, 'configure-options': configure_options, 'kwargs': kwargs, 'template': template_file, 'defaults': defaults_file, 'plugin-folder': plugin_folder, } print("Bukkit information retrieved on %s (v: %s)" % ( plugin_name, self.bukkit_resources[plugin_name]['version'])) # Go ahead and collect all the Spigot resources in the yml file # and their desired versions (or latest) if 'Spigot' in config.keys(): spigot_plugins = config['Spigot'] for plugin_id in spigot_plugins.keys(): data = spigot_plugins[plugin_id] spigot_resource = None version = data['version'] name = data['name'] configure_after_download = False configure_script = None configure_options = {} template_file = None defaults_file = None plugin_folder = None kwargs = {} if 'configure' in data.keys(): if 'options' in data['configure']: configure_after_download = True for option, value in data['configure']['options'].items(): configure_options[option] = value if 'args' in data['configure'].keys(): for key, value in data['configure']['args'].items(): kwargs[key] = value if 'script' in data['configure'].keys(): configure_script = data['configure']['script'] if 'template' in data['configure'].keys(): template_file = data['configure']['template'] if 'defaults' in data['configure'].keys(): defaults_file = data['configure']['defaults'] if 'plugin-data-folder' in data['configure'].keys(): plugin_folder = data['configure']['plugin-data-folder'] if (template_file is not None and defaults_file is None) or ( defaults_file is not None and template_file is None): template_file = None defaults_file = None configure_after_download = False print(textwrap.dedent("""\n +==================================================================+ Configuration Error for {name} ({version}) +==================================================================+ Generating a plugins configuration file via template & default configuration requires the 'template', 'plugin-data-folder', and 'defaults' node to be present, and valid inside your requirements file ({configuration}). Using one without the others nulls the functionality, as a template has placeholders for your config data, and the defaults file fills in the blanks where you have not specified values. Using either without a plugin data folder doesn't make sense, as you'd want to keep the template and defaults once it's generated. McResolver will continue to download these plugins, though configuration cannot happen unless you provide a template, plugin data folder, and defaults file, or a script that handles the configuration. +==================================================================+ """).format(name=name, version=version, configuration=self.requirements_file)) if isinstance(plugin_id, int) or plugin_id.isdigit(): spigot_resource = SpigotResource.from_id(plugin_id) else: print( "Unable to retrieve Spigot plugin (%s) via its name... Potential feature in the future!" % name) continue if spigot_resource is None: print("Invalid plugin %s (v: %s)" % (name, version)) continue if not spigot_resource.has_version(version=version): if not self.retrieve_latest_on_version_error: print("Unable to retrieve version %s for %s" % (version, name)) continue self.spigot_resources[plugin_id] = { 'version': 'latest', 'name': name, 'resource': spigot_resource, 'configure': configure_after_download, 'script': configure_script, 'configure-options': configure_options, 'kwargs': kwargs, 'template': template_file, 'defaults': defaults_file, 'plugin-folder': plugin_folder, } else: self.spigot_resources[plugin_id] = { 'version': version, 'name': name, 'resource': spigot_resource, 'configure': configure_after_download, 'script': configure_script, 'configure-options': configure_options, 'kwargs': kwargs, 'template': template_file, 'defaults': defaults_file, 'plugin-folder': plugin_folder, } if isinstance(plugin_id, int): print("Spigot information retrieved on %s [id. %s] (v. %s)" % (name, plugin_id, self.spigot_resources[plugin_id][ 'version']))
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)