/garden/subprocess.py

https://gitlab.com/frabad/garden · Python · 167 lines · 123 code · 18 blank · 26 comment · 30 complexity · 5afeb280451c10c01f9c7e90b4ae6037 MD5 · raw file

  1. import collections
  2. import pathlib
  3. from .sys import whereis
  4. import subprocess
  5. def shrun(command,iotext=True):
  6. """execute a shell command"""
  7. command = command if isinstance(command,list) else command.split()
  8. result = None
  9. try:
  10. proc = subprocess.run(command,
  11. check=True, capture_output=True, text=iotext)
  12. if rcode := proc.returncode and rcode != 0:
  13. print("return code: ", rcode)
  14. result = proc.stdout.strip() if iotext else proc.stdout
  15. except(subprocess.CalledProcessError) as e:
  16. print(e)
  17. return result
  18. def messages_to_codes(messages) -> dict:
  19. """convert a list of messages to a dict of int codes"""
  20. messages = messages if isinstance(messages,list) else messages.split(",")
  21. return {codenum : message for codenum, message in zip(
  22. (int(c) for c in range(len(messages))),
  23. (m.replace("\n"," ").strip() for m in messages))}
  24. class Pipeline(object):
  25. Step = collections.namedtuple("Step","name source exe description")
  26. class Process(object):
  27. """"""
  28. def __init__(self, name, ifile=None, tfile=None, params={}):
  29. """an XML process
  30. Parameters:
  31. name:
  32. the name of an executable as str
  33. ifile:
  34. the name of the input document as str
  35. tfile:
  36. the name of an XSL-T transformation sheet as str
  37. params:
  38. additional config parameters as str
  39. """
  40. self.name = name
  41. self.params = params
  42. self.result, self.ofile = None, None
  43. self.available = any([whereis(self.name)])
  44. self.ifile = pathlib.Path(ifile) if ifile else None
  45. self.tfile = pathlib.Path(tfile) if tfile else None
  46. if self.ifile and self.available:
  47. self.ofile = self.ifile.with_suffix("."+self.name)
  48. self.result = self.run()
  49. def xsltproc(self):
  50. """process an xsltproc call"""
  51. cmd = "xsltproc --xinclude --output %s" % self.ofile
  52. if any(self.params):
  53. for param in self.params.items():
  54. cmd = cmd + " --stringparam %s" % param
  55. return cmd + " %s %s" % (self.tfile, self.ifile)
  56. def xmllint(self):
  57. """call xmlllint to format and reindent input XML document"""
  58. cmd = "xmllint --xinclude --encode UTF-8 --noblanks --pretty 1"
  59. return cmd + "--output %s %s" % (self.ofile, self.ifile)
  60. def fo2rtf(self):
  61. """call fo2rtf to format an input XSL-FO document to RTF"""
  62. self.ofile = self.ifile.with_suffix(".rtf")
  63. return "fo2rtf %s > %s" % (ofile, self.ifile)
  64. def fop(self):
  65. """call fop to format an input XSL-FO document to PDF"""
  66. self.ofile = self.ifile.with_suffix(".pdf")
  67. cmd = "fop -fo %s -pdf %s " % (self.ifile, self.ofile)
  68. xconf = self.ifile.parent / "fop.xconf"
  69. if xconf.exists():
  70. cmd += "-c %s" % xconf
  71. return cmd
  72. def run(self):
  73. """run a process and generate an output document as ofile"""
  74. def call(name):
  75. """call one of several method by its name"""
  76. methods={
  77. "xsltproc": self.xsltproc,
  78. "xmllint": self.xmllint,
  79. "fo2rtf": self.fo2rtf,
  80. "fop": self.fop,
  81. }
  82. return methods[name]() if name in methods else None
  83. cmd = call(self.name)
  84. if cmd:
  85. with open(self.ofile,'w'):
  86. shrun(cmd)
  87. else:
  88. print(f"'{name}' is not one of the supported commands",
  89. f"{tuple(methods.keys())}.")
  90. if self.ofile.exists() and self.ofile.stat().st_size > 0:
  91. return self.ofile
  92. def __init__(self, steps):
  93. """process pipeline manager"""
  94. self.steps = steps
  95. self.abilities = { s.name: s.description
  96. for s in steps if Pipeline.Process(s.exe).available}
  97. def format(self):
  98. """simple str layout"""
  99. return "\n".join([f" {k}: {v}" for k,v in self.abilities.items()])
  100. def run(self, source, finalform=None):
  101. """run a series of chained processes"""
  102. def make(ifile, target, xsltdir="xslt"):
  103. """config/make a transformation
  104. Can guess the path of an XSL-T located at xslt/target.xsl
  105. """
  106. exe = [s for s in self.steps if s.name == target][0].exe
  107. cmd = [exe, ifile]
  108. if exe == "xsltproc":
  109. target = pathlib.Path(target)
  110. xsltdir = pathlib.Path(xsltdir)
  111. target = (xsltdir / target).with_suffix(".xsl")
  112. cmd.append(target)
  113. return Pipeline.Process(*cmd)
  114. def trace(step):
  115. """trace a step in a pipeline
  116. Builds an list of ordered processes for a given step"""
  117. def _previous(step):
  118. """find the previous step in a pipeline"""
  119. [_step] = [s for s in self.steps if s.name == step] or [None]
  120. if _step:
  121. return _step.source
  122. pipeline, previous = [step], _previous(step)
  123. while previous:
  124. pipeline.append(previous)
  125. previous = _previous(previous)
  126. pipeline.reverse()
  127. return tuple(pipeline)
  128. finalform = finalform or self.steps[-1].name
  129. steps = trace(finalform)
  130. for idx, outform in enumerate(steps):
  131. process = make(source, outform)
  132. if process.result:
  133. outname = pathlib.Path(process.result).with_suffix("."+outform)
  134. pathlib.Path(process.result).rename(outname)
  135. if 0 < idx < len(steps):
  136. pathlib.Path(source).unlink()
  137. source = outname
  138. return source