Создал(а) 'diagrams.py'
This commit is contained in:
parent
9473132040
commit
1dc7e7820c
315
diagrams.py
Normal file
315
diagrams.py
Normal 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
|
||||||
Loading…
x
Reference in New Issue
Block a user