Changed: The `%d/%sd/%bd` expansion patterns are now affected by the global `date_format`.

- Can be disabled using `date_reformat: false`.
- Related to #121
This commit is contained in:
Diego Capusotto 2021-12-15 17:15:49 -03:00
parent 631cef7fd9
commit 3eb82bc86d
10 changed files with 118 additions and 51 deletions

View File

@ -63,6 +63,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
a context not related to variants. I.e. when a `compress` target expands
`%v`.
- Now you get an error when defining two outputs with the same name.
- The `%d/%sd/%bd` expansion patterns are now affected by the global `date_format`.
Can be disabled using `date_reformat: false`. (#121)
### Fixed
- Position files now defaults to use the auxiliar origin as KiCad.

View File

@ -312,7 +312,10 @@ The behavior of these patterns isn't fully defined in this case and the results
#### Date format option
* The **%d**, **%sd** and **%bd** patterns use the date and time from the PCB and schematic. When abscent they use the file timestamp. The `date_time_format` global option controls the format used.
* The **%d**, **%sd** and **%bd** patterns use the date and time from the PCB and schematic.
When abscent they use the file timestamp, and the `date_time_format` global option controls the format used.
When available, and in ISO format, the `date_format` controls the format used.
You can disable this reformatting assigning `false` to the `date_reformat` option.
* The **%D** format is controlled by the `date_format` global option.
* The **%T** format is controlled by the `time_format` global option.

View File

@ -254,7 +254,10 @@ The behavior of these patterns isn't fully defined in this case and the results
#### Date format option
* The **%d**, **%sd** and **%bd** patterns use the date and time from the PCB and schematic. When abscent they use the file timestamp. The `date_time_format` global option controls the format used.
* The **%d**, **%sd** and **%bd** patterns use the date and time from the PCB and schematic.
When abscent they use the file timestamp, and the `date_time_format` global option controls the format used.
When available, and in ISO format, the `date_format` controls the format used.
You can disable this reformatting assigning `false` to the `date_reformat` option.
* The **%D** format is controlled by the `date_format` global option.
* The **%T** format is controlled by the `time_format` global option.

View File

@ -31,9 +31,14 @@ class Globals(FiltersOptions):
self.date_time_format = '%Y-%m-%d_%H-%M-%S'
""" Format used for the PCB and schematic date when using the file timestamp. Uses the `strftime` format """
self.date_format = '%Y-%m-%d'
""" Format used for the day we started the script. Uses the `strftime` format """
""" Format used for the day we started the script.
Is also used for the PCB/SCH date formatting when `time_reformat` is enabled (default behavior).
Uses the `strftime` format """
self.time_format = '%H-%M-%S'
""" Format used for the time we started the script. Uses the `strftime` format """
self.time_reformat = True
""" Tries to reformat the PCB/SCH date using the `date_format`.
This assumes you let KiCad fill this value and hence the time is in ISO format (YY-MM-DD) """
self.set_doc('filters', " [list(dict)] KiBot warnings to be ignored ")
self._filter_what = 'KiBot warnings'
self._unkown_is_error = True
@ -41,7 +46,7 @@ class Globals(FiltersOptions):
@staticmethod
def set_global(current, new_val, opt):
if current:
if current is not None:
logger.info('Using command line value `{}` for global option `{}`'.format(current, opt))
return current
if new_val:
@ -56,6 +61,7 @@ class Globals(FiltersOptions):
GS.global_date_time_format = self.set_global(GS.global_date_time_format, self.date_time_format, 'date_time_format')
GS.global_date_format = self.set_global(GS.global_date_format, self.date_format, 'date_format')
GS.global_time_format = self.set_global(GS.global_time_format, self.time_format, 'time_format')
GS.global_time_reformat = self.set_global(GS.global_time_reformat, self.time_reformat, 'time_reformat')
GS.global_kiauto_wait_start = self.set_global(GS.global_kiauto_wait_start, self.kiauto_wait_start, 'kiauto_wait_start')
if GS.global_kiauto_wait_start and int(GS.global_kiauto_wait_start) != GS.global_kiauto_wait_start:
GS.global_kiauto_wait_start = int(GS.global_kiauto_wait_start)

View File

@ -4,10 +4,10 @@
# License: GPL-3.0
# Project: KiBot (formerly KiPlot)
import os
from datetime import datetime
from datetime import datetime, date
from sys import exit
from .misc import (EXIT_BAD_ARGS)
from .log import (get_logger)
from .misc import EXIT_BAD_ARGS, W_DATEFORMAT
from .log import get_logger
logger = get_logger(__name__)
@ -87,6 +87,7 @@ class GS(object):
global_date_time_format = None
global_date_format = None
global_time_format = None
global_time_reformat = None
test_boolean = True
@staticmethod
@ -121,6 +122,20 @@ class GS(object):
GS.sch_com3 = GS.sch.comment3
GS.sch_com4 = GS.sch.comment4
@staticmethod
def format_date(d, fname, what):
if not d:
return datetime.fromtimestamp(os.path.getmtime(fname)).strftime(GS.global_date_time_format)
elif GS.global_time_reformat:
try:
dt = date.fromisoformat(d)
except ValueError as e:
logger.warning(W_DATEFORMAT+"Trying to reformat {} time, but not in ISO format ({})".format(what, d))
logger.warning(W_DATEFORMAT+"Problem: {}".format(e))
return d
return dt.strftime(GS.global_date_format)
return d
@staticmethod
def load_pcb_title_block():
if GS.pcb_title is not None:
@ -131,10 +146,7 @@ class GS(object):
GS.pcb_comp = ''
# This is based on InterativeHtmlBom expansion
title_block = GS.board.GetTitleBlock()
GS.pcb_date = title_block.GetDate()
if not GS.pcb_date:
file_mtime = os.path.getmtime(GS.pcb_file)
GS.pcb_date = datetime.fromtimestamp(file_mtime).strftime(GS.global_date_time_format)
GS.pcb_date = GS.format_date(title_block.GetDate(), GS.pcb_file, 'PCB')
GS.pcb_title = title_block.GetTitle()
if not GS.pcb_title:
GS.pcb_title = GS.pcb_basename

View File

@ -11,7 +11,6 @@ Currently oriented to collect the components for the BoM.
# Encapsulate file/line
import re
import os
from datetime import datetime
from copy import deepcopy
from collections import OrderedDict
from .config import KiConf, un_quote
@ -961,7 +960,10 @@ class SchematicComponent(object):
self.footprint_lib = res[0]
self.footprint = res[1]
else:
raise SchFileError('Footprint with more than one colon', f.value, fr)
if fr:
raise SchFileError('Footprint with more than one colon', f.value, fr)
else:
raise SchError('Footprint with more than one colon (`{}`)'.format(f.value))
basic += 1
elif f.number == 3:
self.datasheet = f.value
@ -1498,9 +1500,7 @@ class Schematic(object):
# Load the title block
self._get_title_block(f)
# Fill in some missing info
if not self.date:
file_mtime = os.path.getmtime(fname)
self.date = datetime.fromtimestamp(file_mtime).strftime(GS.global_date_time_format)
self.date = GS.format_date(self.date, fname, 'SCH')
if not self.title:
self.title = os.path.splitext(os.path.basename(fname))[0]
logger.debug("SCH title: `{}`".format(self.title))

View File

@ -212,6 +212,7 @@ W_NEEDSPCB = '(W071) '
W_NOGLOBALS = '(W072) '
W_EMPTREP = '(W073) '
W_BADCHARS = '(W074) '
W_DATEFORMAT = '(W075) '
class Rect(object):

View File

@ -20,6 +20,11 @@ def filter(v):
return inspect.isclass(v) or not (callable(v) or isinstance(v, (dict, list)))
def _cl(text):
""" Eliminates dangerous characters from the text """
return re.sub(r'[\\\/\?%\*:|"<>]', '_', text)
class Optionable(object):
""" A class to validate and hold configuration outputs/options.
Is configured from a dict and the collected values are stored in its attributes. """
@ -219,40 +224,40 @@ class Optionable(object):
""" Expansions common to the PCB and Schematic """
# PCB expansions, explicit
if GS.board and '%b' in name:
name = name.replace('%bc', GS.pcb_comp)
name = name.replace('%bd', GS.pcb_date)
name = name.replace('%bc', _cl(GS.pcb_comp))
name = name.replace('%bd', _cl(GS.pcb_date))
name = name.replace('%bF', GS.pcb_no_ext)
name = name.replace('%bf', GS.pcb_basename)
name = name.replace('%bp', GS.pcb_title)
name = name.replace('%br', GS.pcb_rev)
name = name.replace('%bC1', GS.pcb_com1)
name = name.replace('%bC2', GS.pcb_com2)
name = name.replace('%bC3', GS.pcb_com3)
name = name.replace('%bC4', GS.pcb_com4)
name = name.replace('%bp', _cl(GS.pcb_title))
name = name.replace('%br', _cl(GS.pcb_rev))
name = name.replace('%bC1', _cl(GS.pcb_com1))
name = name.replace('%bC2', _cl(GS.pcb_com2))
name = name.replace('%bC3', _cl(GS.pcb_com3))
name = name.replace('%bC4', _cl(GS.pcb_com4))
if GS.solved_global_variant:
name = name.replace('%g', GS.solved_global_variant.file_id)
name = name.replace('%G', GS.solved_global_variant.name)
# Schematic expansions, explicit
if GS.sch and '%s' in name:
name = name.replace('%sc', GS.sch_comp)
name = name.replace('%sd', GS.sch_date)
name = name.replace('%sc', _cl(GS.sch_comp))
name = name.replace('%sd', _cl(GS.sch_date))
name = name.replace('%sF', GS.sch_no_ext)
name = name.replace('%sf', GS.sch_basename)
name = name.replace('%sp', GS.sch_title)
name = name.replace('%sr', GS.sch_rev)
name = name.replace('%sC1', GS.sch_com1)
name = name.replace('%sC2', GS.sch_com2)
name = name.replace('%sC3', GS.sch_com3)
name = name.replace('%sC4', GS.sch_com4)
name = name.replace('%sp', _cl(GS.sch_title))
name = name.replace('%sr', _cl(GS.sch_rev))
name = name.replace('%sC1', _cl(GS.sch_com1))
name = name.replace('%sC2', _cl(GS.sch_com2))
name = name.replace('%sC3', _cl(GS.sch_com3))
name = name.replace('%sC4', _cl(GS.sch_com4))
name = name.replace('%D', GS.n.strftime(GS.global_date_format))
name = name.replace('%T', GS.n.strftime(GS.global_time_format))
if self:
name = name.replace('%i', self._expand_id)
name = name.replace('%v', self._find_variant())
name = name.replace('%V', self._find_variant_name())
name = name.replace('%v', _cl(self._find_variant()))
name = name.replace('%V', _cl(self._find_variant_name()))
name = name.replace('%x', self._expand_ext)
if parent and hasattr(parent, 'output_id'):
name = name.replace('%I', parent.output_id)
name = name.replace('%I', _cl(parent.output_id))
return name
def expand_filename_both(self, name, is_sch=True):
@ -281,27 +286,27 @@ class Optionable(object):
# This member can be called with a preflight object
name = Optionable.expand_filename_common(self, name, parent)
if GS.board and do_pcb:
name = name.replace('%c', GS.pcb_comp)
name = name.replace('%d', GS.pcb_date)
name = name.replace('%c', _cl(GS.pcb_comp))
name = name.replace('%d', _cl(GS.pcb_date))
name = name.replace('%F', GS.pcb_no_ext)
name = name.replace('%f', GS.pcb_basename)
name = name.replace('%p', GS.pcb_title)
name = name.replace('%r', GS.pcb_rev)
name = name.replace('%C1', GS.pcb_com1)
name = name.replace('%C2', GS.pcb_com2)
name = name.replace('%C3', GS.pcb_com3)
name = name.replace('%C4', GS.pcb_com4)
name = name.replace('%p', _cl(GS.pcb_title))
name = name.replace('%r', _cl(GS.pcb_rev))
name = name.replace('%C1', _cl(GS.pcb_com1))
name = name.replace('%C2', _cl(GS.pcb_com2))
name = name.replace('%C3', _cl(GS.pcb_com3))
name = name.replace('%C4', _cl(GS.pcb_com4))
if GS.sch and do_sch:
name = name.replace('%c', GS.sch_comp)
name = name.replace('%d', GS.sch_date)
name = name.replace('%c', _cl(GS.sch_comp))
name = name.replace('%d', _cl(GS.sch_date))
name = name.replace('%F', GS.sch_no_ext)
name = name.replace('%f', GS.sch_basename)
name = name.replace('%p', GS.sch_title)
name = name.replace('%r', GS.sch_rev)
name = name.replace('%C1', GS.sch_com1)
name = name.replace('%C2', GS.sch_com2)
name = name.replace('%C3', GS.sch_com3)
name = name.replace('%C4', GS.sch_com4)
name = name.replace('%p', _cl(GS.sch_title))
name = name.replace('%r', _cl(GS.sch_rev))
name = name.replace('%C1', _cl(GS.sch_com1))
name = name.replace('%C2', _cl(GS.sch_com2))
name = name.replace('%C3', _cl(GS.sch_com3))
name = name.replace('%C4', _cl(GS.sch_com4))
# sanitize the name to avoid characters illegal in file systems
name = name.replace('\\', '/')
name = re.sub(r'[?%*:|"<>]', '_', name)

View File

@ -875,3 +875,22 @@ def test_compress_sources_1(test_dir):
files = ['source/'+prj+'.kicad_pcb', 'source/'+prj+'.sch', 'source/deeper.sch', 'source/sub-sheet.sch']
ctx.test_compress(prj + '-result.tar.bz2', files)
ctx.clean_up()
def test_date_format_1(test_dir):
""" Date from SCH reformated """
prj = 'test_v5'
ctx = context.TestContext(test_dir, 'test_date_format_1', prj, 'date_format_1', '')
ctx.run(extra=[])
ctx.expect_out_file(POS_DIR+'/test_v5_20200812.csv')
ctx.clean_up()
def test_date_format_2(test_dir):
""" Date from SCH reformated """
prj = 'bom'
ctx = context.TestContext(test_dir, 'test_date_format_2', prj, 'date_format_1', '')
ctx.run(extra=[])
ctx.expect_out_file(POS_DIR+'/bom_13_07_2020.csv')
assert ctx.search_err('Trying to reformat SCH time, but not in ISO format')
ctx.clean_up()

View File

@ -0,0 +1,16 @@
kibot:
version: 1
global:
output: '%f_%sd.%x'
date_format: '%Y%m%d'
outputs:
- name: 'position'
type: position
dir: positiondir
options:
format: CSV # CSV or ASCII format
units: millimeters # millimeters or inches
separate_files_for_front_and_back: false
only_smd: true