diff --git a/CHANGELOG.md b/CHANGELOG.md index 716bd9db..c7e13950 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- Help for the supported outputs (--help-list-outputs, --help-outputs and + --help-output) +- Better YAML validation. ## [0.4.0] - 2020-06-17 ### Added diff --git a/kiplot/__main__.py b/kiplot/__main__.py index 522fc1e2..80a70535 100644 --- a/kiplot/__main__.py +++ b/kiplot/__main__.py @@ -18,7 +18,7 @@ from . import log log.set_domain('kiplot') from .kiplot import (GS, generate_outputs) from .pre_base import (BasePreFlight) -from .config_reader import (CfgYamlReader) +from .config_reader import (CfgYamlReader, print_outputs_help, print_output_help) from .misc import (NO_PCB_FILE, EXIT_BAD_ARGS) from .__version__ import __version__ @@ -30,8 +30,11 @@ def main(): parser.add_argument('-b', '--board-file', help='The PCB .kicad-pcb board file') parser.add_argument('-c', '--plot-config', help='The plotting config file to use') parser.add_argument('-d', '--out-dir', default='.', help='The output directory (cwd if not given)') + parser.add_argument('--help-list-outputs', action='store_true', help='List supported outputs') + parser.add_argument('--help-output', help='Help for this particular output') + parser.add_argument('--help-outputs', action='store_true', help='List supported outputs and details') parser.add_argument('-i', '--invert-sel', action='store_true', help='Generate the outputs not listed as targets') - parser.add_argument('-l', '--list', action='store_true', help='List available outputs') + parser.add_argument('-l', '--list', action='store_true', help='List available outputs (in the config file)') group.add_argument('-q', '--quiet', action='store_true', help='remove information logs') parser.add_argument('-s', '--skip-pre', nargs=1, help='skip pre-flight actions, comma separated list or `all`') group.add_argument('-v', '--verbose', action='store_true', help='show debugging information') @@ -46,6 +49,13 @@ def main(): # Output dir: relative to CWD (absolute path overrides) GS.out_dir = os.path.join(os.getcwd(), args.out_dir) + if args.help_outputs or args.help_list_outputs: + print_outputs_help(details=args.help_outputs) + sys.exit(0) + if args.help_output: + print_output_help(args.help_output) + sys.exit(0) + # Determine the PCB file if args.board_file is None: board_files = glob('*.kicad_pcb') diff --git a/kiplot/config_reader.py b/kiplot/config_reader.py index b484233b..1581cde2 100644 --- a/kiplot/config_reader.py +++ b/kiplot/config_reader.py @@ -9,7 +9,7 @@ import pcbnew from .error import (KiPlotConfigurationError) from .kiplot import (Layer, get_layer_id_from_pcb) -from .misc import (NO_YAML_MODULE, EXIT_BAD_CONFIG) +from .misc import (NO_YAML_MODULE, EXIT_BAD_CONFIG, EXIT_BAD_ARGS) from mcpy import activate # noqa: F401 # Output classes from .out_base import BaseOutput @@ -268,3 +268,75 @@ class CfgYamlReader(object): if version is None: config_error("YAML config needs `kiplot.version`.") return outputs + + +def trim(docstring): + """ PEP 257 recommended trim for __doc__ """ + if not docstring: + return '' + # Convert tabs to spaces (following the normal Python rules) + # and split into a list of lines: + lines = docstring.expandtabs().splitlines() + # Determine minimum indentation (first line doesn't count): + indent = sys.maxsize + for line in lines[1:]: + stripped = line.lstrip() + if stripped: + indent = min(indent, len(line) - len(stripped)) + # Remove indentation (first line is special): + trimmed = [lines[0].strip()] + if indent < sys.maxsize: + for line in lines[1:]: + trimmed.append(line[indent:].rstrip()) + # Strip off trailing and leading blank lines: + while trimmed and not trimmed[-1]: + trimmed.pop() + while trimmed and not trimmed[0]: + trimmed.pop(0) + # Return a single string: + return trimmed + + +def print_output_options(name, cl): + obj = cl('', name, '') + print(' * Options:') + num_opts = 0 + for k, v in obj.__dict__.items(): + if k[0] != '_': + help_attr = '_help_'+k + help = obj.__dict__.get(help_attr) + print(' - {}: {}'.format(k, help if help else 'Undocumented')) + num_opts = num_opts+1 + if num_opts == 0: + print(' - No available options') + + +def print_one_out_help(details, n, o): + lines = trim(o.__doc__) + if len(lines) == 0: + lines = ['Undocumented', 'No description'] + if details: + print('* '+lines[0]) + print(' * Type: `{}`'.format(n)) + print(' * Description: '+lines[1]) + for ln in range(2, len(lines)): + print(' '+lines[ln]) + print_output_options(n, o) + else: + print('* {} [{}]'.format(lines[0], n)) + + +def print_outputs_help(details=False): + outs = BaseOutput.get_registered() + logger.debug('{} supported outputs'.format(len(outs))) + for n, o in outs.items(): + if details: + print() + print_one_out_help(details, n, o) + + +def print_output_help(name): + if not BaseOutput.is_registered(name): + logger.error('Unknown output type `{}`, try --help-list-outputs'.format(name)) + sys.exit(EXIT_BAD_ARGS) + print_one_out_help(True, name, BaseOutput.get_class_for(name)) diff --git a/kiplot/out_base.py b/kiplot/out_base.py index 066d2784..57792aae 100644 --- a/kiplot/out_base.py +++ b/kiplot/out_base.py @@ -59,6 +59,10 @@ class BaseOutput(object): def get_class_for(name): return BaseOutput._registered[name] + @staticmethod + def get_registered(): + return BaseOutput._registered + def __str__(self): return "'{}' ({}) [{}]".format(self._description, self._name, self._type)