def parameter_public_entry(self): return (PARAMETER_PUBLIC_ENTRY_CONST.format( param_class=self.param_class, type=self.cpp_type, immutable_accessor_name=to_pascal_case(self.param_name), param_variable_name=self.param_variable_name, ) if self.is_constant else PARAMETER_PUBLIC_ENTRY.format( param_class=self.param_class, type=self.cpp_type, immutable_accessor_name=to_pascal_case(self.param_name), mutable_accessor_name="Mutable" + to_pascal_case(self.param_name), param_variable_name=self.param_variable_name, ))
def load_command_line_arg_into_config(self): return LOAD_COMMAND_LINE_ARG_INTO_CONFIG.format( param_name=self.param_name, param_accessor_name=to_pascal_case(self.param_name), dependencies="", arg_prefix="", )
def load_command_line_arg_into_config_with_dependencies( self, dependencies: str, arg_prefix: str): return LOAD_COMMAND_LINE_ARG_INTO_CONFIG.format( param_name=self.param_name, param_accessor_name=to_pascal_case(self.param_name), dependencies=dependencies, arg_prefix=arg_prefix, )
def create_header_config_list_from_metadata( top_level_config_name: str, config_metadata: dict) -> List[CppHeaderConfig]: """Takes the config metadata loaded by config_yaml_loader, and converts it to a list of CppHeaderConfig objects; this includes setting the dependency graphs needed for the configs. :param top_level_config_name: the name of the top level config :param config_metadata: the dictionary containing the config metadata :return: list of CppHeaderConfig objects """ cpp_configs_dict = {} dependency_graph = nx.DiGraph() top_level_config = CppHeaderConfig(top_level_config_name, True) # first pass to construct all CppHeaderConfig objects for config, metadata in config_metadata.items(): config_name = to_pascal_case(config.split(".")[0]) config = CppHeaderConfig(config_name) top_level_config.include_config(config) if PARAMETER_KEY in metadata: for parameter in metadata[PARAMETER_KEY]: param_metadata = list(parameter.values())[0] param_type = list(parameter.keys())[0] cpp_param = CppParameter(param_type, param_metadata) config.add_parameter(cpp_param) cpp_configs_dict[config_name] = config dependency_graph.add_node(config_name, config=config) # second pass to create dependency graph for config, metadata in config_metadata.items(): config_name = to_pascal_case(config.split(".")[0]) config = cpp_configs_dict[config_name] if INCLUDE_KEY in metadata: for included_yaml in metadata[INCLUDE_KEY]: included_config_name = to_pascal_case( included_yaml.split(".")[0]) config.include_config( cpp_configs_dict[included_config_name]) # add an edge from config node to included config node dependency_graph.add_edge(config_name, included_config_name) # for each node, create a subgraph of relevant dependencies # Note: This can be optimized by doing traversal from each source, and creating subgraphs for # all its descendants during the same traversal for node in dependency_graph.nodes: # find the subgraph of the dependency graph relevant to the current node dependency_graph.nodes[node][ "config"].dependency_graph = dependency_graph.subgraph( nx.algorithms.dag.descendants(dependency_graph, node)) top_level_config.dependency_graph = dependency_graph cpp_configs = [ dependency_graph.nodes[node]["config"] for node in list( reversed(list(nx.topological_sort(dependency_graph)))) ] cpp_configs.append(top_level_config) return cpp_configs
def create_config_list_from_metadata( top_level_config_name, config_metadata ) -> List[CConfig]: """Takes the config metadata loaded by config_yaml_loader and converts it to a list of CConfig objects. The CConfig objects contain useful string representations that can directly be written to the generated file. Note: The top level config includes all other configs by default. :param top_level_config_name: The name of the "top level" config that contains all configs :param config_metadata: The output of config_yaml_loader :returns: list of CConfig """ c_configs = [] top_level_config = CConfig( top_level_config_name, f"{top_level_config_name}_ptr" ) for config, metadata in config_metadata.items(): # we convert the yaml file name to pascal case # and store that as the config_name config_name = to_pascal_case(config.split(".")[0]) config = CConfig( config_name, "{}_ptr->{}".format(top_level_config_name, config_name) ) # a config can be empty if it has NO constant parameters # an empty config will get generated as an empty struct which is # NOT allowed in C, this flag will make sure we don't generate empty_configs # which is a config that has neither parameters nor includes config_empty = True # add all the valid CParameters to the CConfig if PARAMETER_KEY in metadata: for parameter in metadata[PARAMETER_KEY]: param_metadata = list(parameter.values())[0] param_type = list(parameter.keys())[0] if CONSTANT_KEY in param_metadata: if param_type not in C_TYPE_MAP: # we need to ignore parameters that are # meant for cpp parameters (ex. factory, enum) continue param_type = C_TYPE_MAP[param_type] # this is the fully resolved pointer access # to the location of this parameter ptr_to_instance = "{}->{}".format( config.ptr_to_instance, param_metadata["name"] ) c_param = CParameter( param_metadata["name"], param_type, param_metadata["value"], ) config.add_parameter(c_param) config_empty = False # add all the valid included configs to the CConfig if INCLUDE_KEY in metadata: for included_yaml in metadata["include"]: config.include_config(to_pascal_case(included_yaml.split(".")[0])) config_empty = False if not config_empty: top_level_config.include_config(config_name) c_configs.append(config) c_configs.append(top_level_config) return c_configs
def write_config_metadata_proto( output_proto: str, top_level_proto: str, config_metadata: dict, ): """Generates the .proto file contain all the protobuf representations of all dynamic parameter configs. :param output_proto: the name of the proto :param top_level_proto: the top level proto name :param config_metadata: the dictionary containing the config metadata """ output_proto_contents = "" list_of_includes = [] for config, config_definition in config_metadata.items(): message_contents = "" entry_count = 1 name = to_pascal_case(config.split(".")[0]) list_of_includes.append(config) # generate includes if "include" in config_definition: for included_config in config_definition["include"]: # There is no way to forward declare messages in proto # so lets make use of google.protobuf.Any to store nested # configs # # Since we are autogenerating, we should know which index # corresponds to which type message_contents += PROTO_CONFIG_ENTRY.format( name=included_config.split(".")[0], count=entry_count) entry_count += 1 # generate parameters if "parameters" in config_definition: for param_entry in config_definition["parameters"]: for param_type, param_definition in param_entry.items(): message_contents += "".join( PROTO_PARAM_ENTRY.format( type=type_map.PROTO_TYPE_MAP[param_type], name=param_definition["name"], count=entry_count, )) entry_count += 1 # append to output output_proto_contents += PROTO_MESSAGE_DEFINITION.format( name=name, contents=message_contents, ) # make the top level config top_level_config_contents = "" entry_count = 1 for include in list(set(list_of_includes)): top_level_config_contents += PROTO_CONFIG_ENTRY.format( name=include.split(".")[0], count=entry_count) entry_count += 1 output_proto_contents += PROTO_MESSAGE_DEFINITION.format( name=top_level_proto, contents=top_level_config_contents) # write the output with open(f"{output_proto}", "w") as proto_file: proto_file.write( CONFIG_PROTO.format( autogen_warning=AUTOGEN_WARNING, contents=output_proto_contents, ))