cli: add `show-effective-configuration` command
authorPhilippe Proulx <eeppeliteloop@gmail.com>
Fri, 7 Aug 2020 21:17:13 +0000 (17:17 -0400)
committerPhilippe Proulx <eeppeliteloop@gmail.com>
Fri, 7 Aug 2020 21:18:02 +0000 (17:18 -0400)
This new command is equivalent to `generate --dump-config`, but in a
more dedicated way:

    $ barectf show-effective-configuration config.yaml

It only accepts relevant options.

One of the options is `--indent-spaces` which controls the indentation
space count of printed YAML lines.

This patch deprecates `--dump-config`: I'll leave it working in
barectf 3 to remain backward-compatible, but I'm removing it from the
`generate` command's help.

Signed-off-by: Philippe Proulx <eeppeliteloop@gmail.com>
barectf/cli.py

index 956ee0c7370c13482070147008c659370f0c92ee..468879edf591a5d7d6a91e59b086af41676c156d 100644 (file)
@@ -22,6 +22,7 @@
 # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
 import pkg_resources
+import collections
 import termcolor
 import argparse
 import os.path
@@ -104,78 +105,66 @@ def _opt_item_val(items, long_name, default=None):
     return True
 
 
-class _CliCfg:
+class _CliError(Exception):
     pass
 
 
-class _CliGenCmdCfg(_CliCfg):
-    def __init__(self, config_file_path, c_source_dir, c_header_dir, metadata_stream_dir,
-                 inclusion_dirs, ignore_inclusion_not_found, dump_config, v2_prefix):
-        self._config_file_path = config_file_path
-        self._c_source_dir = c_source_dir
-        self._c_header_dir = c_header_dir
-        self._metadata_stream_dir = metadata_stream_dir
-        self._inclusion_dirs = inclusion_dirs
-        self._ignore_inclusion_not_found = ignore_inclusion_not_found
-        self._dump_config = dump_config
-        self._v2_prefix = v2_prefix
+# Returns a `_CfgCmdCfg` object from the command-line parsing results
+# `parse_res`.
+def _cfg_cmd_cfg_from_parse_res(parse_res):
+    # check configuration file path
+    cfg_file_path = None
 
-    @property
-    def config_file_path(self):
-        return self._config_file_path
+    for item in parse_res.items:
+        if type(item) is barectf_argpar._NonOptItem:
+            if cfg_file_path is not None:
+                raise _CliError('Multiple configuration file paths provided')
 
-    @property
-    def c_source_dir(self):
-        return self._c_source_dir
+            cfg_file_path = item.text
 
-    @property
-    def c_header_dir(self):
-        return self._c_header_dir
+    if cfg_file_path is None:
+        raise _CliError('Missing configuration file path')
 
-    @property
-    def metadata_stream_dir(self):
-        return self._metadata_stream_dir
+    if not os.path.isfile(cfg_file_path):
+        raise _CliError(f'`{cfg_file_path}` is not an existing, regular file')
 
-    @property
-    def inclusion_dirs(self):
-        return self._inclusion_dirs
+    # inclusion directories
+    inclusion_dirs = [item.arg_text for item in _find_opt_items(parse_res.items, 'include-dir')]
 
-    @property
-    def ignore_inclusion_not_found(self):
-        return self._ignore_inclusion_not_found
+    for dir in inclusion_dirs:
+        if not os.path.isdir(dir):
+            raise _CliError(f'`{dir}` is not an existing directory')
 
-    @property
-    def dump_config(self):
-        return self._dump_config
+    inclusion_dirs.append(os.getcwd())
 
-    @property
-    def v2_prefix(self):
-        return self._v2_prefix
+    # other options
+    ignore_inclusion_file_not_found = _opt_item_val(parse_res.items, 'ignore-include-not-found',
+                                                    False)
+
+    return _CfgCmdCfg(cfg_file_path, inclusion_dirs, ignore_inclusion_file_not_found)
 
 
 def _print_gen_cmd_usage():
     print('''Usage: barectf generate [--code-dir=DIR] [--headers-dir=DIR]
                         [--metadata-dir=DIR] [--prefix=PREFIX]
                         [--include-dir=DIR]... [--ignore-include-not-found]
-                        [--dump-config] CONFIG-FILE-PATH
+                        CONFIG-FILE-PATH
 
 Options:
-  -c DIR, --code-dir=DIR        Write C source files to DIR
-  --dump-config                 Print the effective configuration file
-  -H DIR, --headers-dir=DIR     Write C header files to DIR
+  -c DIR, --code-dir=DIR        Write C source files to DIR instead of the CWD
+  -H DIR, --headers-dir=DIR     Write C header files to DIR instead of the CWD
   --ignore-include-not-found    Continue to process the configuration file when
                                 included files are not found
   -I DIR, --include-dir=DIR     Add DIR to the list of directories to be
                                 searched for inclusion files
-  -m DIR, --metadata-dir=DIR    Write the metadata stream file to DIR
+  -m DIR, --metadata-dir=DIR    Write the metadata stream file to DIR instead of
+                                the CWD
   -p PREFIX, --prefix=PREFIX    Set the configuration prefix to PREFIX''')
 
 
-class _CliError(Exception):
-    pass
-
-
-def _cli_gen_cfg_from_args(orig_args):
+# Returns a source and metadata stream file generating command object
+# from the specific command-line arguments `orig_args`.
+def _gen_cmd_cfg_from_args(orig_args):
     # parse original arguments
     opt_descrs = [
         barectf_argpar.OptDescr('h', 'help'),
@@ -195,41 +184,77 @@ def _cli_gen_cfg_from_args(orig_args):
         _print_gen_cmd_usage()
         sys.exit()
 
-    # check configuration file path
-    config_file_path = None
-
-    for item in res.items:
-        if type(item) is barectf_argpar._NonOptItem:
-            if config_file_path is not None:
-                raise _CliError('Multiple configuration file paths provided')
-
-            config_file_path = item.text
-
-    if config_file_path is None:
-        raise _CliError('Missing configuration file path')
-
-    if not os.path.isfile(config_file_path):
-        raise _CliError(f'`{config_file_path}` is not an existing, regular file')
+    # get common configuration file command CLI configuration
+    cfg_cmd_cfg = _cfg_cmd_cfg_from_parse_res(res)
 
     # directories
     c_source_dir = _opt_item_val(res.items, 'code-dir', os.getcwd())
     c_header_dir = _opt_item_val(res.items, 'headers-dir', os.getcwd())
     metadata_stream_dir = _opt_item_val(res.items, 'metadata-dir', os.getcwd())
-    inclusion_dirs = [item.arg_text for item in _find_opt_items(res.items, 'include-dir')]
 
-    for dir in [c_source_dir, c_header_dir, metadata_stream_dir] + inclusion_dirs:
+    for dir in [c_source_dir, c_header_dir, metadata_stream_dir]:
         if not os.path.isdir(dir):
             raise _CliError(f'`{dir}` is not an existing directory')
 
-    inclusion_dirs.append(os.getcwd())
-
     # other options
-    ignore_inclusion_not_found = _opt_item_val(res.items, 'ignore-include-not-found', False)
     dump_config = _opt_item_val(res.items, 'dump-config', False)
     v2_prefix = _opt_item_val(res.items, 'prefix')
 
-    return _CliGenCmdCfg(config_file_path, c_source_dir, c_header_dir, metadata_stream_dir,
-                         inclusion_dirs, ignore_inclusion_not_found, dump_config, v2_prefix)
+    return _GenCmd(_GenCmdCfg(cfg_cmd_cfg.cfg_file_path, c_source_dir, c_header_dir,
+                              metadata_stream_dir, cfg_cmd_cfg.inclusion_dirs,
+                              cfg_cmd_cfg.ignore_inclusion_file_not_found, dump_config, v2_prefix))
+
+
+def _print_show_effective_cfg_cmd_usage():
+    print('''Usage: barectf show-effective-configuration [--include-dir=DIR]...
+                                            [--ignore-include-not-found]
+                                            [--indent-spaces=COUNT] CONFIG-FILE-PATH
+
+Options:
+  --ignore-include-not-found    Continue to process the configuration file when
+                                included files are not found
+  -I DIR, --include-dir=DIR     Add DIR to the list of directories to be
+                                searched for inclusion files
+  --indent-spaces=COUNT         Use COUNT spaces at a time to indent YAML lines
+                                instead of 2''')
+
+
+# Returns an effective configuration showing command object from the
+# specific command-line arguments `orig_args`.
+def _show_effective_cfg_cfg_from_args(orig_args):
+    # parse original arguments
+    opt_descrs = [
+        barectf_argpar.OptDescr('h', 'help'),
+        barectf_argpar.OptDescr('I', 'include-dir', True),
+        barectf_argpar.OptDescr(long_name='indent-spaces', has_arg=True),
+        barectf_argpar.OptDescr(long_name='ignore-include-not-found'),
+    ]
+    res = barectf_argpar.parse(orig_args, opt_descrs)
+    assert len(res.ingested_orig_args) == len(orig_args)
+
+    # command help?
+    if len(_find_opt_items(res.items, 'help')) > 0:
+        _print_show_effective_cfg_cmd_usage()
+        sys.exit()
+
+    # get common configuration command CLI configuration
+    cfg_cmd_cfg = _cfg_cmd_cfg_from_parse_res(res)
+
+    # other options
+    indent_space_count = _opt_item_val(res.items, 'indent-spaces', 2)
+
+    try:
+        indent_space_count = int(indent_space_count)
+    except (ValueError, TypeError):
+        raise _CliError(f'Invalid `--indent-spaces` option argument: `{indent_space_count}`')
+
+    if indent_space_count < 1 or indent_space_count > 8:
+        raise _CliError(f'Invalid `--indent-spaces` option argument (`{indent_space_count}`): expecting a value in [1, 8]')
+
+    return _ShowEffectiveCfgCmd(_ShowEffectiveCfgCmdCfg(cfg_cmd_cfg.cfg_file_path,
+                                                        cfg_cmd_cfg.inclusion_dirs,
+                                                        cfg_cmd_cfg.ignore_inclusion_file_not_found,
+                                                        indent_space_count))
 
 
 def _print_general_usage():
@@ -242,13 +267,19 @@ General options:
   -V, --version    Show version and quit
 
 Available commands:
-  gen, generate    Generate the C source and CTF metadata files of a tracer
-                   from a configuration file
+  gen, generate                   Generate the C source and CTF metadata files
+                                  of a tracer from a configuration file
+  show-effective-configuration,   Print the effective configuration file for a
+  show-effective-config           given configuration file and inclusion
+                                  directories
 
 Run `barectf COMMAND --help` to show the help of COMMAND.''')
 
 
-def _cli_cfg_from_args():
+# Returns a command object from the command-line arguments `orig_args`.
+#
+# All the `orig_args` elements are considered.
+def _cmd_from_args(orig_args):
     # We use our `argpar` module here instead of Python's `argparse`
     # because we need to support the two following use cases:
     #
@@ -262,21 +293,28 @@ def _cli_cfg_from_args():
         barectf_argpar.OptDescr('V', 'version'),
         barectf_argpar.OptDescr('h', 'help'),
     ]
-    orig_args = sys.argv[1:]
     res = barectf_argpar.parse(orig_args, general_opt_descrs, False)
 
     # find command name, collecting preceding (common) option items
+    cmd_from_args_funcs = {
+        'generate': _gen_cmd_cfg_from_args,
+        'gen': _gen_cmd_cfg_from_args,
+        'show-effective-configuration': _show_effective_cfg_cfg_from_args,
+        'show-effective-config': _show_effective_cfg_cfg_from_args,
+        'show-effective-cfg': _show_effective_cfg_cfg_from_args,
+    }
     general_opt_items = []
     cmd_first_orig_arg_index = None
-    cmd_name = None
+    cmd_from_args_func = None
 
     for item in res.items:
         if type(item) is barectf_argpar._NonOptItem:
-            if item.text in ['gen', 'generate']:
-                cmd_name = 'generate'
-                cmd_first_orig_arg_index = item.orig_arg_index + 1
-            else:
+            cmd_from_args_func = cmd_from_args_funcs.get(item.text)
+
+            if cmd_from_args_func is None:
                 cmd_first_orig_arg_index = item.orig_arg_index
+            else:
+                cmd_first_orig_arg_index = item.orig_arg_index + 1
 
             break
         else:
@@ -296,80 +334,175 @@ def _cli_cfg_from_args():
     # execute command
     cmd_orig_args = orig_args[cmd_first_orig_arg_index:]
 
-    if cmd_name is None:
+    if cmd_from_args_func is None:
         # default `generate` command
-        return _cli_gen_cfg_from_args(cmd_orig_args)
+        return _gen_cmd_cfg_from_args(cmd_orig_args)
     else:
-        assert cmd_name == 'generate'
-        return _cli_gen_cfg_from_args(cmd_orig_args)
+        return cmd_from_args_func(cmd_orig_args)
+
+
+class _CmdCfg:
+    pass
+
+
+class _CfgCmdCfg(_CmdCfg):
+    def __init__(self, cfg_file_path, inclusion_dirs, ignore_inclusion_file_not_found):
+        self._cfg_file_path = cfg_file_path
+        self._inclusion_dirs = inclusion_dirs
+        self._ignore_inclusion_file_not_found = ignore_inclusion_file_not_found
+
+    @property
+    def cfg_file_path(self):
+        return self._cfg_file_path
+
+    @property
+    def inclusion_dirs(self):
+        return self._inclusion_dirs
+
+    @property
+    def ignore_inclusion_file_not_found(self):
+        return self._ignore_inclusion_file_not_found
+
+
+class _Cmd:
+    def __init__(self, cfg):
+        self._cfg = cfg
+
+    @property
+    def cfg(self):
+        return self._cfg
+
+    def exec(self):
+        raise NotImplementedError
+
+
+class _GenCmdCfg(_CfgCmdCfg):
+    def __init__(self, cfg_file_path, c_source_dir, c_header_dir, metadata_stream_dir,
+                 inclusion_dirs, ignore_inclusion_file_not_found, dump_config, v2_prefix):
+        super().__init__(cfg_file_path, inclusion_dirs, ignore_inclusion_file_not_found)
+        self._c_source_dir = c_source_dir
+        self._c_header_dir = c_header_dir
+        self._metadata_stream_dir = metadata_stream_dir
+        self._dump_config = dump_config
+        self._v2_prefix = v2_prefix
+
+    @property
+    def c_source_dir(self):
+        return self._c_source_dir
+
+    @property
+    def c_header_dir(self):
+        return self._c_header_dir
+
+    @property
+    def metadata_stream_dir(self):
+        return self._metadata_stream_dir
+
+    @property
+    def dump_config(self):
+        return self._dump_config
+
+    @property
+    def v2_prefix(self):
+        return self._v2_prefix
+
+
+# Source and metadata stream file generating command.
+class _GenCmd(_Cmd):
+    def exec(self):
+        # create configuration
+        try:
+            with open(self.cfg.cfg_file_path) as f:
+                if self.cfg.dump_config:
+                    # print effective configuration file
+                    print(barectf.effective_configuration_file(f, True, self.cfg.inclusion_dirs,
+                                                               self.cfg.ignore_inclusion_file_not_found))
+
+                    # barectf.configuration_from_file() reads the file again
+                    # below: rewind.
+                    f.seek(0)
+
+                config = barectf.configuration_from_file(f, True, self.cfg.inclusion_dirs,
+                                                         self.cfg.ignore_inclusion_file_not_found)
+        except barectf._ConfigurationParseError as exc:
+            _print_config_error(exc)
+        except Exception as exc:
+            _print_unknown_exc(exc)
+
+        if self.cfg.v2_prefix is not None:
+            # Override prefixes.
+            #
+            # For historical reasons, the `--prefix` option applies the
+            # barectf 2 configuration prefix rules. Therefore, get the
+            # equivalent barectf 3 prefixes first.
+            v3_prefixes = barectf_config_parse_common._v3_prefixes_from_v2_prefix(self.cfg.v2_prefix)
+            cg_opts = config.options.code_generation_options
+            cg_opts = barectf.ConfigurationCodeGenerationOptions(v3_prefixes.identifier,
+                                                                 v3_prefixes.file_name,
+                                                                 cg_opts.default_stream_type,
+                                                                 cg_opts.header_options,
+                                                                 cg_opts.clock_type_c_types)
+            config = barectf.Configuration(config.trace, barectf.ConfigurationOptions(cg_opts))
+
+        # create a barectf code generator
+        code_gen = barectf.CodeGenerator(config)
+
+        def write_file(dir, file):
+            with open(os.path.join(dir, file.name), 'w') as f:
+                f.write(file.contents)
+
+        def write_files(dir, files):
+            for file in files:
+                write_file(dir, file)
+
+        try:
+            # generate and write metadata stream file
+            write_file(self.cfg.metadata_stream_dir, code_gen.generate_metadata_stream())
+
+            # generate and write C header files
+            write_files(self.cfg.c_header_dir, code_gen.generate_c_headers())
+
+            # generate and write C source files
+            write_files(self.cfg.c_source_dir, code_gen.generate_c_sources())
+        except Exception as exc:
+            # We know `config` is valid, therefore the code generator cannot
+            # fail for a reason known to barectf.
+            _print_unknown_exc(exc)
+
+
+class _ShowEffectiveCfgCmdCfg(_CfgCmdCfg):
+    def __init__(self, cfg_file_path, inclusion_dirs, ignore_inclusion_file_not_found,
+                 indent_space_count):
+        super().__init__(cfg_file_path, inclusion_dirs, ignore_inclusion_file_not_found)
+        self._indent_space_count = indent_space_count
+
+    @property
+    def indent_space_count(self):
+        return self._indent_space_count
+
+
+# Effective configuration showing command.
+class _ShowEffectiveCfgCmd(_Cmd):
+    def exec(self):
+        try:
+            with open(self.cfg.cfg_file_path) as f:
+                print(barectf.effective_configuration_file(f, True, self.cfg.inclusion_dirs,
+                                                           self.cfg.ignore_inclusion_file_not_found,
+                                                           self.cfg.indent_space_count))
+        except barectf._ConfigurationParseError as exc:
+            _print_config_error(exc)
+        except Exception as exc:
+            _print_unknown_exc(exc)
 
 
 def _run():
-    # parse arguments
+    # create command from arguments
     try:
-        cli_cfg = _cli_cfg_from_args()
+        cmd = _cmd_from_args(sys.argv[1:])
     except barectf_argpar._Error as exc:
         _print_error(f'Command-line: For argument `{exc.orig_arg}`: {exc.msg}')
     except _CliError as exc:
         _print_error(f'Command-line: {exc}')
 
-    assert type(cli_cfg) is _CliGenCmdCfg
-
-    # create configuration
-    try:
-        with open(cli_cfg.config_file_path) as f:
-            if cli_cfg.dump_config:
-                # print effective configuration file
-                print(barectf.effective_configuration_file(f, True, cli_cfg.inclusion_dirs,
-                                                           cli_cfg.ignore_inclusion_not_found))
-
-                # barectf.configuration_from_file() reads the file again
-                # below: rewind.
-                f.seek(0)
-
-            config = barectf.configuration_from_file(f, True, cli_cfg.inclusion_dirs,
-                                                     cli_cfg.ignore_inclusion_not_found)
-    except barectf._ConfigurationParseError as exc:
-        _print_config_error(exc)
-    except Exception as exc:
-        _print_unknown_exc(exc)
-
-    if cli_cfg.v2_prefix is not None:
-        # Override prefixes.
-        #
-        # For historical reasons, the `--prefix` option applies the
-        # barectf 2 configuration prefix rules. Therefore, get the
-        # equivalent barectf 3 prefixes first.
-        v3_prefixes = barectf_config_parse_common._v3_prefixes_from_v2_prefix(cli_cfg.v2_prefix)
-        cg_opts = config.options.code_generation_options
-        cg_opts = barectf.ConfigurationCodeGenerationOptions(v3_prefixes.identifier,
-                                                             v3_prefixes.file_name,
-                                                             cg_opts.default_stream_type,
-                                                             cg_opts.header_options,
-                                                             cg_opts.clock_type_c_types)
-        config = barectf.Configuration(config.trace, barectf.ConfigurationOptions(cg_opts))
-
-    # create a barectf code generator
-    code_gen = barectf.CodeGenerator(config)
-
-    def write_file(dir, file):
-        with open(os.path.join(dir, file.name), 'w') as f:
-            f.write(file.contents)
-
-    def write_files(dir, files):
-        for file in files:
-            write_file(dir, file)
-
-    try:
-        # generate and write metadata stream file
-        write_file(cli_cfg.metadata_stream_dir, code_gen.generate_metadata_stream())
-
-        # generate and write C header files
-        write_files(cli_cfg.c_header_dir, code_gen.generate_c_headers())
-
-        # generate and write C source files
-        write_files(cli_cfg.c_source_dir, code_gen.generate_c_sources())
-    except Exception as exc:
-        # We know `config` is valid, therefore the code generator cannot
-        # fail for a reason known to barectf.
-        _print_unknown_exc(exc)
+    # execute command
+    cmd.exec()
This page took 0.028587 seconds and 4 git commands to generate.