/garden/subprocess.py
https://gitlab.com/frabad/garden · Python · 167 lines · 123 code · 18 blank · 26 comment · 30 complexity · 5afeb280451c10c01f9c7e90b4ae6037 MD5 · raw file
- import collections
- import pathlib
- from .sys import whereis
- import subprocess
- def shrun(command,iotext=True):
- """execute a shell command"""
- command = command if isinstance(command,list) else command.split()
- result = None
- try:
- proc = subprocess.run(command,
- check=True, capture_output=True, text=iotext)
- if rcode := proc.returncode and rcode != 0:
- print("return code: ", rcode)
- result = proc.stdout.strip() if iotext else proc.stdout
- except(subprocess.CalledProcessError) as e:
- print(e)
- return result
- def messages_to_codes(messages) -> dict:
- """convert a list of messages to a dict of int codes"""
- messages = messages if isinstance(messages,list) else messages.split(",")
- return {codenum : message for codenum, message in zip(
- (int(c) for c in range(len(messages))),
- (m.replace("\n"," ").strip() for m in messages))}
- class Pipeline(object):
-
- Step = collections.namedtuple("Step","name source exe description")
-
- class Process(object):
- """"""
-
- def __init__(self, name, ifile=None, tfile=None, params={}):
- """an XML process
-
- Parameters:
-
- name:
- the name of an executable as str
- ifile:
- the name of the input document as str
- tfile:
- the name of an XSL-T transformation sheet as str
- params:
- additional config parameters as str
-
- """
- self.name = name
- self.params = params
- self.result, self.ofile = None, None
- self.available = any([whereis(self.name)])
- self.ifile = pathlib.Path(ifile) if ifile else None
- self.tfile = pathlib.Path(tfile) if tfile else None
- if self.ifile and self.available:
- self.ofile = self.ifile.with_suffix("."+self.name)
- self.result = self.run()
-
- def xsltproc(self):
- """process an xsltproc call"""
- cmd = "xsltproc --xinclude --output %s" % self.ofile
- if any(self.params):
- for param in self.params.items():
- cmd = cmd + " --stringparam %s" % param
- return cmd + " %s %s" % (self.tfile, self.ifile)
- def xmllint(self):
- """call xmlllint to format and reindent input XML document"""
- cmd = "xmllint --xinclude --encode UTF-8 --noblanks --pretty 1"
- return cmd + "--output %s %s" % (self.ofile, self.ifile)
- def fo2rtf(self):
- """call fo2rtf to format an input XSL-FO document to RTF"""
- self.ofile = self.ifile.with_suffix(".rtf")
- return "fo2rtf %s > %s" % (ofile, self.ifile)
- def fop(self):
- """call fop to format an input XSL-FO document to PDF"""
- self.ofile = self.ifile.with_suffix(".pdf")
- cmd = "fop -fo %s -pdf %s " % (self.ifile, self.ofile)
- xconf = self.ifile.parent / "fop.xconf"
- if xconf.exists():
- cmd += "-c %s" % xconf
- return cmd
-
- def run(self):
- """run a process and generate an output document as ofile"""
-
- def call(name):
- """call one of several method by its name"""
- methods={
- "xsltproc": self.xsltproc,
- "xmllint": self.xmllint,
- "fo2rtf": self.fo2rtf,
- "fop": self.fop,
- }
- return methods[name]() if name in methods else None
-
- cmd = call(self.name)
- if cmd:
- with open(self.ofile,'w'):
- shrun(cmd)
- else:
- print(f"'{name}' is not one of the supported commands",
- f"{tuple(methods.keys())}.")
- if self.ofile.exists() and self.ofile.stat().st_size > 0:
- return self.ofile
-
-
- def __init__(self, steps):
- """process pipeline manager"""
- self.steps = steps
- self.abilities = { s.name: s.description
- for s in steps if Pipeline.Process(s.exe).available}
-
- def format(self):
- """simple str layout"""
- return "\n".join([f" {k}: {v}" for k,v in self.abilities.items()])
-
- def run(self, source, finalform=None):
- """run a series of chained processes"""
-
- def make(ifile, target, xsltdir="xslt"):
- """config/make a transformation
-
- Can guess the path of an XSL-T located at xslt/target.xsl
-
- """
- exe = [s for s in self.steps if s.name == target][0].exe
- cmd = [exe, ifile]
- if exe == "xsltproc":
- target = pathlib.Path(target)
- xsltdir = pathlib.Path(xsltdir)
- target = (xsltdir / target).with_suffix(".xsl")
- cmd.append(target)
- return Pipeline.Process(*cmd)
-
- def trace(step):
- """trace a step in a pipeline
-
- Builds an list of ordered processes for a given step"""
-
- def _previous(step):
- """find the previous step in a pipeline"""
- [_step] = [s for s in self.steps if s.name == step] or [None]
- if _step:
- return _step.source
-
- pipeline, previous = [step], _previous(step)
- while previous:
- pipeline.append(previous)
- previous = _previous(previous)
- pipeline.reverse()
- return tuple(pipeline)
-
- finalform = finalform or self.steps[-1].name
- steps = trace(finalform)
- for idx, outform in enumerate(steps):
- process = make(source, outform)
- if process.result:
- outname = pathlib.Path(process.result).with_suffix("."+outform)
- pathlib.Path(process.result).rename(outname)
- if 0 < idx < len(steps):
- pathlib.Path(source).unlink()
- source = outname
- return source