Commit f4cd1f8d0b3fc45801ceeec3769ad79c95fb74a7

Authored by alexis
1 parent e0bbbfaf
Exists in dev

Add of an unofficial version of plantuml which is fixed (the error which was occ…

…uring is commented in the file)
Showing 1 changed file with 234 additions and 0 deletions   Show diff stats
install/plantuml.py 0 → 100755
... ... @@ -0,0 +1,234 @@
  1 +#!/usr/bin/env python
  2 +
  3 +from __future__ import print_function
  4 +
  5 +import base64
  6 +import string
  7 +from argparse import ArgumentParser
  8 +from io import open
  9 +from os import environ, path, makedirs
  10 +from zlib import compress
  11 +
  12 +import httplib2
  13 +import six
  14 +from six.moves.urllib.parse import urlencode
  15 +
  16 +if six.PY2:
  17 + from string import maketrans
  18 +else:
  19 + maketrans = bytes.maketrans
  20 +
  21 +__version__ = 0, 3, 0
  22 +__version_string__ = '.'.join(str(x) for x in __version__)
  23 +
  24 +__author__ = 'Doug Napoleone, Samuel Marks, Eric Frederich'
  25 +__email__ = 'doug.napoleone+plantuml@gmail.com'
  26 +
  27 +
  28 +plantuml_alphabet = string.digits + string.ascii_uppercase + string.ascii_lowercase + '-_'
  29 +base64_alphabet = string.ascii_uppercase + string.ascii_lowercase + string.digits + '+/'
  30 +b64_to_plantuml = maketrans(base64_alphabet.encode('utf-8'), plantuml_alphabet.encode('utf-8'))
  31 +
  32 +
  33 +class PlantUMLError(Exception):
  34 + """
  35 + Error in processing.
  36 + """
  37 + pass
  38 +
  39 +
  40 +class PlantUMLConnectionError(PlantUMLError):
  41 + """
  42 + Error connecting or talking to PlantUML Server.
  43 + """
  44 + pass
  45 +
  46 +
  47 +class PlantUMLHTTPError(PlantUMLConnectionError):
  48 + """
  49 + Request to PlantUML server returned HTTP Error.
  50 + """
  51 +
  52 + def __init__(self, response, content, *args, **kwdargs):
  53 + # Solving issue with message following the solution on the PR : https://github.com/dougn/python-plantuml/pull/9
  54 + #super(PlantUMLConnectionError, self).__init__(*args, **kwdargs)
  55 + self.response = response
  56 + self.content = content
  57 + #if not self.message:
  58 + if not getattr(self,'message',None):
  59 + self.message = "%d: %s" % (
  60 + self.response.status, self.response.reason)
  61 + super(PlantUMLConnectionError, self).__init__(*args, **kwdargs)
  62 +
  63 +
  64 +def deflate_and_encode(plantuml_text):
  65 + """zlib compress the plantuml text and encode it for the plantuml server.
  66 + """
  67 + zlibbed_str = compress(plantuml_text.encode('utf-8'))
  68 + compressed_string = zlibbed_str[2:-4]
  69 + return base64.b64encode(compressed_string).translate(b64_to_plantuml).decode('utf-8')
  70 +
  71 +
  72 +class PlantUML(object):
  73 + """Connection to a PlantUML server with optional authentication.
  74 +
  75 + All parameters are optional.
  76 +
  77 + :param str url: URL to the PlantUML server image CGI. defaults to
  78 + http://www.plantuml.com/plantuml/img/
  79 + :param dict basic_auth: This is if the plantuml server requires basic HTTP
  80 + authentication. Dictionary containing two keys, 'username'
  81 + and 'password', set to appropriate values for basic HTTP
  82 + authentication.
  83 + :param dict form_auth: This is for plantuml server requires a cookie based
  84 + webform login authentication. Dictionary containing two
  85 + primary keys, 'url' and 'body'. The 'url' should point to
  86 + the login URL for the server, and the 'body' should be a
  87 + dictionary set to the form elements required for login.
  88 + The key 'method' will default to 'POST'. The key 'headers'
  89 + defaults to
  90 + {'Content-type':'application/x-www-form-urlencoded'}.
  91 + Example: form_auth={'url': 'http://example.com/login/',
  92 + 'body': { 'username': 'me', 'password': 'secret'}
  93 + :param dict http_opts: Extra options to be passed off to the
  94 + httplib2.Http() constructor.
  95 + :param dict request_opts: Extra options to be passed off to the
  96 + httplib2.Http().request() call.
  97 +
  98 + """
  99 +
  100 + def __init__(self, url, basic_auth={}, form_auth={},
  101 + http_opts={}, request_opts={}):
  102 + self.HttpLib2Error = httplib2.HttpLib2Error
  103 + self.url = url
  104 + self.request_opts = request_opts
  105 + self.auth_type = 'basic_auth' if basic_auth else (
  106 + 'form_auth' if form_auth else None)
  107 + self.auth = basic_auth if basic_auth else (
  108 + form_auth if form_auth else None)
  109 +
  110 + # Proxify
  111 + try:
  112 + from urlparse import urlparse
  113 + import socks
  114 +
  115 + proxy_uri = urlparse(environ.get('HTTPS_PROXY', environ.get('HTTP_PROXY')))
  116 + if proxy_uri:
  117 + proxy = {'proxy_info': httplib2.ProxyInfo(socks.PROXY_TYPE_HTTP,
  118 + proxy_uri.hostname, proxy_uri.port)}
  119 + http_opts.update(proxy)
  120 + self.request_opts.update(proxy)
  121 + except ImportError:
  122 + pass
  123 +
  124 + self.http = httplib2.Http(**http_opts)
  125 +
  126 + if self.auth_type == 'basic_auth':
  127 + self.http.add_credentials(
  128 + self.auth['username'], self.auth['password'])
  129 + elif self.auth_type == 'form_auth':
  130 + if 'url' not in self.auth:
  131 + raise PlantUMLError(
  132 + "The form_auth option 'url' must be provided and point to "
  133 + "the login url.")
  134 + if 'body' not in self.auth:
  135 + raise PlantUMLError(
  136 + "The form_auth option 'body' must be provided and include "
  137 + "a dictionary with the form elements required to log in. "
  138 + "Example: form_auth={'url': 'http://example.com/login/', "
  139 + "'body': { 'username': 'me', 'password': 'secret'}")
  140 + login_url = self.auth['url']
  141 + body = self.auth['body']
  142 + method = self.auth.get('method', 'POST')
  143 + headers = self.auth.get(
  144 + 'headers', {'Content-type': 'application/x-www-form-urlencoded'})
  145 + try:
  146 + response, content = self.http.request(
  147 + login_url, method, headers=headers,
  148 + body=urlencode(body))
  149 + except self.HttpLib2Error as e:
  150 + raise PlantUMLConnectionError(e)
  151 + if response.status != 200:
  152 + raise PlantUMLHTTPError(response, content)
  153 + self.request_opts['Cookie'] = response['set-cookie']
  154 +
  155 + def get_url(self, plantuml_text):
  156 + """Return the server URL for the image.
  157 + You can use this URL in an IMG HTML tag.
  158 +
  159 + :param str plantuml_text: The plantuml markup to render
  160 + :returns: the plantuml server image URL
  161 + """
  162 + return self.url + deflate_and_encode(plantuml_text)
  163 +
  164 + def processes(self, plantuml_text):
  165 + """Processes the plantuml text into the raw PNG image data.
  166 +
  167 + :param str plantuml_text: The plantuml markup to render
  168 + :returns: the raw image data
  169 + """
  170 + url = self.get_url(plantuml_text)
  171 + try:
  172 + response, content = self.http.request(url, **self.request_opts)
  173 + except self.HttpLib2Error as e:
  174 + raise PlantUMLConnectionError(e)
  175 + if response.status != 200:
  176 + raise PlantUMLHTTPError(response, content)
  177 + return content
  178 +
  179 + def processes_file(self, filename, outfile=None, errorfile=None, directory=''):
  180 + """Take a filename of a file containing plantuml text and processes
  181 + it into a .png image.
  182 +
  183 + :param str filename: Text file containing plantuml markup
  184 + :param str outfile: Filename to write the output image to. If not
  185 + supplied, then it will be the input filename with the
  186 + file extension replaced with '.png'.
  187 + :param str errorfile: Filename to write server html error page
  188 + to. If this is not supplined, then it will be the
  189 + input ``filename`` with the extension replaced with
  190 + '_error.html'.
  191 + :returns: ``True`` if the image write succedded, ``False`` if there was
  192 + an error written to ``errorfile``.
  193 + """
  194 + if outfile is None:
  195 + outfile = path.splitext(filename)[0] + '.png'
  196 + if errorfile is None:
  197 + errorfile = path.splitext(filename)[0] + '_error.html'
  198 + if directory and not path.exists(directory):
  199 + makedirs(directory)
  200 + data = open(filename).read()
  201 + try:
  202 + content = self.processes(data)
  203 + except PlantUMLHTTPError as e:
  204 + # added wb when oppening file (in official version it will open the file with usual format (not bytes)
  205 + err = open(path.join(directory, errorfile), 'wb')
  206 + err.write(e.content)
  207 + err.close()
  208 + return False
  209 + out = open(path.join(directory, outfile), 'wb')
  210 + out.write(content)
  211 + out.close()
  212 + return True
  213 +
  214 +
  215 +def _build_parser():
  216 + parser = ArgumentParser(description='Generate images from plantuml defined files using plantuml server')
  217 + parser.add_argument('files', metavar='filename', nargs='+',
  218 + help='file(s) to generate images from')
  219 + parser.add_argument('-o', '--out', default='',
  220 + help='directory to put the files into')
  221 + parser.add_argument('-s', '--server', default='http://www.plantuml.com/plantuml/img/',
  222 + help='server to generate from, defaults to "http://www.plantuml.com/plantuml/img/"')
  223 + return parser
  224 +
  225 +
  226 +def main():
  227 + args = _build_parser().parse_args()
  228 + pl = PlantUML(args.server)
  229 + print(list(map(lambda filename: {'filename': filename,
  230 + 'gen_success': pl.processes_file(filename, directory=args.out)}, args.files)))
  231 +
  232 +
  233 +if __name__ == '__main__':
  234 + main()
... ...