KiBot/kibot/pre_set_text_variables.py

168 lines
6.7 KiB
Python

# -*- coding: utf-8 -*-
# Copyright (c) 2022 Salvador E. Tropea
# Copyright (c) 2022 Instituto Nacional de Tecnología Industrial
# License: GPL-3.0
# Project: KiBot (formerly KiPlot)
"""
Dependencies:
- from: Git
role: Find commit hash and/or date
- from: Bash
role: Run external commands to create replacement text
"""
import json
import os
import re
import shlex
from subprocess import run, PIPE
import sys
from .error import KiPlotConfigurationError
from .misc import FAILED_EXECUTE, W_EMPTREP
from .optionable import Optionable
from .pre_base import BasePreFlight
from .gs import GS
from .macros import macros, document, pre_class # noqa: F401
from . import log
logger = log.get_logger()
re_git = re.compile(r'([^a-zA-Z_]|^)(git) ')
class KiCadVariable(Optionable):
""" KiCad variable definition """
def __init__(self):
super().__init__()
self._unknown_is_error = True
with document:
self.name = ''
""" Name of the variable. The `version` variable will be expanded using `${version}` """
self.variable = None
""" {name} """
self.text = ''
""" Text to insert instead of the variable """
self.command = ''
""" Command to execute to get the text, will be used only if `text` is empty.
This command will be executed using the Bash shell.
Be careful about spaces in file names (i.e. use "$KIBOT_PCB_NAME").
The `KIBOT_PCB_NAME` environment variable is the PCB file and the
`KIBOT_SCH_NAME` environment variable is the schematic file """
self.before = ''
""" Text to add before the output of `command` """
self.after = ''
""" Text to add after the output of `command` """
self.expand_kibot_patterns = True
""" Expand %X patterns. The context is `schematic` """
def config(self, parent):
super().config(parent)
if not self.name:
raise KiPlotConfigurationError("Missing variable name ({})".format(str(self._tree)))
class Set_Text_VariablesOptions(Optionable):
""" A list of KiCad variables """
def __init__(self):
super().__init__()
with document:
self.variables = KiCadVariable
""" [dict|list(dict)] Variables """
def config(self, parent):
super().config(parent)
if isinstance(self.variables, type):
self.variables = []
elif isinstance(self.variables, KiCadVariable):
self.variables = [self.variables]
@pre_class
class Set_Text_Variables(BasePreFlight): # noqa: F821
""" [dict|list(dict)] Defines KiCad 6 variables.
They are expanded using ${VARIABLE}, and stored in the project file.
This preflight replaces `pcb_replace` and `sch_replace` when using KiCad 6.
The KiCad project file is modified """
def __init__(self, name, value):
f = Set_Text_VariablesOptions()
f.set_tree({'variables': value})
f.config(self)
super().__init__(name, f.variables)
@classmethod
def get_doc(cls):
return cls.__doc__, KiCadVariable
@classmethod
def get_example(cls):
""" Returns a YAML value for the example config """
return ("\n - name: 'git_hash'"
"\n command: 'git log -1 --format=\"%h\" \"$KIBOT_PCB_NAME\"'"
"\n before: 'Git hash: <'"
"\n after: '>'")
def apply(self):
o = self._value
if len(o) == 0:
return
if GS.ki5:
raise KiPlotConfigurationError("The `set_text_variables` preflight is for KiCad 6 or newer")
pro_name = GS.pro_file
if not pro_name or not os.path.isfile(pro_name):
raise KiPlotConfigurationError("Trying to define KiCad 6 variables but the project is missing ({})".
format(pro_name))
# Get the current definitions
with open(pro_name, 'rt') as f:
pro_text = f.read()
data = json.loads(pro_text)
text_variables = data.get('text_variables', {})
GS.pro_variables = text_variables
logger.debug("- Current variables: {}".format(text_variables))
# Define the requested variables
if GS.pcb_file:
os.environ['KIBOT_PCB_NAME'] = GS.pcb_file
if GS.sch_file:
os.environ['KIBOT_SCH_NAME'] = GS.sch_file
bash_command = None
for r in o:
text = r.text
if not text and r.command:
command = r.command
if re_git.search(command):
git_command = self.ensure_tool('git')
command = re_git.sub(r'\1'+git_command.replace('\\', r'\\')+' ', command)
if not bash_command:
bash_command = self.ensure_tool('Bash')
cmd = [bash_command, '-c', command]
logger.debug('Executing: '+shlex.join(command))
result = run(cmd, stdout=PIPE, stderr=PIPE, universal_newlines=True)
if result.returncode:
logger.error('Failed to execute:\n{}\nreturn code {}'.format(r.command, result.returncode))
if result.stdout:
logger.error('stdout:\n{}'.format(result.stdout))
if result.stderr:
logger.error('stderr:\n{}'.format(result.stderr))
sys.exit(FAILED_EXECUTE)
if not result.stdout:
logger.warning(W_EMPTREP+"Empty value from `{}`".format(r.command))
text = result.stdout.strip()
text = r.before + text + r.after
logger.debug(' - ' + r.name + ' -> ' + text)
text_variables[r.name] = text
logger.debug("- Expanding %X patterns in variables")
# Now that we have the variables defined expand the %X patterns (they could use variables)
for r in o:
if r.expand_kibot_patterns:
text = text_variables[r.name]
new_text = Optionable.expand_filename_both(self, text, make_safe=False)
if text != new_text:
logger.debug(' - ' + r.name + ' -> ' + new_text)
text_variables[r.name] = new_text
logger.debug("- New list of variables: {}".format(text_variables))
# Store the modified project
data['text_variables'] = text_variables
GS.make_bkp(pro_name)
with open(pro_name, 'wt') as f:
f.write(json.dumps(data, sort_keys=True, indent=2))
if GS.board:
# Force a project and PCB reload
GS.reload_project(pro_name)