Loading .gitignore 0 → 100644 +3 −0 Original line number Diff line number Diff line .idea __pycache__ *.egg-info genopts/__init__.py 0 → 100644 +2 −0 Original line number Diff line number Diff line # This file is here just so that 'genopts' becomes a package and we can import it in tests # The script itself is standalone genopts/genopts.py 0 → 100755 +589 −0 Original line number Diff line number Diff line #!/usr/bin/env python3 # Copyright 2022, Barcelona Supercomputing Center (BSC), Spain # # This software was partially supported by the EuroHPC-funded project ADMIRE # (Project ID: 956748, https://www.admire-eurohpc.eu). # # This file is part of genopts. # # genopts is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License # as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. # # genopts is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty # of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License along with genopts. If not, see # <https://www.gnu.org/licenses/>. # # SPDX-License-Identifier: GPL-3.0-or-later import argparse import sys from abc import ABC, abstractmethod from dataclasses import dataclass from pathlib import Path from typing import Iterable, Optional, Any, Dict, List import yaml from cerberus import Validator from cerberus.errors import ValidationError as CerberusValidationError VERSION = "0.1.0" TEMPLATES: Dict[str, str] = { 'schema': """\ #ifndef {header_guard} #define {header_guard} #include <filesystem> #include "file_options/file_options.hpp" #include "parsers.hpp" #include "keywords.hpp" #include "defaults.hpp" namespace fs = std::filesystem; {namespace_begin} using file_options::converter; using file_options::declare_file; using file_options::declare_group; using file_options::declare_list; using file_options::declare_option; using file_options::file_schema; using file_options::opt_type; using file_options::sec_type; const file_schema valid_options = declare_file({{ {sections} }}); {namespace_end} #endif /* {header_guard} */ """, 'keywords': """\ #ifndef {header_guard} #define {header_guard} {namespace_begin} {section_keywords} {option_keywords} {namespace_end} #endif /* {header_guard} */ """} # Validation schema for a file_option in OPT_DESC_FILE OPTION_SCHEMA = { 'type': 'dict', 'schema': { # the name for the option 'name': { 'type': 'string', 'required': True }, # is the option mandatory? 'required': { 'type': 'boolean', 'required': True }, # the option type 'type': { 'type': 'string', 'required': True, # 'allowed': ['bool', 'int', 'double', 'string'] }, # an optional converter function 'converter': { 'type': 'string' } } } # Validation schema for a section in OPT_DESC_FILE SECTION_SCHEMA = { 'type': 'dict', 'schema': { # the name of the section 'name': { 'type': 'string', 'required': True, }, # is the section mandatory? 'required': { 'type': 'boolean', 'required': True }, # the list of options for this section 'options': { 'type': 'list', 'schema': OPTION_SCHEMA } } } # Validation schema for the genopts option description file OPT_DESC_FILE_SCHEMA = { 'sections': { 'type': 'list', 'schema': SECTION_SCHEMA }, } # Validation schema for the genopts configuration file CONFIG_SCHEMA = { 'genopts': { 'type': 'dict', 'required': True, 'schema': { 'schema': { 'type': 'dict', 'schema': { 'copyright_file': {'type': 'string'}, 'header_guard': {'type': 'string', 'required': True}, 'namespace': {'type': 'string'}, 'output_path': { 'type': 'dict', 'schema': { 'header': {'type': 'string'}, } } } }, 'keywords': { 'type': 'dict', 'schema': { 'copyright_file': {'type': 'string'}, 'header_guard': {'type': 'string', 'required': True}, 'namespace': {'type': 'string'}, 'output_path': { 'type': 'dict', 'schema': { 'header': {'type': 'string'}, } } } } } } } class ValidationError(Exception): def __init__(self, message: str, errors: Iterable[CerberusValidationError]): self.message = message self.errors = errors def __str__(self): return (f"{self.message}\n" + f"Cerberus errors: {self.errors}") class GeneratorError(ValidationError): pass class ConfigError(ValidationError): pass class Namespace(ABC): namespace: Iterable[str] def __init__(self, namespace: str): self.namespace = namespace.split('::') @abstractmethod def begin(self) -> str: return NotImplemented @abstractmethod def end(self) -> str: return NotImplemented class Cxx14Namespace(Namespace): def begin(self) -> str: return '\n'.join(f'namespace {n} {{' for n in self.namespace) def end(self) -> str: return '\n'.join(f'}} // namespace {n}' for n in reversed(self.namespace)) class Cxx17Namespace(Namespace): def begin(self) -> str: return f"namespace {'::'.join(self.namespace)} {{" def end(self) -> str: return f"}} // namespace {'::'.join(self.namespace)}" class NamespaceFactory: NAMESPACES_BY_NAME = { "c++17": Cxx17Namespace, "c++14": Cxx14Namespace, } @staticmethod def create_namespace(namespace: str, name: str) -> Namespace: if name not in NamespaceFactory.NAMESPACES_BY_NAME.keys(): raise ValueError(f"Invalid output language: {name}") return NamespaceFactory.NAMESPACES_BY_NAME[name](namespace) @dataclass class OutputPath: header: Optional[Path] implementation: Optional[Path] class OutputConfig: namespace: Namespace output_path: OutputPath header_guard: Optional[str] copyright_text: Optional[str] def __init__(self, namespace: Namespace, output_path: OutputPath, header_guard: Optional[str], copyright_file: Optional[str]): self.namespace = namespace self.header_guard = header_guard self.output_path = output_path self.copyright_text = Path(copyright_file).read_text() if copyright_file else '' def __repr__(self): return f"OutputConfig(" \ f"namespace={self.namespace}, " \ f"header_guard='{self.header_guard}', " \ f"output_path='{self.output_path}', " \ f"copyright_text='{repr(self.copyright_text)}'" \ f")" class Config: output_lang: str schema_config: OutputConfig keywords_config: OutputConfig keys: Iterable[str] = ['schema', 'keywords'] def __init__(self, output_lang: str, schema_config: OutputConfig, keywords_config: OutputConfig): self.output_lang = output_lang self.schema_config = schema_config self.keywords_config = keywords_config def __repr__(self): return f"Config(" \ f"output_lang='{self.output_lang}', " \ f"schema_config='{self.schema_config}', " \ f"keywords_config={self.keywords_config})" class Option: name: str type: str required: bool converter: Optional[str] template: str = """\ declare_option<{type}>( keywords::{name}, opt_type::{required}, converter<{type}>({converter})) """ def __init__(self, name: str, type: str, required: bool, converter: Optional[str] = None): self.name = name self.type = type self.required = required self.converter = converter def __repr__(self) -> str: return f"Option(name='{self.name}', type='{self.type}', required={self.required}, converter={self.converter})" def __str__(self) -> str: return self.template.format(name=self.name, type=self.type, required="mandatory" if self.required else "optional", converter=self.converter) class Section: name: str required: bool options: Iterable[Option] template: str = """\ declare_section( keywords::{name}, sec_type::{required}, declare_group( {options} ) ) """ def __init__(self, name: str, required: bool, options: Iterable[Option]): self.name = name self.required = required self.options = options def __repr__(self) -> str: return f"Section(name='{self.name}', options={self.options})" def __str__(self) -> str: return self.template.format( name=self.name, required="mandatory" if self.required else "optional", options=",\n".join(str(opt) for opt in self.options) ) class Keyword: name: str def __init__(self, name: str): self.name = name def __str__(self) -> str: return f"constexpr static auto {self.name} {{\"{self.name}\"}};" class Generator: data: Any config: Config sections: List[Section] keywords_by_section: Dict[str, List[Keyword]] def __init__(self, config: Config, desc_file: Path): self.sections = [] self.keywords_by_section = {} with open(desc_file, "r") as f: self.data = yaml.safe_load(f) self.config = config v = Validator() if not v.validate(self.data, OPT_DESC_FILE_SCHEMA): raise GeneratorError("Description file is invalid", v.errors) from None for s in self.data['sections']: section_name = s['name'] options = [] for opt in s['options']: options.append( Option(opt['name'], opt['type'], opt['required'], opt.get('converter', None))) if section_name not in self.keywords_by_section: self.keywords_by_section[section_name] = [] self.keywords_by_section[section_name].append(Keyword(opt['name'])) self.sections.append( Section(s['name'], s['required'], options)) def get_schema(self) -> str: cfg = self.config.schema_config copyright_notice = cfg.copyright_text if copyright_notice: copyright_notice += "\n" return (copyright_notice + TEMPLATES['schema'].format( header_guard=cfg.header_guard, namespace_begin=cfg.namespace.begin() + "\n", namespace_end="\n" + cfg.namespace.end(), sections='\n'.join(str(s) for s in self.sections), )) def get_keywords(self) -> str: section_keywords = [Keyword(s.name) for s in self.sections] cfg = self.config.keywords_config copyright_notice = cfg.copyright_text if copyright_notice: copyright_notice += "\n" return (copyright_notice + TEMPLATES['keywords'].format( header_guard=cfg.header_guard, namespace_begin=cfg.namespace.begin() + "\n", namespace_end="\n" + cfg.namespace.end(), section_keywords="// section names\n" + "\n".join(str(k) for k in section_keywords) + "\n", option_keywords='\n'.join(f"// option names for '{sn}' section\n" + '\n'.join(str(k) for k in self.keywords_by_section[sn.name]) for sn in section_keywords) )) def load_config(config_file: Path, output_lang: str) -> Config: with open(config_file, "r") as f: data = yaml.safe_load(f) v = Validator() if not v.validate(data, CONFIG_SCHEMA): raise ConfigError("Invalid configuration file", v.errors) dg = data['genopts'] cfgs = [ OutputConfig( NamespaceFactory.create_namespace(dg[k].get('namespace'), output_lang), OutputPath( Path(dg[k]['output_path']['header']), Path(dg[k]['output_path']['implementation']) if 'implementation' in dg[k]['output_path'] else None), dg[k].get('header_guard', None), dg[k].get('copyright_file', None), ) for k in Config.keys ] return Config(output_lang, *cfgs) class _PrintVersion(argparse.Action): def __init__(self, option_strings, dest, const=None, default=None, required=False, help=None, metavar=None): super().__init__(option_strings=option_strings, dest=dest, nargs=0, const=const, default=default, required=required, help=help) def __call__(self, parser, namespace, values, option_string=None): setattr(namespace, self.dest, self.const) print(f"genopts {VERSION}") sys.exit(0) def parse_args(args) -> argparse.Namespace: parser = argparse.ArgumentParser(prog="genopts", description="Parse OPT_DESC_FILE and generate a C++ schema that describes " "the options it defines") parser.add_argument( "opt_desc_file", type=Path, metavar="OPT_DESC_FILE", help="A file describing the desired options." ) parser.add_argument( "--config-file", "-c", type=Path, metavar="CONFIG_FILE", default=Path("config.yml"), help="A configuration file that should be used to control the generated output." ) parser.add_argument( "--std", choices=["c++17", "c++14"], dest="output_lang", metavar="LANG", default="c++17", help="Determine the output language. Possible values for LANG are:\n" "\n" "c++17\n" " 2017 ISO C++ standard." "c++14\n" " 2014 ISO C++ standard." ) parser.add_argument( "--write-schema", action='store_true', help="Generate a 'schema' header and write it to the location defined in CONFIG_FILE." ) parser.add_argument( "--no-write-schema", dest='write_schema', action='store_false', help="Do not generate a 'schema' header." ) parser.set_defaults(write_schema=True) parser.add_argument( "--write-keywords", action='store_true', help="Generate a 'keywords' header and write it to the location defined in CONFIG_FILE." ) parser.add_argument( "--no-write-keywords", dest='write_keywords', action='store_false', help="Do not generate a 'keywords' header/implementation." ) parser.set_defaults(write_keywords=True) parser.add_argument( "--force-console", "-C", action='store_true', help="Write generated code to stdout instead of files" ) parser.add_argument( "--version", "-V", action=_PrintVersion, help="Display genopts version information." ) return parser.parse_args(args) def main(args): try: args = parse_args(args) cfg = load_config(args.config_file, args.output_lang) g = Generator(cfg, args.opt_desc_file) if args.write_schema: hpp_schema = g.get_schema() if args.force_console: print(hpp_schema) else: cfg.schema_config.output_path.header.write_text(hpp_schema) print(f"Written {cfg.schema_config.output_path.header}") if args.write_keywords: hpp_keywords = g.get_keywords() if args.force_console: print(hpp_keywords) else: cfg.keywords_config.output_path.header.write_text(hpp_keywords) print(f"Written {cfg.keywords_config.output_path.header}") except ConfigError as e: print(e, file=sys.stderr) sys.exit(1) except OSError as e: print(e, file=sys.stderr) sys.exit(1) except ValidationError as e: print(e, file=sys.stderr) sys.exit(1) if __name__ == '__main__': main(sys.argv[1:]) requirements.txt 0 → 100644 +3 −0 Original line number Diff line number Diff line pytest~=7.1.2 PyYAML~=6.0 Cerberus~=1.3.4 Loading
.gitignore 0 → 100644 +3 −0 Original line number Diff line number Diff line .idea __pycache__ *.egg-info
genopts/__init__.py 0 → 100644 +2 −0 Original line number Diff line number Diff line # This file is here just so that 'genopts' becomes a package and we can import it in tests # The script itself is standalone
genopts/genopts.py 0 → 100755 +589 −0 Original line number Diff line number Diff line #!/usr/bin/env python3 # Copyright 2022, Barcelona Supercomputing Center (BSC), Spain # # This software was partially supported by the EuroHPC-funded project ADMIRE # (Project ID: 956748, https://www.admire-eurohpc.eu). # # This file is part of genopts. # # genopts is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License # as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. # # genopts is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty # of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License along with genopts. If not, see # <https://www.gnu.org/licenses/>. # # SPDX-License-Identifier: GPL-3.0-or-later import argparse import sys from abc import ABC, abstractmethod from dataclasses import dataclass from pathlib import Path from typing import Iterable, Optional, Any, Dict, List import yaml from cerberus import Validator from cerberus.errors import ValidationError as CerberusValidationError VERSION = "0.1.0" TEMPLATES: Dict[str, str] = { 'schema': """\ #ifndef {header_guard} #define {header_guard} #include <filesystem> #include "file_options/file_options.hpp" #include "parsers.hpp" #include "keywords.hpp" #include "defaults.hpp" namespace fs = std::filesystem; {namespace_begin} using file_options::converter; using file_options::declare_file; using file_options::declare_group; using file_options::declare_list; using file_options::declare_option; using file_options::file_schema; using file_options::opt_type; using file_options::sec_type; const file_schema valid_options = declare_file({{ {sections} }}); {namespace_end} #endif /* {header_guard} */ """, 'keywords': """\ #ifndef {header_guard} #define {header_guard} {namespace_begin} {section_keywords} {option_keywords} {namespace_end} #endif /* {header_guard} */ """} # Validation schema for a file_option in OPT_DESC_FILE OPTION_SCHEMA = { 'type': 'dict', 'schema': { # the name for the option 'name': { 'type': 'string', 'required': True }, # is the option mandatory? 'required': { 'type': 'boolean', 'required': True }, # the option type 'type': { 'type': 'string', 'required': True, # 'allowed': ['bool', 'int', 'double', 'string'] }, # an optional converter function 'converter': { 'type': 'string' } } } # Validation schema for a section in OPT_DESC_FILE SECTION_SCHEMA = { 'type': 'dict', 'schema': { # the name of the section 'name': { 'type': 'string', 'required': True, }, # is the section mandatory? 'required': { 'type': 'boolean', 'required': True }, # the list of options for this section 'options': { 'type': 'list', 'schema': OPTION_SCHEMA } } } # Validation schema for the genopts option description file OPT_DESC_FILE_SCHEMA = { 'sections': { 'type': 'list', 'schema': SECTION_SCHEMA }, } # Validation schema for the genopts configuration file CONFIG_SCHEMA = { 'genopts': { 'type': 'dict', 'required': True, 'schema': { 'schema': { 'type': 'dict', 'schema': { 'copyright_file': {'type': 'string'}, 'header_guard': {'type': 'string', 'required': True}, 'namespace': {'type': 'string'}, 'output_path': { 'type': 'dict', 'schema': { 'header': {'type': 'string'}, } } } }, 'keywords': { 'type': 'dict', 'schema': { 'copyright_file': {'type': 'string'}, 'header_guard': {'type': 'string', 'required': True}, 'namespace': {'type': 'string'}, 'output_path': { 'type': 'dict', 'schema': { 'header': {'type': 'string'}, } } } } } } } class ValidationError(Exception): def __init__(self, message: str, errors: Iterable[CerberusValidationError]): self.message = message self.errors = errors def __str__(self): return (f"{self.message}\n" + f"Cerberus errors: {self.errors}") class GeneratorError(ValidationError): pass class ConfigError(ValidationError): pass class Namespace(ABC): namespace: Iterable[str] def __init__(self, namespace: str): self.namespace = namespace.split('::') @abstractmethod def begin(self) -> str: return NotImplemented @abstractmethod def end(self) -> str: return NotImplemented class Cxx14Namespace(Namespace): def begin(self) -> str: return '\n'.join(f'namespace {n} {{' for n in self.namespace) def end(self) -> str: return '\n'.join(f'}} // namespace {n}' for n in reversed(self.namespace)) class Cxx17Namespace(Namespace): def begin(self) -> str: return f"namespace {'::'.join(self.namespace)} {{" def end(self) -> str: return f"}} // namespace {'::'.join(self.namespace)}" class NamespaceFactory: NAMESPACES_BY_NAME = { "c++17": Cxx17Namespace, "c++14": Cxx14Namespace, } @staticmethod def create_namespace(namespace: str, name: str) -> Namespace: if name not in NamespaceFactory.NAMESPACES_BY_NAME.keys(): raise ValueError(f"Invalid output language: {name}") return NamespaceFactory.NAMESPACES_BY_NAME[name](namespace) @dataclass class OutputPath: header: Optional[Path] implementation: Optional[Path] class OutputConfig: namespace: Namespace output_path: OutputPath header_guard: Optional[str] copyright_text: Optional[str] def __init__(self, namespace: Namespace, output_path: OutputPath, header_guard: Optional[str], copyright_file: Optional[str]): self.namespace = namespace self.header_guard = header_guard self.output_path = output_path self.copyright_text = Path(copyright_file).read_text() if copyright_file else '' def __repr__(self): return f"OutputConfig(" \ f"namespace={self.namespace}, " \ f"header_guard='{self.header_guard}', " \ f"output_path='{self.output_path}', " \ f"copyright_text='{repr(self.copyright_text)}'" \ f")" class Config: output_lang: str schema_config: OutputConfig keywords_config: OutputConfig keys: Iterable[str] = ['schema', 'keywords'] def __init__(self, output_lang: str, schema_config: OutputConfig, keywords_config: OutputConfig): self.output_lang = output_lang self.schema_config = schema_config self.keywords_config = keywords_config def __repr__(self): return f"Config(" \ f"output_lang='{self.output_lang}', " \ f"schema_config='{self.schema_config}', " \ f"keywords_config={self.keywords_config})" class Option: name: str type: str required: bool converter: Optional[str] template: str = """\ declare_option<{type}>( keywords::{name}, opt_type::{required}, converter<{type}>({converter})) """ def __init__(self, name: str, type: str, required: bool, converter: Optional[str] = None): self.name = name self.type = type self.required = required self.converter = converter def __repr__(self) -> str: return f"Option(name='{self.name}', type='{self.type}', required={self.required}, converter={self.converter})" def __str__(self) -> str: return self.template.format(name=self.name, type=self.type, required="mandatory" if self.required else "optional", converter=self.converter) class Section: name: str required: bool options: Iterable[Option] template: str = """\ declare_section( keywords::{name}, sec_type::{required}, declare_group( {options} ) ) """ def __init__(self, name: str, required: bool, options: Iterable[Option]): self.name = name self.required = required self.options = options def __repr__(self) -> str: return f"Section(name='{self.name}', options={self.options})" def __str__(self) -> str: return self.template.format( name=self.name, required="mandatory" if self.required else "optional", options=",\n".join(str(opt) for opt in self.options) ) class Keyword: name: str def __init__(self, name: str): self.name = name def __str__(self) -> str: return f"constexpr static auto {self.name} {{\"{self.name}\"}};" class Generator: data: Any config: Config sections: List[Section] keywords_by_section: Dict[str, List[Keyword]] def __init__(self, config: Config, desc_file: Path): self.sections = [] self.keywords_by_section = {} with open(desc_file, "r") as f: self.data = yaml.safe_load(f) self.config = config v = Validator() if not v.validate(self.data, OPT_DESC_FILE_SCHEMA): raise GeneratorError("Description file is invalid", v.errors) from None for s in self.data['sections']: section_name = s['name'] options = [] for opt in s['options']: options.append( Option(opt['name'], opt['type'], opt['required'], opt.get('converter', None))) if section_name not in self.keywords_by_section: self.keywords_by_section[section_name] = [] self.keywords_by_section[section_name].append(Keyword(opt['name'])) self.sections.append( Section(s['name'], s['required'], options)) def get_schema(self) -> str: cfg = self.config.schema_config copyright_notice = cfg.copyright_text if copyright_notice: copyright_notice += "\n" return (copyright_notice + TEMPLATES['schema'].format( header_guard=cfg.header_guard, namespace_begin=cfg.namespace.begin() + "\n", namespace_end="\n" + cfg.namespace.end(), sections='\n'.join(str(s) for s in self.sections), )) def get_keywords(self) -> str: section_keywords = [Keyword(s.name) for s in self.sections] cfg = self.config.keywords_config copyright_notice = cfg.copyright_text if copyright_notice: copyright_notice += "\n" return (copyright_notice + TEMPLATES['keywords'].format( header_guard=cfg.header_guard, namespace_begin=cfg.namespace.begin() + "\n", namespace_end="\n" + cfg.namespace.end(), section_keywords="// section names\n" + "\n".join(str(k) for k in section_keywords) + "\n", option_keywords='\n'.join(f"// option names for '{sn}' section\n" + '\n'.join(str(k) for k in self.keywords_by_section[sn.name]) for sn in section_keywords) )) def load_config(config_file: Path, output_lang: str) -> Config: with open(config_file, "r") as f: data = yaml.safe_load(f) v = Validator() if not v.validate(data, CONFIG_SCHEMA): raise ConfigError("Invalid configuration file", v.errors) dg = data['genopts'] cfgs = [ OutputConfig( NamespaceFactory.create_namespace(dg[k].get('namespace'), output_lang), OutputPath( Path(dg[k]['output_path']['header']), Path(dg[k]['output_path']['implementation']) if 'implementation' in dg[k]['output_path'] else None), dg[k].get('header_guard', None), dg[k].get('copyright_file', None), ) for k in Config.keys ] return Config(output_lang, *cfgs) class _PrintVersion(argparse.Action): def __init__(self, option_strings, dest, const=None, default=None, required=False, help=None, metavar=None): super().__init__(option_strings=option_strings, dest=dest, nargs=0, const=const, default=default, required=required, help=help) def __call__(self, parser, namespace, values, option_string=None): setattr(namespace, self.dest, self.const) print(f"genopts {VERSION}") sys.exit(0) def parse_args(args) -> argparse.Namespace: parser = argparse.ArgumentParser(prog="genopts", description="Parse OPT_DESC_FILE and generate a C++ schema that describes " "the options it defines") parser.add_argument( "opt_desc_file", type=Path, metavar="OPT_DESC_FILE", help="A file describing the desired options." ) parser.add_argument( "--config-file", "-c", type=Path, metavar="CONFIG_FILE", default=Path("config.yml"), help="A configuration file that should be used to control the generated output." ) parser.add_argument( "--std", choices=["c++17", "c++14"], dest="output_lang", metavar="LANG", default="c++17", help="Determine the output language. Possible values for LANG are:\n" "\n" "c++17\n" " 2017 ISO C++ standard." "c++14\n" " 2014 ISO C++ standard." ) parser.add_argument( "--write-schema", action='store_true', help="Generate a 'schema' header and write it to the location defined in CONFIG_FILE." ) parser.add_argument( "--no-write-schema", dest='write_schema', action='store_false', help="Do not generate a 'schema' header." ) parser.set_defaults(write_schema=True) parser.add_argument( "--write-keywords", action='store_true', help="Generate a 'keywords' header and write it to the location defined in CONFIG_FILE." ) parser.add_argument( "--no-write-keywords", dest='write_keywords', action='store_false', help="Do not generate a 'keywords' header/implementation." ) parser.set_defaults(write_keywords=True) parser.add_argument( "--force-console", "-C", action='store_true', help="Write generated code to stdout instead of files" ) parser.add_argument( "--version", "-V", action=_PrintVersion, help="Display genopts version information." ) return parser.parse_args(args) def main(args): try: args = parse_args(args) cfg = load_config(args.config_file, args.output_lang) g = Generator(cfg, args.opt_desc_file) if args.write_schema: hpp_schema = g.get_schema() if args.force_console: print(hpp_schema) else: cfg.schema_config.output_path.header.write_text(hpp_schema) print(f"Written {cfg.schema_config.output_path.header}") if args.write_keywords: hpp_keywords = g.get_keywords() if args.force_console: print(hpp_keywords) else: cfg.keywords_config.output_path.header.write_text(hpp_keywords) print(f"Written {cfg.keywords_config.output_path.header}") except ConfigError as e: print(e, file=sys.stderr) sys.exit(1) except OSError as e: print(e, file=sys.stderr) sys.exit(1) except ValidationError as e: print(e, file=sys.stderr) sys.exit(1) if __name__ == '__main__': main(sys.argv[1:])
requirements.txt 0 → 100644 +3 −0 Original line number Diff line number Diff line pytest~=7.1.2 PyYAML~=6.0 Cerberus~=1.3.4