Imports can be nested now

- import stuff that was imported ...
- Is the first approach, works for a simple case

Related to #218
This commit is contained in:
Salvador E. Tropea 2022-07-12 10:55:00 -03:00
parent 42503a909a
commit 6c118b73fa
7 changed files with 124 additions and 25 deletions

View File

@ -9,7 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Try to download missing tools and Python modules. - Try to download missing tools and Python modules.
The user also gets more information when something is missing. The user also gets more information when something is missing.
It can be disabled from the command line. It can be disabled from the command line.
- A global option to cross components without a body. - A global option to cross components without a body (#219)
- Now you can nest imports (import from an imported file) (#218)
## [1.2.0] - 2022-06-15 ## [1.2.0] - 2022-06-15
### Added ### Added

View File

@ -45,6 +45,16 @@ except ImportError:
exit(NO_YAML_MODULE) exit(NO_YAML_MODULE)
class CollectedImports(object):
def __init__(self):
super().__init__()
self.outputs = []
self.filters = {}
self.variants = {}
self.globals = {}
self.preflights = []
class CfgYamlReader(object): class CfgYamlReader(object):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
@ -227,15 +237,15 @@ class CfgYamlReader(object):
return values return values
CfgYamlReader._config_error_import(fname, '`{}` must be a string or a list ({})'.format(kind, str(v))) CfgYamlReader._config_error_import(fname, '`{}` must be a string or a list ({})'.format(kind, str(v)))
def _parse_import_outputs(self, outs, explicit_outs, fn_rel, data): def _parse_import_outputs(self, outs, explicit_outs, fn_rel, data, imported):
if (outs is None or len(outs) > 0) and 'outputs' in data:
i_outs = self._parse_outputs(data['outputs'])
if outs is not None:
sel_outs = [] sel_outs = []
if (outs is None or len(outs) > 0) and 'outputs' in data:
i_outs = imported.outputs+self._parse_outputs(data['outputs'])
if outs is not None:
for o in i_outs: for o in i_outs:
if o.name in outs: if o.name in outs:
sel_outs.append(o) sel_outs.append(o)
outs.remove(o) outs.remove(o.name)
for o in outs: for o in outs:
logger.warning(W_UNKOUT+"can't import `{}` output from `{}` (missing)".format(o, fn_rel)) logger.warning(W_UNKOUT+"can't import `{}` output from `{}` (missing)".format(o, fn_rel))
else: else:
@ -243,19 +253,16 @@ class CfgYamlReader(object):
if len(sel_outs) == 0: if len(sel_outs) == 0:
logger.warning(W_NOOUTPUTS+"No outputs found in `{}`".format(fn_rel)) logger.warning(W_NOOUTPUTS+"No outputs found in `{}`".format(fn_rel))
else: else:
try:
RegOutput.add_outputs(sel_outs, fn_rel)
except KiPlotConfigurationError as e:
config_error(str(e))
logger.debug('Outputs loaded from `{}`: {}'.format(fn_rel, list(map(lambda c: c.name, sel_outs)))) logger.debug('Outputs loaded from `{}`: {}'.format(fn_rel, list(map(lambda c: c.name, sel_outs))))
if outs is None and explicit_outs and 'outputs' not in data: if outs is None and explicit_outs and 'outputs' not in data:
logger.warning(W_NOOUTPUTS+"No outputs found in `{}`".format(fn_rel)) logger.warning(W_NOOUTPUTS+"No outputs found in `{}`".format(fn_rel))
return sel_outs
def _parse_import_filters(self, filters, explicit_fils, fn_rel, data): def _parse_import_filters(self, filters, explicit_fils, fn_rel, data, imported):
if (filters is None or len(filters) > 0) and 'filters' in data:
i_fils = self._parse_filters(data['filters'])
if filters is not None:
sel_fils = {} sel_fils = {}
if (filters is None or len(filters) > 0) and 'filters' in data:
i_fils = imported.filters.update(self._parse_filters(data['filters']))
if filters is not None:
for f in filters: for f in filters:
if f in i_fils: if f in i_fils:
sel_fils[f] = i_fils[f] sel_fils[f] = i_fils[f]
@ -266,16 +273,16 @@ class CfgYamlReader(object):
if len(sel_fils) == 0: if len(sel_fils) == 0:
logger.warning(W_NOFILTERS+"No filters found in `{}`".format(fn_rel)) logger.warning(W_NOFILTERS+"No filters found in `{}`".format(fn_rel))
else: else:
RegOutput.add_filters(sel_fils)
logger.debug('Filters loaded from `{}`: {}'.format(fn_rel, sel_fils.keys())) logger.debug('Filters loaded from `{}`: {}'.format(fn_rel, sel_fils.keys()))
if filters is None and explicit_fils and 'filters' not in data: if filters is None and explicit_fils and 'filters' not in data:
logger.warning(W_NOFILTERS+"No filters found in `{}`".format(fn_rel)) logger.warning(W_NOFILTERS+"No filters found in `{}`".format(fn_rel))
return sel_fils
def _parse_import_variants(self, vars, explicit_vars, fn_rel, data): def _parse_import_variants(self, vars, explicit_vars, fn_rel, data, imported):
if (vars is None or len(vars) > 0) and 'variants' in data:
i_vars = self._parse_variants(data['variants'])
if vars is not None:
sel_vars = {} sel_vars = {}
if (vars is None or len(vars) > 0) and 'variants' in data:
i_vars = imported.variants.update(self._parse_variants(data['variants']))
if vars is not None:
for f in vars: for f in vars:
if f in i_vars: if f in i_vars:
sel_vars[f] = i_vars[f] sel_vars[f] = i_vars[f]
@ -286,18 +293,20 @@ class CfgYamlReader(object):
if len(sel_vars) == 0: if len(sel_vars) == 0:
logger.warning(W_NOVARIANTS+"No variants found in `{}`".format(fn_rel)) logger.warning(W_NOVARIANTS+"No variants found in `{}`".format(fn_rel))
else: else:
RegOutput.add_variants(sel_vars)
logger.debug('Variants loaded from `{}`: {}'.format(fn_rel, sel_vars.keys())) logger.debug('Variants loaded from `{}`: {}'.format(fn_rel, sel_vars.keys()))
if vars is None and explicit_vars and 'variants' not in data: if vars is None and explicit_vars and 'variants' not in data:
logger.warning(W_NOVARIANTS+"No variants found in `{}`".format(fn_rel)) logger.warning(W_NOVARIANTS+"No variants found in `{}`".format(fn_rel))
return sel_vars
def _parse_import_globals(self, globals, explicit_globals, fn_rel, data): def _parse_import_globals(self, globals, explicit_globals, fn_rel, data, imported):
sel_globals = {}
if (globals is None or len(globals) > 0) and 'global' in data: if (globals is None or len(globals) > 0) and 'global' in data:
i_globals = data['global'] i_globals = data['global']
if not isinstance(i_globals, dict): if not isinstance(i_globals, dict):
config_error("Incorrect `global` section (must be a dict), while importing from {}".format(fn_rel)) config_error("Incorrect `global` section (must be a dict), while importing from {}".format(fn_rel))
imported.globals.update(i_globals)
i_globals = imported.globals
if globals is not None: if globals is not None:
sel_globals = {}
for f in globals: for f in globals:
if f in i_globals: if f in i_globals:
sel_globals[f] = i_globals[f] sel_globals[f] = i_globals[f]
@ -308,18 +317,22 @@ class CfgYamlReader(object):
if len(sel_globals) == 0: if len(sel_globals) == 0:
logger.warning(W_NOGLOBALS+"No globals found in `{}`".format(fn_rel)) logger.warning(W_NOGLOBALS+"No globals found in `{}`".format(fn_rel))
else: else:
self.imported_globals.update(sel_globals)
logger.debug('Globals loaded from `{}`: {}'.format(fn_rel, sel_globals.keys())) logger.debug('Globals loaded from `{}`: {}'.format(fn_rel, sel_globals.keys()))
if globals is None and explicit_globals and 'global' not in data: if globals is None and explicit_globals and 'global' not in data:
logger.warning(W_NOGLOBALS+"No globals found in `{}`".format(fn_rel)) logger.warning(W_NOGLOBALS+"No globals found in `{}`".format(fn_rel))
return sel_globals
def _parse_import(self, imp, name): def _parse_import(self, imp, name, apply=True, depth=0):
""" Get imports """ """ Get imports """
logger.debug("Parsing imports: {}".format(imp)) logger.debug("Parsing imports: {}".format(imp))
depth += 1
if depth > 20:
config_error("Import depth greater than 20, make sure this isn't an infinite loop")
if not isinstance(imp, list): if not isinstance(imp, list):
config_error("Incorrect `import` section (must be a list)") config_error("Incorrect `import` section (must be a list)")
# Import the files # Import the files
dir = os.path.dirname(os.path.abspath(name)) dir = os.path.dirname(os.path.abspath(name))
all_collected = CollectedImports()
for entry in imp: for entry in imp:
if isinstance(entry, str): if isinstance(entry, str):
fn = entry fn = entry
@ -364,14 +377,31 @@ class CfgYamlReader(object):
config_error("missing import file `{}`".format(fn)) config_error("missing import file `{}`".format(fn))
fn_rel = os.path.relpath(fn) fn_rel = os.path.relpath(fn)
data = self.load_yaml(open(fn)) data = self.load_yaml(open(fn))
if 'import' in data:
# Do a recursive import
imported = self._parse_import(data['import'], fn, apply=False, depth=depth)
else:
# Nothing to import, start fresh
imported = CollectedImports()
# Parse and filter all stuff, add them to all_collected
# Outputs # Outputs
self._parse_import_outputs(outs, explicit_outs, fn_rel, data) all_collected.outputs.extend(self._parse_import_outputs(outs, explicit_outs, fn_rel, data, imported))
# Filters # Filters
self._parse_import_filters(filters, explicit_fils, fn_rel, data) all_collected.filters.update(self._parse_import_filters(filters, explicit_fils, fn_rel, data, imported))
# Variants # Variants
self._parse_import_variants(vars, explicit_vars, fn_rel, data) all_collected.variants.update(self._parse_import_variants(vars, explicit_vars, fn_rel, data, imported))
# Globals # Globals
self._parse_import_globals(globals, explicit_globals, fn_rel, data) all_collected.globals.update(self._parse_import_globals(globals, explicit_globals, fn_rel, data, imported))
if apply:
# This is the main import (not a recursive one) apply the results
try:
RegOutput.add_outputs(all_collected.outputs, fn_rel)
except KiPlotConfigurationError as e:
config_error(str(e))
RegOutput.add_filters(all_collected.filters)
RegOutput.add_variants(all_collected.variants)
self.imported_globals = all_collected.globals
return all_collected
def load_yaml(self, fstream): def load_yaml(self, fstream):
try: try:

View File

@ -823,6 +823,24 @@ def test_import_4(test_dir):
ctx.clean_up() ctx.clean_up()
def test_import_5(test_dir):
""" Infinite loop import """
prj = 'test_v5'
ctx = context.TestContext(test_dir, prj, 'import_test_5')
ctx.run(EXIT_BAD_CONFIG)
ctx.search_err(r'.*infinite loop')
ctx.clean_up()
def test_import_6(test_dir):
""" Import an output and change it, but using an import inside another """
prj = 'test_v5'
ctx = context.TestContext(test_dir, prj, 'import_test_6')
ctx.run(extra=['position_mine'])
ctx.expect_out_file(POS_DIR+'/test_v5_(both_pos).csv')
ctx.clean_up()
def test_disable_default_1(test_dir): def test_disable_default_1(test_dir):
""" Disable in the same file and out-of-order """ """ Disable in the same file and out-of-order """
prj = 'test_v5' prj = 'test_v5'

View File

@ -0,0 +1,15 @@
kibot:
version: 1
import:
- import_test_5b.kibot.yaml
outputs:
- name: 'position_mine'
comment: "Pick and place file"
type: position
dir: positiondir
extends: position
disable_run_by_default: true
options:
separate_files_for_front_and_back: false

View File

@ -0,0 +1,6 @@
kibot:
version: 1
import:
# Create an infinite loop
- import_test_5.kibot.yaml

View File

@ -0,0 +1,17 @@
kibot:
version: 1
import:
# Here we change the global.output pattern
- file: global_import.kibot.yaml
- file: import_test_6b.kibot.yaml
outputs: position
outputs:
- name: 'position_mine'
comment: "Pick and place file"
type: position
dir: positiondir
extends: position
options:
separate_files_for_front_and_back: false

View File

@ -0,0 +1,12 @@
kibot:
version: 1
import:
- file: simple_position_csv.kibot.yaml
outputs:
- name: 'position_mine'
comment: "Pick and place file"
type: position
dir: positiondir
extends: position