Создал(а) 'diagrams.py'

This commit is contained in:
dimon 2022-10-24 15:31:10 +00:00
parent 9473132040
commit 1dc7e7820c

315
diagrams.py Normal file
View File

@ -0,0 +1,315 @@
from subprocess import Popen, PIPE
import sys
from sys import platform
from os.path import expanduser
from pathlib import Path
import os.path
import zlib
import re
import json
class Example(object):
""" Example handling """
@staticmethod
def get(generator):
scriptdir = os.path.dirname(os.path.abspath(__file__))
example = scriptdir + "/../web/examples/example.%s" % generator
if os.path.isfile(example):
txt = Path(example).read_text()
else:
txt = "no example for %s found" % generator
return txt
class Command(object):
""" a command to be run using the shell environment """
def __init__(self, cmd, versionOption="--version", timeout=5, debug=False):
""" construct me """
self.cmd = cmd
self.timeout = timeout
self.versionOption = versionOption
self.cmdpath = None
self.debug = debug
def call(self, args):
""" call me with the given args"""
return self.docall(self.cmdpath, self.cmd, args)
def callalias(self, alias, args):
""" call me with the given args"""
#print ("callalias cmdpath %s\nalias %s\nargs %s" % (self.cmdpath, alias, args), file=sys.stderr)
return self.docall(self.cmdpath, alias, args)
def docall(self, cmdpath, cmd, args):
""" call with a specific path and command"""
cmdline = "%s%s %s" % (cmdpath, cmd, str(args))
if self.debug:
print ("calling %s" % cmdline, file=sys.stderr)
process = Popen(cmdline, shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE)
try:
stdout, stderr = process.communicate(timeout=self.timeout)
if self.debug:
if stdout is not None:
print ("stdout: %s" % stdout.decode('utf-8'), file=sys.stderr)
if stderr is not None:
print ("stderr: %s" % stderr.decode('utf-8'), file=sys.stderr)
return stdout, stderr
except Exception:
process.kill()
return None, None
def check(self):
cmdpaths = []
# do we know the cmdpath?
if self.cmdpath is None:
# no we need to try multiple options in a specific order
# prio #1: $HOME/bin
home = expanduser("~")
cmdpaths.append(home + "/bin/")
# prio #2: e.g. Macports
if platform == "darwin":
cmdpaths.append("/opt/local/bin/")
# prio #3: default path / no path
# no path - use default PATH
cmdpaths.append("")
else:
# we know the valid path
cmdpaths.append(self.cmdpath)
for cmdpath in cmdpaths:
stdout, stderr = self.docall(cmdpath, self.cmd, self.versionOption)
stdoutTxt = None
stderrTxt = None
if stdout is not None:
stdoutTxt = stdout.decode("utf-8")
if stderr is not None:
stderrTxt = stderr.decode("utf-8")
if not "not found" in stderrTxt and not "No such file or directory" in stderrTxt:
self.cmdpath = cmdpath
return stdoutTxt, stderrTxt
return None, None
class Generators(object):
generatorDict = {}
""" the available generators """
@staticmethod
def get(generator):
if len(Generators.generatorDict) is 0:
for gen in Generators.generators():
Generators.generatorDict[gen.id] = gen
gen = None
if generator in Generators.generatorDict:
gen = Generators.generatorDict[generator]
return gen
@staticmethod
def generatorIdForAlias(alias):
for gen in Generators.generators():
if alias in gen.aliases:
return gen.id
return None
@staticmethod
def generators():
scriptdir = os.path.dirname(os.path.abspath(__file__))
for plantumlpath in [".", ".."]:
plantumljar = scriptdir + "/" + plantumlpath + "/plantuml.jar";
if os.path.isfile(plantumljar):
break;
if plantumljar is None:
raise Exception("plantuml.jar not found in %s or .. of it", scriptdir)
#else
gens = [
Generator("graphviz", "GraphViz", "dot", "-V", logo="https://graphviz.gitlab.io/_pages/Resources/app.png", url="https://www.graphviz.org/",
aliases=[ 'dot', 'neato', 'twopi', 'circo', 'fdp', 'sfdp', 'patchwork', 'osage' ],
defaultType='png',
outputTypes=['dot', 'xdot', 'ps', 'pdf', 'svg', 'fig', 'png', 'gif', 'jpg', 'json', 'imap', 'cmapx']
),
Generator("mscgen", "Mscgen", "mscgen", "", logo="http://www.mcternan.me.uk/mscgen/img/msc-sig.png", url="http://www.mcternan.me.uk/mscgen/", defaultType='png', outputTypes=['png', 'eps', 'svg', 'ismap']),
Generator("plantuml", "PlantUML", "java -jar " + plantumljar, "-version", aliases=['plantuml'],
logo="https://useblocks.com/assets/img/posts/plantuml_logo.png",
url="https://plantuml.com",
defaultType='png',
download="http://sourceforge.net/projects/plantuml/files/plantuml.jar/download",
outputTypes=['png', 'svg', 'eps', 'pdf', 'vdx', 'xmi', 'scxml', 'html', 'txt', 'utxt',
'latex', 'latex:nopreamble'])
]
return gens
class GenerateResult(object):
def __init__(self, crc32, outputType, path, stdout, stderr):
""" construct me """
self.crc32 = crc32;
self.outputType = outputType;
self.path = path;
self.stdout = stdout
self.stderr = stderr
def errMsg(self):
""" decode my stdout and stderr to an error message"""
msg = ""
if self.stdout is not None:
msg = msg + self.stdout.decode('utf-8')
if self.stderr is not None:
msg = msg + self.stderr.decode('utf-8')
return msg
def asHtml(self):
""" return me as HTML"""
url = '/render/%s/%s' % (self.outputType, self.crc32)
if self.outputType in ['gif', 'jpg', 'png', 'svg']:
return "<img src='%s'>" % url;
elif self.outputType in ['pdf']:
return "<object data='%s' width='640' height='640'></object>" % url;
else:
return "<a href='%s'>%s %s</a>" % (url, self.outputType, self.crc32)
def isValid(self):
""" check if i am valid"""
valid = os.path.isfile(self.path) and not self.errMsg()
return valid
def asJson(self, baseurl):
""" return my result as JSON for the Mediawiki diagrams extension"""
errMsg=self.errMsg();
if errMsg:
jsonTxt="""{
"error": "generating %s failed",
"message": %s
}""" % (self.outputType,json.dumps(errMsg))
else:
jsonTxt = """{
"diagrams": {
"png": {
"url": "%s/png/%s.png"
}
}
}""" % (baseurl,self.crc32)
return jsonTxt
class Generator(object):
""" a diagram generator """
@staticmethod
def getOutputDirectory():
home = expanduser("~")
outputDir = home + "/.diagrams/"
if not os.path.isdir(outputDir):
os.mkdir(outputDir);
return outputDir
def __init__(self, genid, name, cmd, versionOption, logo=None, url=None, download=None, defaultType=None, aliases=None, outputTypes=None, debug=False):
""" construct me """
self.id = genid
self.name = name
self.cmd = cmd
self.gencmd = None
self.logo = logo
self.url = url
self.htmlInfo = None
self.download = download
self.versionOption = versionOption;
if aliases is None:
self.aliases = [cmd]
else:
self.aliases = aliases
self.defaultType = defaultType
self.selectedType = defaultType
self.outputTypes = outputTypes
self.debug = debug
pass
def getHtmlInfo(self):
""" get info on this generator to be displayed via HTML"""
# cache the info since getVersion is a costly process
if self.htmlInfo is None:
version = self.getVersion()
self.htmlInfo = "<a href='%s' title='%s:%s'><img src='%s'/></a>" % (self.url, self.name, version, self.logo)
return self.htmlInfo
def check(self):
""" check my version"""
self.gencmd = Command(self.cmd, self.versionOption, debug=self.debug)
return self.gencmd.check()
def getVersion(self):
stdOutText, stdErrText = self.check()
if stdOutText is None:
stdOutText = ''
if stdErrText is None:
stdErrText = ''
outputText = stdOutText + stdErrText
found = re.search(r'version.*[,)]', outputText)
if found:
version = found.group()
else:
# actually an error message
version = outputText
# invalidate gencmd
self.gencmd = None
return version
@staticmethod
def getHash(txt):
hashValue = zlib.crc32(txt.encode()) & 0xffffffff
hashId = hex(hashValue)
#print ("txt %s\nhashValue %s\nhashId %s" % (txt, hashValue, hashId), file=sys.stderr)
return hashId
def wrap(self,txt):
""" wraot the given text"""
if self.id == "plantuml":
txt="@startuml\n%s\n@enduml\n" % txt
return txt
def generate(self, alias, txt, outputType, renderer, useCached=True):
""" generate """
txt=self.wrap(txt)
hashId = Generator.getHash(txt)
inputPath = "%s%s.%s" % (Generator.getOutputDirectory(), hashId, 'txt')
stdout = None
stderr = None
if not (os.path.isfile(inputPath) and useCached):
text_file = open(inputPath, "wt", encoding='utf-8')
try:
if self.debug:
print ("txt %s" % txt, file=sys.stderr)
text_file.write("%s" % txt)
except Exception as e:
print ("exception %s" % e, file=sys.stderr)
finally:
text_file.close()
outputPath = "%s%s.%s" % (Generator.getOutputDirectory(), hashId, outputType)
if os.path.isfile(outputPath) and useCached:
if self.debug:
print ("cached %s #%s from %s" % (outputType, hashId, outputPath), file=sys.stderr)
#else:
if True:
if self.debug:
print ("generating %s #%s to %s" % (outputType, hashId, outputPath), file=sys.stderr)
if self.id == "graphviz":
args = "-T%s %s -o %s" % (outputType, inputPath, outputPath)
if len(renderer) != 0: #exists and not empty
alias = renderer
else:
alias = "dot"
#print ("renderer %s\nalias %s" % (renderer, alias), file=sys.stderr)
elif self.id == "mscgen":
args = "-T%s -i %s -o %s" % (outputType, inputPath, outputPath)
elif self.id == "plantuml":
args = "-charset UTF-8 -t%s %s" % (outputType, inputPath)
alias = "java -jar /var/diagrams/plantuml.jar"
else:
print ("unknown generator %s" % self.id, file=sys.stderr)
if self.gencmd is None:
self.check()
stdout, stderr = self.gencmd.callalias(alias, args)
result = GenerateResult(hashId, outputType, outputPath, stdout, stderr)
return result