Loading genopts/genopts.py +201 −114 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later import argparse import string import sys from abc import ABC, abstractmethod from dataclasses import dataclass Loading @@ -30,50 +31,6 @@ 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', Loading Loading @@ -190,36 +147,64 @@ class ConfigError(ValidationError): class Namespace(ABC): namespace: Iterable[str] separator: str = '::' namespace: List[str] def __init__(self, namespace: str): self.namespace = namespace.split('::') self.namespace = namespace.split(Namespace.separator) def __contains__(self, item) -> bool: return item.namespace[0:len(self.namespace)] == self.namespace def __sub__(self, other): nn = [n for n in self.namespace if n not in other.namespace] return type(self)(Namespace.separator.join(nn)) @abstractmethod def begin(self) -> str: def __format__(self, format_spec) -> str: return NotImplemented @abstractmethod def end(self) -> str: def __repr__(self) -> str: return NotImplemented class Cxx14Namespace(Namespace): def begin(self) -> str: def __format__(self, format_spec): # {:B} formats the opening statement of a namespace if format_spec == "B": return '\n'.join(f'namespace {n} {{' for n in self.namespace) def end(self) -> str: # {:E} formats the opening statement of a namespace if format_spec == "E": return '\n'.join(f'}} // namespace {n}' for n in reversed(self.namespace)) # no specifier just formats the namespace as a name return Namespace.separator.join(n for n in self.namespace) def __repr__(self) -> str: def add_quotes(s): return f"'{s}'" return f"Cxx14Namespace(namespace={', '.join([add_quotes(n) for n in self.namespace])})" class Cxx17Namespace(Namespace): def begin(self) -> str: return f"namespace {'::'.join(self.namespace)} {{" def __format__(self, format_spec): # {:B} formats the opening statement of a namespace if format_spec == "B": return f"namespace {Namespace.separator.join(self.namespace)} {{" # {:E} formats the opening statement of a namespace if format_spec == "E": return f"}} // namespace {Namespace.separator.join(self.namespace)}" # no specifier just formats the namespace as a name return Namespace.separator.join(n for n in self.namespace) def end(self) -> str: return f"}} // namespace {'::'.join(self.namespace)}" def __repr__(self) -> str: def add_quotes(s): return f"'{s}'" return f"Cxx17Namespace(namespace={', '.join([add_quotes(n) for n in self.namespace])})" class NamespaceFactory: Loading Loading @@ -268,8 +253,8 @@ class OutputConfig: class Config: output_lang: str schema_config: OutputConfig keywords_config: OutputConfig schema: OutputConfig keywords: OutputConfig keys: Iterable[str] = ['schema', 'keywords'] def __init__(self, Loading @@ -277,23 +262,74 @@ class Config: schema_config: OutputConfig, keywords_config: OutputConfig): self.output_lang = output_lang self.schema_config = schema_config self.keywords_config = keywords_config self.schema = schema_config self.keywords = 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})" f"schema='{self.schema}', " \ f"keywords={self.keywords})" class Option(ABC): class ContextMeta(type): _instances = {} def __call__(cls, *args, **kwargs): if cls not in cls._instances: instance = super(ContextMeta, cls).__call__(*args, **kwargs) cls._instances[cls] = instance return cls._instances[cls] class FormattingContext(metaclass=ContextMeta): _current_namespace: Namespace = None @property def current_namespace(self) -> Namespace: return self._current_namespace @current_namespace.setter def current_namespace(self, value): self._current_namespace = value class Keyword: name: str namespace: Namespace def __init__(self, name: str, namespace: Namespace): self.name = name self.namespace = namespace def __format__(self, format_spec): # {D} formats a keyword definition if format_spec == 'D': return f"constexpr static auto {self.name} {{\"{self.name}\"}};" # {I} formats a keyword instantiation if format_spec == 'I': ctx = FormattingContext() ns = self.namespace if self.namespace in ctx.current_namespace: ns -= ctx.current_namespace return f"{ns}{Namespace.separator}{self.name}" return self.name def __str__(self) -> str: return self.name class Option(ABC): keyword: Keyword type: str required: bool def __init__(self, name: str, type: str, required: bool): self.name = name def __init__(self, keyword: Keyword, type: str, required: bool): self.keyword = keyword self.type = type self.required = required Loading @@ -310,20 +346,22 @@ class ConvertibleOption(Option): converter: Optional[str] template: str = """\ declare_option<{type}>( keywords::{name}, {keyword}, opt_type::{required}, converter<{type}>({converter})) """ def __init__(self, name: str, type: str, required: bool, converter: Optional[str] = None): super().__init__(name, type, required) def __init__(self, keyword: Keyword, type: str, required: bool, converter: Optional[str] = None): super().__init__(keyword, type, required) self.converter = converter def __repr__(self) -> str: return f"Option(name='{self.name}', type='{self.type}', required={self.required}, converter={self.converter})" return f"Option(keyword='{self.keyword}', type='{self.type}', required={self.required}, " \ f"converter={self.converter})" def __str__(self) -> str: return self.template.format(name=self.name, type=self.type, return self.template.format(keyword=f"{self.keyword:I}", type=self.type, required="mandatory" if self.required else "optional", converter=self.converter) Loading @@ -331,37 +369,37 @@ declare_option<{type}>( class NonConvertibleOption(Option): template: str = """\ declare_option<{type}>( keywords::{name}, {keyword}, opt_type::{required}) """ def __init__(self, name: str, type: str, required: bool): super().__init__(name, type, required) def __init__(self, keyword: Keyword, type: str, required: bool): super().__init__(keyword, type, required) def __repr__(self) -> str: return f"Option(name='{self.name}', type='{self.type}', required={self.required})" return f"Option(keyword='{self.keyword}', type='{self.type}', required={self.required})" def __str__(self) -> str: return self.template.format(name=self.name, type=self.type, return self.template.format(keyword=self.keyword, type=self.type, required="mandatory" if self.required else "optional") class OptionFactory: @staticmethod def create_option(name: str, type: str, required: bool, converter: Optional[str] = None) -> Option: def create_option(keyword: Keyword, type: str, required: bool, converter: Optional[str] = None) -> Option: if converter: return ConvertibleOption(name, type, required, converter) return ConvertibleOption(keyword, type, required, converter) else: return NonConvertibleOption(name, type, required) return NonConvertibleOption(keyword, type, required) class Section: name: str keyword: Keyword required: bool options: Iterable[Option] template: str = """\ declare_section( keywords::{name}, {keyword}, sec_type::{required}, declare_group( {options} Loading @@ -369,32 +407,22 @@ declare_section( ) """ def __init__(self, name: str, required: bool, options: Iterable[Option]): self.name = name def __init__(self, keyword: Keyword, required: bool, options: Iterable[Option]): self.keyword = keyword self.required = required self.options = options def __repr__(self) -> str: return f"Section(name='{self.name}', options={self.options})" return f"Section(keyword='{self.keyword}', options={self.options})" def __str__(self) -> str: return self.template.format( name=self.name, keyword=f"{self.keyword:I}", 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 Loading @@ -404,6 +432,7 @@ class Generator: def __init__(self, config: Config, desc_file: Path): self.sections = [] self.keywords_by_section = {} self.ctx = FormattingContext() with open(desc_file, "r") as f: self.data = yaml.safe_load(f) Loading @@ -419,53 +448,100 @@ class Generator: for opt in s['options']: options.append( OptionFactory.create_option(opt['name'], OptionFactory.create_option(Keyword(opt['name'], config.keywords.namespace), 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.keywords_by_section[section_name].append(Keyword(opt['name'], config.keywords.namespace)) self.sections.append( Section(s['name'], Section(Keyword(s['name'], config.keywords.namespace), s['required'], options)) def get_schema(self) -> str: cfg = self.config.schema_config template = """\ #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} */ """ cfg = self.config.schema copyright_notice = cfg.copyright_text if copyright_notice: copyright_notice += "\n" self.ctx.current_namespace = self.config.schema.namespace return (copyright_notice + TEMPLATES['schema'].format( template.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), namespace_begin=f"{cfg.namespace:B}\n", namespace_end=f"\n{cfg.namespace:E}", 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] template = """\ #ifndef {header_guard} #define {header_guard} {namespace_begin} {section_keywords} {option_keywords} {namespace_end} #endif /* {header_guard} */ """ cfg = self.config.keywords_config cfg = self.config.keywords section_keywords = [s.keyword for s in self.sections] copyright_notice = cfg.copyright_text if copyright_notice: copyright_notice += "\n" self.ctx.current_namespace = self.config.keywords.namespace return (copyright_notice + TEMPLATES['keywords'].format( template.format( header_guard=cfg.header_guard, namespace_begin=cfg.namespace.begin() + "\n", namespace_end="\n" + cfg.namespace.end(), namespace_begin=f"{cfg.namespace:B}\n", namespace_end=f"\n{cfg.namespace:E}", section_keywords="// section names\n" + "\n".join(str(k) for k in section_keywords) + "\n", "\n".join(f"{k:D}" 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]) '\n'.join(f"{k:D}" for k in self.keywords_by_section[sn.name]) for sn in section_keywords) )) Loading Loading @@ -590,8 +666,19 @@ def parse_args(args) -> argparse.Namespace: return parser.parse_args(args) def main(args = None): class PluralFormatter(string.Formatter): def format_field(self, value, format_spec): if format_spec.startswith('plural,'): words = format_spec.split(',') if value == 1: return words[1] else: return words[2] else: return super().format_field(value, format_spec) def main(args=None): if not args: args = sys.argv[1:] Loading @@ -607,8 +694,8 @@ def main(args = None): 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}") cfg.schema.output_path.header.write_text(hpp_schema) print(f"Written {cfg.schema.output_path.header}") if args.write_keywords: hpp_keywords = g.get_keywords() Loading @@ -616,8 +703,8 @@ def main(args = None): 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}") cfg.keywords.output_path.header.write_text(hpp_keywords) print(f"Written {cfg.keywords.output_path.header}") except ConfigError as e: print(e, file=sys.stderr) Loading Loading
genopts/genopts.py +201 −114 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later import argparse import string import sys from abc import ABC, abstractmethod from dataclasses import dataclass Loading @@ -30,50 +31,6 @@ 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', Loading Loading @@ -190,36 +147,64 @@ class ConfigError(ValidationError): class Namespace(ABC): namespace: Iterable[str] separator: str = '::' namespace: List[str] def __init__(self, namespace: str): self.namespace = namespace.split('::') self.namespace = namespace.split(Namespace.separator) def __contains__(self, item) -> bool: return item.namespace[0:len(self.namespace)] == self.namespace def __sub__(self, other): nn = [n for n in self.namespace if n not in other.namespace] return type(self)(Namespace.separator.join(nn)) @abstractmethod def begin(self) -> str: def __format__(self, format_spec) -> str: return NotImplemented @abstractmethod def end(self) -> str: def __repr__(self) -> str: return NotImplemented class Cxx14Namespace(Namespace): def begin(self) -> str: def __format__(self, format_spec): # {:B} formats the opening statement of a namespace if format_spec == "B": return '\n'.join(f'namespace {n} {{' for n in self.namespace) def end(self) -> str: # {:E} formats the opening statement of a namespace if format_spec == "E": return '\n'.join(f'}} // namespace {n}' for n in reversed(self.namespace)) # no specifier just formats the namespace as a name return Namespace.separator.join(n for n in self.namespace) def __repr__(self) -> str: def add_quotes(s): return f"'{s}'" return f"Cxx14Namespace(namespace={', '.join([add_quotes(n) for n in self.namespace])})" class Cxx17Namespace(Namespace): def begin(self) -> str: return f"namespace {'::'.join(self.namespace)} {{" def __format__(self, format_spec): # {:B} formats the opening statement of a namespace if format_spec == "B": return f"namespace {Namespace.separator.join(self.namespace)} {{" # {:E} formats the opening statement of a namespace if format_spec == "E": return f"}} // namespace {Namespace.separator.join(self.namespace)}" # no specifier just formats the namespace as a name return Namespace.separator.join(n for n in self.namespace) def end(self) -> str: return f"}} // namespace {'::'.join(self.namespace)}" def __repr__(self) -> str: def add_quotes(s): return f"'{s}'" return f"Cxx17Namespace(namespace={', '.join([add_quotes(n) for n in self.namespace])})" class NamespaceFactory: Loading Loading @@ -268,8 +253,8 @@ class OutputConfig: class Config: output_lang: str schema_config: OutputConfig keywords_config: OutputConfig schema: OutputConfig keywords: OutputConfig keys: Iterable[str] = ['schema', 'keywords'] def __init__(self, Loading @@ -277,23 +262,74 @@ class Config: schema_config: OutputConfig, keywords_config: OutputConfig): self.output_lang = output_lang self.schema_config = schema_config self.keywords_config = keywords_config self.schema = schema_config self.keywords = 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})" f"schema='{self.schema}', " \ f"keywords={self.keywords})" class Option(ABC): class ContextMeta(type): _instances = {} def __call__(cls, *args, **kwargs): if cls not in cls._instances: instance = super(ContextMeta, cls).__call__(*args, **kwargs) cls._instances[cls] = instance return cls._instances[cls] class FormattingContext(metaclass=ContextMeta): _current_namespace: Namespace = None @property def current_namespace(self) -> Namespace: return self._current_namespace @current_namespace.setter def current_namespace(self, value): self._current_namespace = value class Keyword: name: str namespace: Namespace def __init__(self, name: str, namespace: Namespace): self.name = name self.namespace = namespace def __format__(self, format_spec): # {D} formats a keyword definition if format_spec == 'D': return f"constexpr static auto {self.name} {{\"{self.name}\"}};" # {I} formats a keyword instantiation if format_spec == 'I': ctx = FormattingContext() ns = self.namespace if self.namespace in ctx.current_namespace: ns -= ctx.current_namespace return f"{ns}{Namespace.separator}{self.name}" return self.name def __str__(self) -> str: return self.name class Option(ABC): keyword: Keyword type: str required: bool def __init__(self, name: str, type: str, required: bool): self.name = name def __init__(self, keyword: Keyword, type: str, required: bool): self.keyword = keyword self.type = type self.required = required Loading @@ -310,20 +346,22 @@ class ConvertibleOption(Option): converter: Optional[str] template: str = """\ declare_option<{type}>( keywords::{name}, {keyword}, opt_type::{required}, converter<{type}>({converter})) """ def __init__(self, name: str, type: str, required: bool, converter: Optional[str] = None): super().__init__(name, type, required) def __init__(self, keyword: Keyword, type: str, required: bool, converter: Optional[str] = None): super().__init__(keyword, type, required) self.converter = converter def __repr__(self) -> str: return f"Option(name='{self.name}', type='{self.type}', required={self.required}, converter={self.converter})" return f"Option(keyword='{self.keyword}', type='{self.type}', required={self.required}, " \ f"converter={self.converter})" def __str__(self) -> str: return self.template.format(name=self.name, type=self.type, return self.template.format(keyword=f"{self.keyword:I}", type=self.type, required="mandatory" if self.required else "optional", converter=self.converter) Loading @@ -331,37 +369,37 @@ declare_option<{type}>( class NonConvertibleOption(Option): template: str = """\ declare_option<{type}>( keywords::{name}, {keyword}, opt_type::{required}) """ def __init__(self, name: str, type: str, required: bool): super().__init__(name, type, required) def __init__(self, keyword: Keyword, type: str, required: bool): super().__init__(keyword, type, required) def __repr__(self) -> str: return f"Option(name='{self.name}', type='{self.type}', required={self.required})" return f"Option(keyword='{self.keyword}', type='{self.type}', required={self.required})" def __str__(self) -> str: return self.template.format(name=self.name, type=self.type, return self.template.format(keyword=self.keyword, type=self.type, required="mandatory" if self.required else "optional") class OptionFactory: @staticmethod def create_option(name: str, type: str, required: bool, converter: Optional[str] = None) -> Option: def create_option(keyword: Keyword, type: str, required: bool, converter: Optional[str] = None) -> Option: if converter: return ConvertibleOption(name, type, required, converter) return ConvertibleOption(keyword, type, required, converter) else: return NonConvertibleOption(name, type, required) return NonConvertibleOption(keyword, type, required) class Section: name: str keyword: Keyword required: bool options: Iterable[Option] template: str = """\ declare_section( keywords::{name}, {keyword}, sec_type::{required}, declare_group( {options} Loading @@ -369,32 +407,22 @@ declare_section( ) """ def __init__(self, name: str, required: bool, options: Iterable[Option]): self.name = name def __init__(self, keyword: Keyword, required: bool, options: Iterable[Option]): self.keyword = keyword self.required = required self.options = options def __repr__(self) -> str: return f"Section(name='{self.name}', options={self.options})" return f"Section(keyword='{self.keyword}', options={self.options})" def __str__(self) -> str: return self.template.format( name=self.name, keyword=f"{self.keyword:I}", 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 Loading @@ -404,6 +432,7 @@ class Generator: def __init__(self, config: Config, desc_file: Path): self.sections = [] self.keywords_by_section = {} self.ctx = FormattingContext() with open(desc_file, "r") as f: self.data = yaml.safe_load(f) Loading @@ -419,53 +448,100 @@ class Generator: for opt in s['options']: options.append( OptionFactory.create_option(opt['name'], OptionFactory.create_option(Keyword(opt['name'], config.keywords.namespace), 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.keywords_by_section[section_name].append(Keyword(opt['name'], config.keywords.namespace)) self.sections.append( Section(s['name'], Section(Keyword(s['name'], config.keywords.namespace), s['required'], options)) def get_schema(self) -> str: cfg = self.config.schema_config template = """\ #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} */ """ cfg = self.config.schema copyright_notice = cfg.copyright_text if copyright_notice: copyright_notice += "\n" self.ctx.current_namespace = self.config.schema.namespace return (copyright_notice + TEMPLATES['schema'].format( template.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), namespace_begin=f"{cfg.namespace:B}\n", namespace_end=f"\n{cfg.namespace:E}", 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] template = """\ #ifndef {header_guard} #define {header_guard} {namespace_begin} {section_keywords} {option_keywords} {namespace_end} #endif /* {header_guard} */ """ cfg = self.config.keywords_config cfg = self.config.keywords section_keywords = [s.keyword for s in self.sections] copyright_notice = cfg.copyright_text if copyright_notice: copyright_notice += "\n" self.ctx.current_namespace = self.config.keywords.namespace return (copyright_notice + TEMPLATES['keywords'].format( template.format( header_guard=cfg.header_guard, namespace_begin=cfg.namespace.begin() + "\n", namespace_end="\n" + cfg.namespace.end(), namespace_begin=f"{cfg.namespace:B}\n", namespace_end=f"\n{cfg.namespace:E}", section_keywords="// section names\n" + "\n".join(str(k) for k in section_keywords) + "\n", "\n".join(f"{k:D}" 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]) '\n'.join(f"{k:D}" for k in self.keywords_by_section[sn.name]) for sn in section_keywords) )) Loading Loading @@ -590,8 +666,19 @@ def parse_args(args) -> argparse.Namespace: return parser.parse_args(args) def main(args = None): class PluralFormatter(string.Formatter): def format_field(self, value, format_spec): if format_spec.startswith('plural,'): words = format_spec.split(',') if value == 1: return words[1] else: return words[2] else: return super().format_field(value, format_spec) def main(args=None): if not args: args = sys.argv[1:] Loading @@ -607,8 +694,8 @@ def main(args = None): 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}") cfg.schema.output_path.header.write_text(hpp_schema) print(f"Written {cfg.schema.output_path.header}") if args.write_keywords: hpp_keywords = g.get_keywords() Loading @@ -616,8 +703,8 @@ def main(args = None): 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}") cfg.keywords.output_path.header.write_text(hpp_keywords) print(f"Written {cfg.keywords.output_path.header}") except ConfigError as e: print(e, file=sys.stderr) Loading