Added a mechanism to specify suboptions.
Now the legacy drill.map.type and drill.report.filename are specified in this way. The BaseOutput class now inherits from Optionable. Suboptions are just Optionable classes. Also: added traceback print when an error is reported and we are in debug mode.
This commit is contained in:
parent
ee11ecf8e7
commit
45ecb1d02a
46
README.md
46
README.md
|
|
@ -228,13 +228,17 @@ Most options are the same you'll find in the KiCad dialogs.
|
||||||
You can create a map file for documentation purposes.
|
You can create a map file for documentation purposes.
|
||||||
This output is what you get from the 'File/Fabrication output/Drill Files' menu in pcbnew.
|
This output is what you get from the 'File/Fabrication output/Drill Files' menu in pcbnew.
|
||||||
* Options:
|
* Options:
|
||||||
- `map`: [string=None] format for a graphical drill map. The valid formats are hpgl, ps, gerber, dxf, svg and pdf.
|
- `map`: [dict|string] [hpgl,ps,gerber,dxf,svg,pdf] format for a graphical drill map.
|
||||||
Not generated unless a format is specified.
|
Not generated unless a format is specified.
|
||||||
|
* Options:
|
||||||
|
- `type`: [string='pdf'] [hpgl,ps,gerber,dxf,svg,pdf] format for a graphical drill map.
|
||||||
- `metric_units`: [boolean=true] use metric units instead of inches.
|
- `metric_units`: [boolean=true] use metric units instead of inches.
|
||||||
- `minimal_header`: [boolean=false] use a minimal header in the file.
|
- `minimal_header`: [boolean=false] use a minimal header in the file.
|
||||||
- `mirror_y_axis`: [boolean=false] invert the Y axis.
|
- `mirror_y_axis`: [boolean=false] invert the Y axis.
|
||||||
- `pth_and_npth_single_file`: [boolean=true] generate one file for both, plated holes and non-plated holes, instead of two separated files.
|
- `pth_and_npth_single_file`: [boolean=true] generate one file for both, plated holes and non-plated holes, instead of two separated files.
|
||||||
- `report`: [string=None] name of the drill report. Not generated unless a name is specified.
|
- `report`: [dict|string] name of the drill report. Not generated unless a name is specified.
|
||||||
|
* Options:
|
||||||
|
- `filename`: [string=''] name of the drill report. Not generated unless a name is specified.
|
||||||
- `use_aux_axis_as_origin`: [boolean=false] use the auxiliar axis as origin for coordinates.
|
- `use_aux_axis_as_origin`: [boolean=false] use the auxiliar axis as origin for coordinates.
|
||||||
|
|
||||||
* Gerber drill format
|
* Gerber drill format
|
||||||
|
|
@ -243,9 +247,13 @@ Most options are the same you'll find in the KiCad dialogs.
|
||||||
You can create a map file for documentation purposes.
|
You can create a map file for documentation purposes.
|
||||||
This output is what you get from the 'File/Fabrication output/Drill Files' menu in pcbnew.
|
This output is what you get from the 'File/Fabrication output/Drill Files' menu in pcbnew.
|
||||||
* Options:
|
* Options:
|
||||||
- `map`: [string=None] format for a graphical drill map. The valid formats are hpgl, ps, gerber, dxf, svg and pdf.
|
- `map`: [dict|string] [hpgl,ps,gerber,dxf,svg,pdf] format for a graphical drill map.
|
||||||
Not generated unless a format is specified.
|
Not generated unless a format is specified.
|
||||||
- `report`: [string=None] name of the drill report. Not generated unless a name is specified.
|
* Options:
|
||||||
|
- `type`: [string='pdf'] [hpgl,ps,gerber,dxf,svg,pdf] format for a graphical drill map.
|
||||||
|
- `report`: [dict|string] name of the drill report. Not generated unless a name is specified.
|
||||||
|
* Options:
|
||||||
|
- `filename`: [string=''] name of the drill report. Not generated unless a name is specified.
|
||||||
- `use_aux_axis_as_origin`: [boolean=false] use the auxiliar axis as origin for coordinates.
|
- `use_aux_axis_as_origin`: [boolean=false] use the auxiliar axis as origin for coordinates.
|
||||||
|
|
||||||
* Gerber format
|
* Gerber format
|
||||||
|
|
@ -254,7 +262,7 @@ Most options are the same you'll find in the KiCad dialogs.
|
||||||
This output is what you get from the File/Plot menu in pcbnew.
|
This output is what you get from the File/Plot menu in pcbnew.
|
||||||
* Options:
|
* Options:
|
||||||
- `create_gerber_job_file`: [boolean=true] creates a file with information about all the generated gerbers.
|
- `create_gerber_job_file`: [boolean=true] creates a file with information about all the generated gerbers.
|
||||||
You can use it in gerbview to load all gerbers at once.
|
You can use it in gerbview to load all gerbers at once.
|
||||||
- `exclude_edge_layer`: [boolean=true] do not include the PCB edge layer.
|
- `exclude_edge_layer`: [boolean=true] do not include the PCB edge layer.
|
||||||
- `exclude_pads_from_silkscreen`: [boolean=false] do not plot the component pads in the silk screen.
|
- `exclude_pads_from_silkscreen`: [boolean=false] do not plot the component pads in the silk screen.
|
||||||
- `force_plot_invisible_refs_vals`: [boolean=false] include references and values even when they are marked as invisible.
|
- `force_plot_invisible_refs_vals`: [boolean=false] include references and values even when they are marked as invisible.
|
||||||
|
|
@ -303,7 +311,7 @@ Most options are the same you'll find in the KiCad dialogs.
|
||||||
- `checkboxes`: [string='Sourced,Placed'] Comma separated list of checkbox columns.
|
- `checkboxes`: [string='Sourced,Placed'] Comma separated list of checkbox columns.
|
||||||
- `dark_mode`: [boolean=false] Default to dark mode.
|
- `dark_mode`: [boolean=false] Default to dark mode.
|
||||||
- `dnp_field`: [string=''] Name of the extra field that indicates do not populate status. Components with this field not empty will be
|
- `dnp_field`: [string=''] Name of the extra field that indicates do not populate status. Components with this field not empty will be
|
||||||
blacklisted.
|
blacklisted.
|
||||||
- `extra_fields`: [string=''] Comma separated list of extra fields to pull from netlist or xml file.
|
- `extra_fields`: [string=''] Comma separated list of extra fields to pull from netlist or xml file.
|
||||||
- `hide_pads`: [boolean=false] Hide footprint pads by default.
|
- `hide_pads`: [boolean=false] Hide footprint pads by default.
|
||||||
- `hide_silkscreen`: [boolean=false] Hide silkscreen by default.
|
- `hide_silkscreen`: [boolean=false] Hide silkscreen by default.
|
||||||
|
|
@ -312,13 +320,13 @@ Most options are the same you'll find in the KiCad dialogs.
|
||||||
- `include_tracks`: [boolean=false] Include track/zone information in output. F.Cu and B.Cu layers only.
|
- `include_tracks`: [boolean=false] Include track/zone information in output. F.Cu and B.Cu layers only.
|
||||||
- `layer_view`: [string='FB'] [F,FB,B] Default layer view.
|
- `layer_view`: [string='FB'] [F,FB,B] Default layer view.
|
||||||
- `name_format`: [string='ibom'] Output file name format supports substitutions:
|
- `name_format`: [string='ibom'] Output file name format supports substitutions:
|
||||||
%f : original pcb file name without extension.
|
%f : original pcb file name without extension.
|
||||||
%p : pcb/project title from pcb metadata.
|
%p : pcb/project title from pcb metadata.
|
||||||
%c : company from pcb metadata.
|
%c : company from pcb metadata.
|
||||||
%r : revision from pcb metadata.
|
%r : revision from pcb metadata.
|
||||||
%d : pcb date from metadata if available, file modification date otherwise.
|
%d : pcb date from metadata if available, file modification date otherwise.
|
||||||
%D : bom generation date.
|
%D : bom generation date.
|
||||||
%T : bom generation time. Extension .html will be added automatically.
|
%T : bom generation time. Extension .html will be added automatically.
|
||||||
- `netlist_file`: [string=''] Path to netlist or xml file.
|
- `netlist_file`: [string=''] Path to netlist or xml file.
|
||||||
- `no_blacklist_virtual`: [boolean=false] Do not blacklist virtual components.
|
- `no_blacklist_virtual`: [boolean=false] Do not blacklist virtual components.
|
||||||
- `no_redraw_on_drag`: [boolean=false] Do not redraw pcb on drag by default.
|
- `no_redraw_on_drag`: [boolean=false] Do not redraw pcb on drag by default.
|
||||||
|
|
@ -340,9 +348,9 @@ Most options are the same you'll find in the KiCad dialogs.
|
||||||
- `number`: [number=1] Number of boards to build (components multiplier).
|
- `number`: [number=1] Number of boards to build (components multiplier).
|
||||||
- `separator`: [string=','] CSV Separator.
|
- `separator`: [string=','] CSV Separator.
|
||||||
- `variant`: [string=''] Board variant(s), used to determine which components
|
- `variant`: [string=''] Board variant(s), used to determine which components
|
||||||
are output to the BoM. To specify multiple variants,
|
are output to the BoM. To specify multiple variants,
|
||||||
with a BOM file exported for each variant, separate
|
with a BOM file exported for each variant, separate
|
||||||
variants with the ';' (semicolon) character.
|
variants with the ';' (semicolon) character.
|
||||||
|
|
||||||
* PDF (Portable Document Format)
|
* PDF (Portable Document Format)
|
||||||
* Type: `pdf`
|
* Type: `pdf`
|
||||||
|
|
@ -410,7 +418,7 @@ Most options are the same you'll find in the KiCad dialogs.
|
||||||
- `sketch_plot`: [boolean=false] don't fill objects, just draw the outline.
|
- `sketch_plot`: [boolean=false] don't fill objects, just draw the outline.
|
||||||
- `tent_vias`: [boolean=true] cover the vias.
|
- `tent_vias`: [boolean=true] cover the vias.
|
||||||
- `width_adjust`: [number=0] this width factor is intended to compensate PS printers/plotters that do not strictly obey line width settings.
|
- `width_adjust`: [number=0] this width factor is intended to compensate PS printers/plotters that do not strictly obey line width settings.
|
||||||
Only used to plot pads and tracks.
|
Only used to plot pads and tracks.
|
||||||
|
|
||||||
* STEP (ISO 10303-21 Clear Text Encoding of the Exchange Structure)
|
* STEP (ISO 10303-21 Clear Text Encoding of the Exchange Structure)
|
||||||
* Type: `step`
|
* Type: `step`
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
all: ../README.md samples/generic_plot.kiplot.yaml
|
all: ../README.md samples/generic_plot.kiplot.yaml
|
||||||
|
|
||||||
../README.md: README.in replace_tags.pl ../kiplot/out_*.py ../kiplot/pre_*.py ../kiplot/__main__.py
|
../README.md: README.in replace_tags.pl ../kiplot/out_*.py ../kiplot/pre_*.py ../kiplot/__main__.py ../kiplot/config_reader.py
|
||||||
cat README.in | perl replace_tags.pl > ../README.md
|
cat README.in | perl replace_tags.pl > ../README.md
|
||||||
|
|
||||||
samples/generic_plot.kiplot.yaml: ../kiplot/out_*.py ../kiplot/pre_*.py ../kiplot/config_reader.py
|
samples/generic_plot.kiplot.yaml: ../kiplot/out_*.py ../kiplot/pre_*.py ../kiplot/config_reader.py
|
||||||
|
|
|
||||||
|
|
@ -120,9 +120,11 @@ outputs:
|
||||||
type: 'excellon'
|
type: 'excellon'
|
||||||
dir: 'Example/excellon_dir'
|
dir: 'Example/excellon_dir'
|
||||||
options:
|
options:
|
||||||
# [string=None] format for a graphical drill map. The valid formats are hpgl, ps, gerber, dxf, svg and pdf.
|
# [dict|string] [hpgl,ps,gerber,dxf,svg,pdf] format for a graphical drill map.
|
||||||
# Not generated unless a format is specified
|
# Not generated unless a format is specified
|
||||||
map: None
|
map:
|
||||||
|
# [string='pdf'] [hpgl,ps,gerber,dxf,svg,pdf] format for a graphical drill map
|
||||||
|
type: 'pdf'
|
||||||
# [boolean=true] use metric units instead of inches
|
# [boolean=true] use metric units instead of inches
|
||||||
metric_units: true
|
metric_units: true
|
||||||
# [boolean=false] use a minimal header in the file
|
# [boolean=false] use a minimal header in the file
|
||||||
|
|
@ -131,8 +133,10 @@ outputs:
|
||||||
mirror_y_axis: false
|
mirror_y_axis: false
|
||||||
# [boolean=true] generate one file for both, plated holes and non-plated holes, instead of two separated files
|
# [boolean=true] generate one file for both, plated holes and non-plated holes, instead of two separated files
|
||||||
pth_and_npth_single_file: true
|
pth_and_npth_single_file: true
|
||||||
# [string=None] name of the drill report. Not generated unless a name is specified
|
# [dict|string] name of the drill report. Not generated unless a name is specified
|
||||||
report: None
|
report:
|
||||||
|
# [string=''] name of the drill report. Not generated unless a name is specified
|
||||||
|
filename: ''
|
||||||
# [boolean=false] use the auxiliar axis as origin for coordinates
|
# [boolean=false] use the auxiliar axis as origin for coordinates
|
||||||
use_aux_axis_as_origin: false
|
use_aux_axis_as_origin: false
|
||||||
|
|
||||||
|
|
@ -144,11 +148,15 @@ outputs:
|
||||||
type: 'gerb_drill'
|
type: 'gerb_drill'
|
||||||
dir: 'Example/gerb_drill_dir'
|
dir: 'Example/gerb_drill_dir'
|
||||||
options:
|
options:
|
||||||
# [string=None] format for a graphical drill map. The valid formats are hpgl, ps, gerber, dxf, svg and pdf.
|
# [dict|string] [hpgl,ps,gerber,dxf,svg,pdf] format for a graphical drill map.
|
||||||
# Not generated unless a format is specified
|
# Not generated unless a format is specified
|
||||||
map: None
|
map:
|
||||||
# [string=None] name of the drill report. Not generated unless a name is specified
|
# [string='pdf'] [hpgl,ps,gerber,dxf,svg,pdf] format for a graphical drill map
|
||||||
report: None
|
type: 'pdf'
|
||||||
|
# [dict|string] name of the drill report. Not generated unless a name is specified
|
||||||
|
report:
|
||||||
|
# [string=''] name of the drill report. Not generated unless a name is specified
|
||||||
|
filename: ''
|
||||||
# [boolean=false] use the auxiliar axis as origin for coordinates
|
# [boolean=false] use the auxiliar axis as origin for coordinates
|
||||||
use_aux_axis_as_origin: false
|
use_aux_axis_as_origin: false
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,15 @@ Class to read KiPlot config files
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from sys import (exit, maxsize)
|
from sys import (exit, maxsize, exc_info)
|
||||||
|
from traceback import print_tb
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from .error import (KiPlotConfigurationError)
|
from .error import (KiPlotConfigurationError)
|
||||||
|
from .gs import GS
|
||||||
from .kiplot import (Layer, load_board)
|
from .kiplot import (Layer, load_board)
|
||||||
from .misc import (NO_YAML_MODULE, EXIT_BAD_CONFIG, EXIT_BAD_ARGS, EXAMPLE_CFG, WONT_OVERWRITE)
|
from .misc import (NO_YAML_MODULE, EXIT_BAD_CONFIG, EXIT_BAD_ARGS, EXAMPLE_CFG, WONT_OVERWRITE)
|
||||||
|
from .optionable import Optionable
|
||||||
from .out_base import BaseOutput
|
from .out_base import BaseOutput
|
||||||
from .pre_base import BasePreFlight
|
from .pre_base import BasePreFlight
|
||||||
# Logger
|
# Logger
|
||||||
|
|
@ -25,6 +28,10 @@ except ImportError: # pragma: no cover
|
||||||
|
|
||||||
|
|
||||||
def config_error(msg):
|
def config_error(msg):
|
||||||
|
if GS.debug_enabled:
|
||||||
|
logger.error('Trace stack:')
|
||||||
|
(type, value, traceback) = exc_info()
|
||||||
|
print_tb(traceback)
|
||||||
logger.error(msg)
|
logger.error(msg)
|
||||||
exit(EXIT_BAD_CONFIG)
|
exit(EXIT_BAD_CONFIG)
|
||||||
|
|
||||||
|
|
@ -225,16 +232,30 @@ def trim(docstring):
|
||||||
return trimmed
|
return trimmed
|
||||||
|
|
||||||
|
|
||||||
def print_output_options(name, cl):
|
def print_output_options(name, cl, indent):
|
||||||
obj = cl('', name, '')
|
ind_str = indent*' '
|
||||||
print(' * Options:')
|
if issubclass(cl, BaseOutput):
|
||||||
|
obj = cl('', name, '')
|
||||||
|
else:
|
||||||
|
obj = cl(name, '')
|
||||||
|
print(ind_str+'* Options:')
|
||||||
num_opts = 0
|
num_opts = 0
|
||||||
for k, v in BaseOutput.get_attrs_gen(obj):
|
for k, v in Optionable.get_attrs_gen(obj):
|
||||||
help = getattr(obj, '_help_'+k)
|
help = getattr(obj, '_help_'+k)
|
||||||
print(' - `{}`: {}.'.format(k, help.rstrip() if help else 'Undocumented'))
|
if help is None:
|
||||||
|
help = 'Undocumented'
|
||||||
|
lines = help.split('\n')
|
||||||
|
preface = ind_str+' - `{}`: '.format(k)
|
||||||
|
clines = len(lines)
|
||||||
|
print('{}{}{}'.format(preface, lines[0].strip(), '.' if clines == 1 else ''))
|
||||||
|
ind_help = len(preface)*' '
|
||||||
|
for ln in range(1, clines):
|
||||||
|
print('{}{}'.format(ind_help+lines[ln].strip(), '.' if ln+1 == clines else ''))
|
||||||
num_opts = num_opts+1
|
num_opts = num_opts+1
|
||||||
|
if isinstance(v, type):
|
||||||
|
print_output_options('', v, indent+4)
|
||||||
if num_opts == 0:
|
if num_opts == 0:
|
||||||
print(' - No available options')
|
print(ind_str+' - No available options')
|
||||||
|
|
||||||
|
|
||||||
def print_one_out_help(details, n, o):
|
def print_one_out_help(details, n, o):
|
||||||
|
|
@ -247,7 +268,7 @@ def print_one_out_help(details, n, o):
|
||||||
print(' * Description: '+lines[1])
|
print(' * Description: '+lines[1])
|
||||||
for ln in range(2, len(lines)):
|
for ln in range(2, len(lines)):
|
||||||
print(' '+lines[ln])
|
print(' '+lines[ln])
|
||||||
print_output_options(n, o)
|
print_output_options(n, o, 2)
|
||||||
else:
|
else:
|
||||||
print('* {} [{}]'.format(lines[0], n))
|
print('* {} [{}]'.format(lines[0], n))
|
||||||
|
|
||||||
|
|
@ -280,6 +301,33 @@ def print_preflights_help():
|
||||||
print('- {}: {}.'.format(n, help.rstrip()))
|
print('- {}: {}.'.format(n, help.rstrip()))
|
||||||
|
|
||||||
|
|
||||||
|
def print_example_options(f, cls, name, indent, po):
|
||||||
|
ind_str = indent*' '
|
||||||
|
if issubclass(cls, BaseOutput):
|
||||||
|
obj = cls('', name, '')
|
||||||
|
else:
|
||||||
|
obj = cls(name, '')
|
||||||
|
if po:
|
||||||
|
obj.read_vals_from_po(po)
|
||||||
|
for k, v in Optionable.get_attrs_gen(obj):
|
||||||
|
help = getattr(obj, '_help_'+k)
|
||||||
|
if help:
|
||||||
|
help_lines = help.split('\n')
|
||||||
|
for hl in help_lines:
|
||||||
|
f.write(ind_str+'# '+hl.strip()+'\n')
|
||||||
|
val = getattr(obj, k)
|
||||||
|
if isinstance(val, str):
|
||||||
|
val = "'{}'".format(val)
|
||||||
|
elif isinstance(val, bool):
|
||||||
|
val = str(val).lower()
|
||||||
|
if isinstance(val, type):
|
||||||
|
f.write(ind_str+'{}:\n'.format(k))
|
||||||
|
print_example_options(f, val, '', indent+2, None)
|
||||||
|
else:
|
||||||
|
f.write(ind_str+'{}: {}\n'.format(k, val))
|
||||||
|
return obj
|
||||||
|
|
||||||
|
|
||||||
def create_example(pcb_file, out_dir, copy_options):
|
def create_example(pcb_file, out_dir, copy_options):
|
||||||
if not os.path.exists(out_dir):
|
if not os.path.exists(out_dir):
|
||||||
os.makedirs(out_dir)
|
os.makedirs(out_dir)
|
||||||
|
|
@ -326,21 +374,7 @@ def create_example(pcb_file, out_dir, copy_options):
|
||||||
f.write(" type: '{}'\n".format(n))
|
f.write(" type: '{}'\n".format(n))
|
||||||
f.write(" dir: 'Example/{}_dir'\n".format(n))
|
f.write(" dir: 'Example/{}_dir'\n".format(n))
|
||||||
f.write(" options:\n")
|
f.write(" options:\n")
|
||||||
obj = cls('', n, '')
|
obj = print_example_options(f, cls, n, 6, po)
|
||||||
if po:
|
|
||||||
obj.read_vals_from_po(po)
|
|
||||||
for k, v in BaseOutput.get_attrs_gen(obj):
|
|
||||||
help = getattr(obj, '_help_'+k)
|
|
||||||
if help:
|
|
||||||
help_lines = help.split('\n')
|
|
||||||
for hl in help_lines:
|
|
||||||
f.write(' # '+hl.strip()+'\n')
|
|
||||||
val = getattr(obj, k)
|
|
||||||
if isinstance(val, str):
|
|
||||||
val = "'{}'".format(val)
|
|
||||||
elif isinstance(val, bool):
|
|
||||||
val = str(val).lower()
|
|
||||||
f.write(' {}: {}\n'.format(k, val))
|
|
||||||
if '_layers' in obj.__dict__:
|
if '_layers' in obj.__dict__:
|
||||||
f.write(' layers:\n')
|
f.write(' layers:\n')
|
||||||
for layer in layers:
|
for layer in layers:
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,127 @@
|
||||||
|
import inspect
|
||||||
|
from re import (compile)
|
||||||
|
from .error import KiPlotConfigurationError
|
||||||
|
from . import log
|
||||||
|
|
||||||
|
logger = log.get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def filter(v):
|
||||||
|
return inspect.isclass(v) or not (callable(v) or isinstance(v, (dict, list)))
|
||||||
|
|
||||||
|
|
||||||
|
class Optionable(object):
|
||||||
|
""" A class to validate and hold configuration options.
|
||||||
|
Is configured from a dict and the collected values are stored in its attributes. """
|
||||||
|
_str_values_re = compile(r"\[string=.*\] \[([^\]]+)\]")
|
||||||
|
_num_range_re = compile(r"\[number=.*\] \[(-?\d+),(-?\d+)\]")
|
||||||
|
|
||||||
|
def __init__(self, name, description):
|
||||||
|
self._name = name
|
||||||
|
self._description = description
|
||||||
|
self._unkown_is_error = True
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _check_str(key, val, doc):
|
||||||
|
if not isinstance(val, str):
|
||||||
|
raise KiPlotConfigurationError("Option `{}` must be a string".format(key))
|
||||||
|
# If the docstring specifies the allowed values in the form [v1,v2...] enforce it
|
||||||
|
m = Optionable._str_values_re.match(doc)
|
||||||
|
if m:
|
||||||
|
vals = m.group(1).split(',')
|
||||||
|
if val not in vals:
|
||||||
|
raise KiPlotConfigurationError("Option `{}` must be any of {} not `{}`".format(key, vals, val))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _check_num(key, val, doc):
|
||||||
|
if not isinstance(val, (int, float)):
|
||||||
|
raise KiPlotConfigurationError("Option `{}` must be a number".format(key))
|
||||||
|
# If the docstring specifies a range in the form [from-to] enforce it
|
||||||
|
m = Optionable._num_range_re.match(doc)
|
||||||
|
if m:
|
||||||
|
min = float(m.group(1))
|
||||||
|
max = float(m.group(2))
|
||||||
|
if val < min or val > max:
|
||||||
|
raise KiPlotConfigurationError("Option `{}` outside its range [{},{}]".format(key, min, max))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _check_bool(key, val):
|
||||||
|
if not isinstance(val, bool):
|
||||||
|
raise KiPlotConfigurationError("Option `{}` must be true/false".format(key))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _typeof(v):
|
||||||
|
if isinstance(v, bool):
|
||||||
|
return 'boolean'
|
||||||
|
if isinstance(v, (int, float)):
|
||||||
|
return 'number'
|
||||||
|
if isinstance(v, str):
|
||||||
|
return 'string'
|
||||||
|
if isinstance(v, dict):
|
||||||
|
return 'dict'
|
||||||
|
if isinstance(v, list):
|
||||||
|
return 'list'
|
||||||
|
return 'None'
|
||||||
|
|
||||||
|
def _perform_config_mapping(self):
|
||||||
|
""" Map the options to class attributes """
|
||||||
|
attrs = Optionable.get_attrs_for(self)
|
||||||
|
for k, v in self._options.items():
|
||||||
|
# Map known attributes and avoid mapping private ones
|
||||||
|
if (k[0] == '_') or (k not in attrs):
|
||||||
|
if self._unkown_is_error:
|
||||||
|
raise KiPlotConfigurationError("Unknown option `{}`".format(k))
|
||||||
|
logger.warning("Unknown option `{}`".format(k))
|
||||||
|
continue
|
||||||
|
# Check the data type
|
||||||
|
cur_val = getattr(self, k)
|
||||||
|
cur_doc = getattr(self, '_help_'+k).lstrip()
|
||||||
|
if isinstance(cur_val, bool):
|
||||||
|
Optionable._check_bool(k, v)
|
||||||
|
elif isinstance(cur_val, (int, float)):
|
||||||
|
Optionable._check_num(k, v, cur_doc)
|
||||||
|
elif isinstance(cur_val, str):
|
||||||
|
Optionable._check_str(k, v, cur_doc)
|
||||||
|
elif isinstance(cur_val, type):
|
||||||
|
# A class, so we need more information i.e. "[dict|string]"
|
||||||
|
if cur_doc[0] == '[':
|
||||||
|
# Separate the valid types for this key
|
||||||
|
valid = cur_doc[1:].split(']')[0].split('|')
|
||||||
|
# Get the type used by the user as a string
|
||||||
|
v_type = Optionable._typeof(v)
|
||||||
|
if v_type not in valid:
|
||||||
|
# Not a valid type for this key
|
||||||
|
if v_type == 'None':
|
||||||
|
raise KiPlotConfigurationError("Empty option `{}`".format(k))
|
||||||
|
raise KiPlotConfigurationError("Option `{}` must be any of {} not `{}`".format(k, valid, v_type))
|
||||||
|
# We know the type matches, now apply validations
|
||||||
|
if isinstance(v, (int, float)) and not isinstance(v, bool):
|
||||||
|
# Note: booleans are also instance of int
|
||||||
|
Optionable._check_num(k, v, cur_doc)
|
||||||
|
elif isinstance(v, str):
|
||||||
|
Optionable._check_str(k, v, cur_doc)
|
||||||
|
elif isinstance(v, dict):
|
||||||
|
# Dicts are solved using Optionable classes
|
||||||
|
new_val = v
|
||||||
|
# Create an object for the valid class
|
||||||
|
v = cur_val(k, cur_doc)
|
||||||
|
# Delegate the validation to the object
|
||||||
|
v.config(new_val)
|
||||||
|
# Seems to be ok, map it
|
||||||
|
setattr(self, k, v)
|
||||||
|
|
||||||
|
def config(self, options):
|
||||||
|
self._options = options
|
||||||
|
if options:
|
||||||
|
self._perform_config_mapping()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_attrs_for(obj):
|
||||||
|
""" Returns all attributes """
|
||||||
|
return dict(inspect.getmembers(obj, filter))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_attrs_gen(obj):
|
||||||
|
""" Returns a (key, val) iterator on public attributes """
|
||||||
|
attrs = Optionable.get_attrs_for(obj)
|
||||||
|
return ((k, v) for k, v in attrs.items() if k[0] != '_')
|
||||||
|
|
@ -1,14 +1,30 @@
|
||||||
import os
|
import os
|
||||||
from pcbnew import (PLOT_FORMAT_HPGL, PLOT_FORMAT_POST, PLOT_FORMAT_GERBER, PLOT_FORMAT_DXF, PLOT_FORMAT_SVG,
|
from pcbnew import (PLOT_FORMAT_HPGL, PLOT_FORMAT_POST, PLOT_FORMAT_GERBER, PLOT_FORMAT_DXF, PLOT_FORMAT_SVG,
|
||||||
PLOT_FORMAT_PDF, wxPoint)
|
PLOT_FORMAT_PDF, wxPoint)
|
||||||
|
from .optionable import Optionable
|
||||||
from .out_base import BaseOutput
|
from .out_base import BaseOutput
|
||||||
from .error import KiPlotConfigurationError
|
|
||||||
from kiplot.macros import macros, document # noqa: F401
|
from kiplot.macros import macros, document # noqa: F401
|
||||||
from . import log
|
from . import log
|
||||||
|
|
||||||
logger = log.get_logger(__name__)
|
logger = log.get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class DrillMap(Optionable):
|
||||||
|
def __init__(self, name, description):
|
||||||
|
super(DrillMap, self).__init__(name, description)
|
||||||
|
with document:
|
||||||
|
self.type = 'pdf'
|
||||||
|
""" [hpgl,ps,gerber,dxf,svg,pdf] format for a graphical drill map """
|
||||||
|
|
||||||
|
|
||||||
|
class DrillReport(Optionable):
|
||||||
|
def __init__(self, name, description):
|
||||||
|
super(DrillReport, self).__init__(name, description)
|
||||||
|
with document:
|
||||||
|
self.filename = ''
|
||||||
|
""" name of the drill report. Not generated unless a name is specified """
|
||||||
|
|
||||||
|
|
||||||
class AnyDrill(BaseOutput):
|
class AnyDrill(BaseOutput):
|
||||||
def __init__(self, name, type, description):
|
def __init__(self, name, type, description):
|
||||||
super(AnyDrill, self).__init__(name, type, description)
|
super(AnyDrill, self).__init__(name, type, description)
|
||||||
|
|
@ -16,11 +32,11 @@ class AnyDrill(BaseOutput):
|
||||||
with document:
|
with document:
|
||||||
self.use_aux_axis_as_origin = False
|
self.use_aux_axis_as_origin = False
|
||||||
""" use the auxiliar axis as origin for coordinates """
|
""" use the auxiliar axis as origin for coordinates """
|
||||||
self._map = None
|
self.map = DrillMap
|
||||||
""" [string=None] format for a graphical drill map. The valid formats are hpgl, ps, gerber, dxf, svg and pdf.
|
""" [dict|string] [hpgl,ps,gerber,dxf,svg,pdf] format for a graphical drill map.
|
||||||
Not generated unless a format is specified """
|
Not generated unless a format is specified """
|
||||||
self._report = None
|
self.report = DrillReport
|
||||||
""" [string=None] name of the drill report. Not generated unless a name is specified """ # pragma: no cover
|
""" [dict|string] name of the drill report. Not generated unless a name is specified """ # pragma: no cover
|
||||||
# Mappings to KiCad values
|
# Mappings to KiCad values
|
||||||
self._map_map = {
|
self._map_map = {
|
||||||
'hpgl': PLOT_FORMAT_HPGL,
|
'hpgl': PLOT_FORMAT_HPGL,
|
||||||
|
|
@ -31,60 +47,20 @@ class AnyDrill(BaseOutput):
|
||||||
'pdf': PLOT_FORMAT_PDF
|
'pdf': PLOT_FORMAT_PDF
|
||||||
}
|
}
|
||||||
|
|
||||||
@property
|
|
||||||
def map(self):
|
|
||||||
return self._map
|
|
||||||
|
|
||||||
@map.setter
|
|
||||||
def map(self, val):
|
|
||||||
# In the original "version 1" of the format this is a dict with one key named `type`.
|
|
||||||
# Currently we spect a string, but we support the old mechanism.
|
|
||||||
if val is None:
|
|
||||||
raise KiPlotConfigurationError("Empty drill `map` section")
|
|
||||||
# Setting from a dict
|
|
||||||
if isinstance(val, dict):
|
|
||||||
if 'type' not in val:
|
|
||||||
raise KiPlotConfigurationError("drill `map` must contain a `type`")
|
|
||||||
type = val['type']
|
|
||||||
if not isinstance(type, str):
|
|
||||||
raise KiPlotConfigurationError("drill `map` `type` must be a string")
|
|
||||||
val = type
|
|
||||||
elif not isinstance(val, str):
|
|
||||||
raise KiPlotConfigurationError("drill `map` must be a string")
|
|
||||||
if val == 'None':
|
|
||||||
val = None
|
|
||||||
elif val not in self._map_map:
|
|
||||||
raise KiPlotConfigurationError("Unknown drill `map` `type`: {}".format(val))
|
|
||||||
self._map = val
|
|
||||||
|
|
||||||
@property
|
|
||||||
def report(self):
|
|
||||||
return self._report
|
|
||||||
|
|
||||||
@report.setter
|
|
||||||
def report(self, val):
|
|
||||||
# In the original "version 1" of the format this is a dict with one key named `filename`.
|
|
||||||
# Currently we spect a string, but we support the old mechanism.
|
|
||||||
if val is None:
|
|
||||||
raise KiPlotConfigurationError("Empty drill `report` section")
|
|
||||||
# Setting from a dict
|
|
||||||
if isinstance(val, dict):
|
|
||||||
if 'filename' not in val:
|
|
||||||
raise KiPlotConfigurationError("drill `report` must contain a `filename`")
|
|
||||||
filename = val['filename']
|
|
||||||
if not isinstance(filename, str):
|
|
||||||
raise KiPlotConfigurationError("drill `report` `filename` must be a string")
|
|
||||||
val = filename
|
|
||||||
elif not isinstance(val, str):
|
|
||||||
raise KiPlotConfigurationError("drill `report` must be a string")
|
|
||||||
if val == 'None':
|
|
||||||
val = None
|
|
||||||
self._report = val
|
|
||||||
|
|
||||||
def config(self, outdir, options, layers):
|
def config(self, outdir, options, layers):
|
||||||
super().config(outdir, options, layers)
|
super().config(outdir, options, layers)
|
||||||
if self._map:
|
# Solve the map for both cases
|
||||||
self._map = self._map_map[self._map]
|
if isinstance(self.map, str):
|
||||||
|
self.map = self._map_map[self.map]
|
||||||
|
elif isinstance(self.map, DrillMap):
|
||||||
|
self.map = self._map_map[self.map.type]
|
||||||
|
else:
|
||||||
|
self.map = None
|
||||||
|
# Solve the report for both cases
|
||||||
|
if isinstance(self.report, DrillReport):
|
||||||
|
self.report = self.report.filename
|
||||||
|
elif not isinstance(self.report, str):
|
||||||
|
self.report = None
|
||||||
|
|
||||||
def run(self, output_dir, board):
|
def run(self, output_dir, board):
|
||||||
# dialog_gendrill.cpp:357
|
# dialog_gendrill.cpp:357
|
||||||
|
|
@ -95,14 +71,14 @@ class AnyDrill(BaseOutput):
|
||||||
drill_writer = self._configure_writer(board, offset)
|
drill_writer = self._configure_writer(board, offset)
|
||||||
|
|
||||||
logger.debug("Generating drill files in "+output_dir)
|
logger.debug("Generating drill files in "+output_dir)
|
||||||
gen_map = self._map is not None
|
gen_map = self.map is not None
|
||||||
if gen_map:
|
if gen_map:
|
||||||
drill_writer.SetMapFileFormat(self._map)
|
drill_writer.SetMapFileFormat(self.map)
|
||||||
logger.debug("Generating drill map type {} in {}".format(self._map, output_dir))
|
logger.debug("Generating drill map type {} in {}".format(self.map, output_dir))
|
||||||
# We always generate the drill file
|
# We always generate the drill file
|
||||||
drill_writer.CreateDrillandMapFilesSet(output_dir, True, gen_map)
|
drill_writer.CreateDrillandMapFilesSet(output_dir, True, gen_map)
|
||||||
|
|
||||||
if self._report is not None:
|
if self.report:
|
||||||
drill_report_file = os.path.join(output_dir, self._report)
|
drill_report_file = os.path.join(output_dir, self.report)
|
||||||
logger.debug("Generating drill report: "+drill_report_file)
|
logger.debug("Generating drill report: "+drill_report_file)
|
||||||
drill_writer.GenDrillReportFile(drill_report_file)
|
drill_writer.GenDrillReportFile(drill_report_file)
|
||||||
|
|
|
||||||
|
|
@ -1,83 +1,22 @@
|
||||||
import inspect
|
from .optionable import Optionable
|
||||||
from re import (compile)
|
|
||||||
from .error import KiPlotConfigurationError
|
|
||||||
from . import log
|
from . import log
|
||||||
|
|
||||||
logger = log.get_logger(__name__)
|
logger = log.get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def filter(v):
|
class BaseOutput(Optionable):
|
||||||
return not (callable(v) or inspect.isclass(v) or isinstance(v, (dict, list)))
|
|
||||||
|
|
||||||
|
|
||||||
class BaseOutput(object):
|
|
||||||
_registered = {}
|
_registered = {}
|
||||||
|
|
||||||
def __init__(self, name, type, description):
|
def __init__(self, name, type, description):
|
||||||
self._name = name
|
super(BaseOutput, self).__init__(name, description)
|
||||||
self._type = type
|
self._type = type
|
||||||
self._description = description
|
|
||||||
self._sch_related = False
|
self._sch_related = False
|
||||||
|
self._unkown_is_error = False
|
||||||
def _perform_config_mapping(self):
|
|
||||||
""" Map the options to class attributes """
|
|
||||||
attrs = BaseOutput.get_attrs_for(self)
|
|
||||||
num_range_re = compile(r"\[number=.*\] \[(-?\d+),(-?\d+)\]")
|
|
||||||
str_values_re = compile(r"\[string=.*\] \[([^\]]+)\]")
|
|
||||||
for k, v in self._options.items():
|
|
||||||
# Map known attributes and avoid mapping private ones
|
|
||||||
if (k[0] == '_') or (k not in attrs):
|
|
||||||
# raise KiPlotConfigurationError("Unknown option `{}` {}".format(k, attrs))
|
|
||||||
logger.warning("Unknown option `{}`".format(k))
|
|
||||||
continue
|
|
||||||
# Check the data type
|
|
||||||
cur_val = getattr(self, k)
|
|
||||||
cur_doc = getattr(self, '_help_'+k).lstrip()
|
|
||||||
if isinstance(cur_val, bool):
|
|
||||||
if not isinstance(v, bool):
|
|
||||||
raise KiPlotConfigurationError("Option `{}` must be true/false".format(k))
|
|
||||||
elif isinstance(cur_val, (int, float)):
|
|
||||||
# Note: booleans are also instance of int
|
|
||||||
if not isinstance(v, (int, float)):
|
|
||||||
raise KiPlotConfigurationError("Option `{}` must be a number".format(k))
|
|
||||||
# If the docstring specifies a range in the form [from-to] enforce it
|
|
||||||
m = num_range_re.match(cur_doc)
|
|
||||||
if m:
|
|
||||||
min = float(m.group(1))
|
|
||||||
max = float(m.group(2))
|
|
||||||
if v < min or v > max:
|
|
||||||
raise KiPlotConfigurationError("Option `{}` outside its range [{},{}]".format(k, min, max))
|
|
||||||
elif isinstance(cur_val, str):
|
|
||||||
if not isinstance(v, str):
|
|
||||||
raise KiPlotConfigurationError("Option `{}` must be a string".format(k))
|
|
||||||
# If the docstring specifies the allowed values in the form [v1,v2...] enforce it
|
|
||||||
m = str_values_re.match(cur_doc)
|
|
||||||
if m:
|
|
||||||
vals = m.group(1).split(',')
|
|
||||||
if v not in vals:
|
|
||||||
raise KiPlotConfigurationError("Option `{}` must be any of {}".format(k, vals))
|
|
||||||
elif isinstance(v, list):
|
|
||||||
raise KiPlotConfigurationError("list not yet supported for `{}`".format(k))
|
|
||||||
# Seems to be ok, map it
|
|
||||||
setattr(self, k, v)
|
|
||||||
|
|
||||||
def config(self, outdir, options, layers):
|
def config(self, outdir, options, layers):
|
||||||
self._outdir = outdir
|
self._outdir = outdir
|
||||||
self._options = options
|
|
||||||
self._layers = layers
|
self._layers = layers
|
||||||
if options:
|
super(BaseOutput, self).config(options)
|
||||||
self._perform_config_mapping()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_attrs_for(obj):
|
|
||||||
""" Returns all attributes """
|
|
||||||
return dict(inspect.getmembers(obj, filter))
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_attrs_gen(obj):
|
|
||||||
""" Returns a (key, val) iterator on public attributes """
|
|
||||||
attrs = BaseOutput.get_attrs_for(obj)
|
|
||||||
return ((k, v) for k, v in attrs.items() if k[0] != '_')
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def attr2longopt(attr):
|
def attr2longopt(attr):
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ from subprocess import (check_output, STDOUT, CalledProcessError)
|
||||||
from .misc import (CMD_IBOM, URL_IBOM, BOM_ERROR)
|
from .misc import (CMD_IBOM, URL_IBOM, BOM_ERROR)
|
||||||
from .gs import (GS)
|
from .gs import (GS)
|
||||||
from .kiplot import (check_script)
|
from .kiplot import (check_script)
|
||||||
|
from .optionable import Optionable
|
||||||
from kiplot.macros import macros, document, output_class # noqa: F401
|
from kiplot.macros import macros, document, output_class # noqa: F401
|
||||||
from . import log
|
from . import log
|
||||||
|
|
||||||
|
|
@ -84,7 +85,7 @@ class IBoM(BaseOutput): # noqa: F821
|
||||||
os.environ['INTERACTIVE_HTML_BOM_NO_DISPLAY'] = ''
|
os.environ['INTERACTIVE_HTML_BOM_NO_DISPLAY'] = ''
|
||||||
cmd = [CMD_IBOM, GS.pcb_file, '--dest-dir', output_dir, '--no-browser', ]
|
cmd = [CMD_IBOM, GS.pcb_file, '--dest-dir', output_dir, '--no-browser', ]
|
||||||
# Convert attributes into options
|
# Convert attributes into options
|
||||||
for k, v in BaseOutput.get_attrs_gen(self): # noqa: F821
|
for k, v in Optionable.get_attrs_gen(self):
|
||||||
if not v:
|
if not v:
|
||||||
continue
|
continue
|
||||||
cmd.append(BaseOutput.attr2longopt(k)) # noqa: F821
|
cmd.append(BaseOutput.attr2longopt(k)) # noqa: F821
|
||||||
|
|
|
||||||
|
|
@ -88,63 +88,63 @@ def test_wrong_version_3():
|
||||||
def test_drill_map_no_type_1():
|
def test_drill_map_no_type_1():
|
||||||
ctx = context.TestContext('ErrorDrillMapNoType1', '3Rs', 'error_drill_map_no_type', None)
|
ctx = context.TestContext('ErrorDrillMapNoType1', '3Rs', 'error_drill_map_no_type', None)
|
||||||
ctx.run(EXIT_BAD_CONFIG)
|
ctx.run(EXIT_BAD_CONFIG)
|
||||||
assert ctx.search_err("Empty drill `map` section")
|
assert ctx.search_err("Empty option .?map.?")
|
||||||
ctx.clean_up()
|
ctx.clean_up()
|
||||||
|
|
||||||
|
|
||||||
def test_drill_map_no_type_2():
|
def test_drill_map_no_type_2():
|
||||||
ctx = context.TestContext('ErrorDrillMapNoType2', '3Rs', 'error_drill_map_no_type_2', None)
|
ctx = context.TestContext('ErrorDrillMapNoType2', '3Rs', 'error_drill_map_no_type_2', None)
|
||||||
ctx.run(EXIT_BAD_CONFIG)
|
ctx.run(EXIT_BAD_CONFIG)
|
||||||
assert ctx.search_err("drill `map` must contain a `type`")
|
assert ctx.search_err("Unknown option .?types.?")
|
||||||
ctx.clean_up()
|
ctx.clean_up()
|
||||||
|
|
||||||
|
|
||||||
def test_drill_map_wrong_type_1():
|
def test_drill_map_wrong_type_1():
|
||||||
ctx = context.TestContext('ErrorDrillMapWrongType1', '3Rs', 'error_drill_map_wrong_type', None)
|
ctx = context.TestContext('ErrorDrillMapWrongType1', '3Rs', 'error_drill_map_wrong_type', None)
|
||||||
ctx.run(EXIT_BAD_CONFIG)
|
ctx.run(EXIT_BAD_CONFIG)
|
||||||
assert ctx.search_err("Unknown drill `map` `type`: bogus")
|
assert ctx.search_err("Option .?type.? must be any of")
|
||||||
ctx.clean_up()
|
ctx.clean_up()
|
||||||
|
|
||||||
|
|
||||||
def test_drill_map_wrong_type_2():
|
def test_drill_map_wrong_type_2():
|
||||||
ctx = context.TestContext('ErrorDrillMapWrongType2', '3Rs', 'error_drill_map_wrong_type_2', None)
|
ctx = context.TestContext('ErrorDrillMapWrongType2', '3Rs', 'error_drill_map_wrong_type_2', None)
|
||||||
ctx.run(EXIT_BAD_CONFIG)
|
ctx.run(EXIT_BAD_CONFIG)
|
||||||
assert ctx.search_err("drill `map` `type` must be a string")
|
assert ctx.search_err("Option .?type.? must be a string")
|
||||||
ctx.clean_up()
|
ctx.clean_up()
|
||||||
|
|
||||||
|
|
||||||
def test_drill_map_wrong_type_3():
|
def test_drill_map_wrong_type_3():
|
||||||
ctx = context.TestContext('ErrorDrillMapWrongType3', '3Rs', 'error_drill_map_wrong_type_3', None)
|
ctx = context.TestContext('ErrorDrillMapWrongType3', '3Rs', 'error_drill_map_wrong_type_3', None)
|
||||||
ctx.run(EXIT_BAD_CONFIG)
|
ctx.run(EXIT_BAD_CONFIG)
|
||||||
assert ctx.search_err("drill `map` must be a string")
|
assert ctx.search_err("Option .?map.? must be any of")
|
||||||
ctx.clean_up()
|
ctx.clean_up()
|
||||||
|
|
||||||
|
|
||||||
def test_drill_report_no_type_1():
|
def test_drill_report_no_type_1():
|
||||||
ctx = context.TestContext('ErrorDrillReportNoType1', '3Rs', 'error_drill_report_no_type', None)
|
ctx = context.TestContext('ErrorDrillReportNoType1', '3Rs', 'error_drill_report_no_type', None)
|
||||||
ctx.run(EXIT_BAD_CONFIG)
|
ctx.run(EXIT_BAD_CONFIG)
|
||||||
assert ctx.search_err("Empty drill `report` section")
|
assert ctx.search_err("Empty option .?report.?")
|
||||||
ctx.clean_up()
|
ctx.clean_up()
|
||||||
|
|
||||||
|
|
||||||
def test_drill_report_no_type_2():
|
def test_drill_report_no_type_2():
|
||||||
ctx = context.TestContext('ErrorDrillReportNoType2', '3Rs', 'error_drill_report_no_type_2', None)
|
ctx = context.TestContext('ErrorDrillReportNoType2', '3Rs', 'error_drill_report_no_type_2', None)
|
||||||
ctx.run(EXIT_BAD_CONFIG)
|
ctx.run(EXIT_BAD_CONFIG)
|
||||||
assert ctx.search_err("drill `report` must contain a `filename`")
|
assert ctx.search_err("Unknown option .?filenames.?")
|
||||||
ctx.clean_up()
|
ctx.clean_up()
|
||||||
|
|
||||||
|
|
||||||
def test_drill_report_wrong_type_2():
|
def test_drill_report_wrong_type_2():
|
||||||
ctx = context.TestContext('ErrorDrillReportWrongType2', '3Rs', 'error_drill_report_wrong_type_2', None)
|
ctx = context.TestContext('ErrorDrillReportWrongType2', '3Rs', 'error_drill_report_wrong_type_2', None)
|
||||||
ctx.run(EXIT_BAD_CONFIG)
|
ctx.run(EXIT_BAD_CONFIG)
|
||||||
assert ctx.search_err("drill `report` `filename` must be a string")
|
assert ctx.search_err("Option .?filename.? must be a string")
|
||||||
ctx.clean_up()
|
ctx.clean_up()
|
||||||
|
|
||||||
|
|
||||||
def test_drill_report_wrong_type_3():
|
def test_drill_report_wrong_type_3():
|
||||||
ctx = context.TestContext('ErrorDrillReportWrongType3', '3Rs', 'error_drill_report_wrong_type_3', None)
|
ctx = context.TestContext('ErrorDrillReportWrongType3', '3Rs', 'error_drill_report_wrong_type_3', None)
|
||||||
ctx.run(EXIT_BAD_CONFIG)
|
ctx.run(EXIT_BAD_CONFIG)
|
||||||
assert ctx.search_err("drill `report` must be a string")
|
assert ctx.search_err("Option .?report.? must be any of")
|
||||||
ctx.clean_up()
|
ctx.clean_up()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -222,7 +222,7 @@ def test_out_unknown_attr():
|
||||||
def test_no_layers():
|
def test_no_layers():
|
||||||
ctx = context.TestContext('ErrorNoLayers', '3Rs', 'error_no_layers', None)
|
ctx = context.TestContext('ErrorNoLayers', '3Rs', 'error_no_layers', None)
|
||||||
ctx.run(EXIT_BAD_CONFIG)
|
ctx.run(EXIT_BAD_CONFIG)
|
||||||
assert ctx.search_err("Missing `layers` list")
|
assert ctx.search_err("Missing .?layers.? list")
|
||||||
ctx.clean_up()
|
ctx.clean_up()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -236,7 +236,7 @@ def test_error_step_origin():
|
||||||
def test_error_step_min_distance():
|
def test_error_step_min_distance():
|
||||||
ctx = context.TestContext('ErrorStepMinDistance', 'bom', 'error_step_min_distance', None)
|
ctx = context.TestContext('ErrorStepMinDistance', 'bom', 'error_step_min_distance', None)
|
||||||
ctx.run(EXIT_BAD_CONFIG)
|
ctx.run(EXIT_BAD_CONFIG)
|
||||||
assert ctx.search_err("`min_distance` must be a number")
|
assert ctx.search_err(".?min_distance.? must be a number")
|
||||||
ctx.clean_up()
|
ctx.clean_up()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue