diff --git a/examples/cat.jpg b/examples/cat.jpg new file mode 100644 index 000000000..d20bc0bf4 Binary files /dev/null and b/examples/cat.jpg differ diff --git a/examples/test.py b/examples/test.py index e69de29bb..b2eb4ee77 100644 --- a/examples/test.py +++ b/examples/test.py @@ -0,0 +1,2 @@ +import this +foo = 'lol' diff --git a/main.py b/main.py index 5897fd5b0..7c95e2de8 100644 --- a/main.py +++ b/main.py @@ -1,19 +1,75 @@ -from flask import Flask +from flask import Flask, request, send_file, send_from_directory +from cStringIO import StringIO +import importlib import os -app = Flask(__name__) +from renderer import FileRenderer + +app = Flask(__name__, static_folder='examples') + +# Recursively import modules +for dirpath, dirnames, filenames in os.walk('renderer'): + for filename in filenames: + if filename.endswith('.py'): + modulename = os.path.join(dirpath, filename)\ + .replace('/', '.')\ + .replace('.py', '') + importlib.import_module(modulename) + +# Optional configuration for renderers +config = { + 'ImageRenderer': {'max_width': '200px'}, +} + +# Module static files should live in renderer//') +def send_module_file(module, filepath): + path, filename = os.path.split(filepath) + module_static_dir = os.path.join('renderer', module, 'static', path) + return send_from_directory(module_static_dir, filename) + +@app.route('/export///', methods=['POST']) +def export(renderer, filename): + exporter = request.form.get('exporter') + renderer_class = FileRenderer.registry.get(renderer) + if renderer_class is None: + return 'Renderer not found.' + renderer_object = renderer_class(**config.get(renderer, {})) + renderer_method = getattr( + renderer_object, + 'export_{}'.format(exporter), + None + ) + if renderer_method is None: + return 'Renderer exporter not found.' + filepath = os.path.join('examples', filename) + rendered, extension = renderer_method(open(filepath)) + + name, ext = os.path.splitext(filename) + export_name = name + extension + + return send_file( + StringIO(rendered), + as_attachment=True, + attachment_filename=export_name, + ) @app.route('/') def index(): html = '' for fn in os.listdir('examples'): - html += '{filename}
'.format( + html += '{filename}
'.format( filename=fn) return html -@app.route('/example/') -def examples(filename): +@app.route('/render/') +def render(filename): + fp = open(os.path.join('examples', filename)) + for name, cls in FileRenderer.registry.items(): + renderer = cls(**config.get(name, {})) + if renderer.detect(fp): + return renderer._render(fp, '/examples/{}'.format(filename)) return filename if __name__ == '__main__': - app.run(debug=True) \ No newline at end of file + app.run(debug=True, port=5001) diff --git a/renderer/__init__.py b/renderer/__init__.py index e69de29bb..c9ade560f 100644 --- a/renderer/__init__.py +++ b/renderer/__init__.py @@ -0,0 +1,86 @@ +import os +import abc +import inspect + +class FileMeta(abc.ABCMeta): + + def __init__(cls, name, bases, dct): + + # Call super-metaclass __init__ + super(FileMeta, cls).__init__(name, bases, dct) + + # Initialize class registry and add self + if not hasattr(cls, 'registry'): + cls.registry = {} + if name != 'FileRenderer': + cls.registry[name] = cls + + # Get export methods + cls.exporters = [ + name.replace('export_', '') + for name, value in dct.iteritems() + if name.startswith('export_') + and inspect.isfunction(value) + ] + +class FileRenderer(object): + + __metaclass__ = FileMeta + + def _render(self, fp, path): + + _, filename = os.path.split(fp.name) + exporters = self.render_exporters(filename) + rendered = self.render(fp, path) + return exporters + '\n' + rendered + + def render_exporters(self, filename): + """Render exporters to an HTML form. + + :param filename: Name of file to export + :return: HTML form with dropdown widget + + """ + if not self.exporters: + return '' + + options = [ + ''.format( + exporter, exporter.capitalize() + ) + for exporter in self.exporters + ] + + return ''' +
+ + +
'''.format( + klass=self.__class__.__name__, + filename=filename, + options='\n'.join(options) + ) + + @abc.abstractmethod + def detect(self, fp): + """Detects whether a given file pointer can be rendered by + this renderer. + + :param fp: File pointer + :return: Can file be rendered? (bool) + + """ + pass + + @abc.abstractmethod + def render(self, fp, path): + """Renders a file to HTML. + + :param fp: File pointer + :param path: Path to file + :return: HTML rendition of file + + """ + pass diff --git a/renderer/code/__init__.py b/renderer/code/__init__.py new file mode 100644 index 000000000..d46c84e01 --- /dev/null +++ b/renderer/code/__init__.py @@ -0,0 +1,25 @@ +from .. import FileRenderer + +import pygments +import pygments.lexers +import pygments.formatters + +formatter = pygments.formatters.HtmlFormatter() + +class CodeRenderer(FileRenderer): + + def detect(self, fp): + fname = fp.name + for ext in ['py', 'rb']: + if fname.endswith(ext): + return True + return False + + def render(self, fp, path): + content = fp.read() + highlight = pygments.highlight( + content, + pygments.lexers.guess_lexer_for_filename(fp.name, content), + formatter + ) + return '' + '\n' + highlight diff --git a/renderer/code/static/css/style.css b/renderer/code/static/css/style.css new file mode 100644 index 000000000..62103f4d5 --- /dev/null +++ b/renderer/code/static/css/style.css @@ -0,0 +1,62 @@ +.highlight .hll { background-color: #ffffcc } +.highlight { background: #f8f8f8; } +.highlight .c { color: #408080; font-style: italic } /* Comment */ +.highlight .err { border: 1px solid #FF0000 } /* Error */ +.highlight .k { color: #008000; font-weight: bold } /* Keyword */ +.highlight .o { color: #666666 } /* Operator */ +.highlight .cm { color: #408080; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #BC7A00 } /* Comment.Preproc */ +.highlight .c1 { color: #408080; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #408080; font-style: italic } /* Comment.Special */ +.highlight .gd { color: #A00000 } /* Generic.Deleted */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .gr { color: #FF0000 } /* Generic.Error */ +.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +.highlight .gi { color: #00A000 } /* Generic.Inserted */ +.highlight .go { color: #888888 } /* Generic.Output */ +.highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +.highlight .gt { color: #0044DD } /* Generic.Traceback */ +.highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ +.highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ +.highlight .kp { color: #008000 } /* Keyword.Pseudo */ +.highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #B00040 } /* Keyword.Type */ +.highlight .m { color: #666666 } /* Literal.Number */ +.highlight .s { color: #BA2121 } /* Literal.String */ +.highlight .na { color: #7D9029 } /* Name.Attribute */ +.highlight .nb { color: #008000 } /* Name.Builtin */ +.highlight .nc { color: #0000FF; font-weight: bold } /* Name.Class */ +.highlight .no { color: #880000 } /* Name.Constant */ +.highlight .nd { color: #AA22FF } /* Name.Decorator */ +.highlight .ni { color: #999999; font-weight: bold } /* Name.Entity */ +.highlight .ne { color: #D2413A; font-weight: bold } /* Name.Exception */ +.highlight .nf { color: #0000FF } /* Name.Function */ +.highlight .nl { color: #A0A000 } /* Name.Label */ +.highlight .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ +.highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */ +.highlight .nv { color: #19177C } /* Name.Variable */ +.highlight .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ +.highlight .w { color: #bbbbbb } /* Text.Whitespace */ +.highlight .mf { color: #666666 } /* Literal.Number.Float */ +.highlight .mh { color: #666666 } /* Literal.Number.Hex */ +.highlight .mi { color: #666666 } /* Literal.Number.Integer */ +.highlight .mo { color: #666666 } /* Literal.Number.Oct */ +.highlight .sb { color: #BA2121 } /* Literal.String.Backtick */ +.highlight .sc { color: #BA2121 } /* Literal.String.Char */ +.highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ +.highlight .s2 { color: #BA2121 } /* Literal.String.Double */ +.highlight .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */ +.highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */ +.highlight .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */ +.highlight .sx { color: #008000 } /* Literal.String.Other */ +.highlight .sr { color: #BB6688 } /* Literal.String.Regex */ +.highlight .s1 { color: #BA2121 } /* Literal.String.Single */ +.highlight .ss { color: #19177C } /* Literal.String.Symbol */ +.highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */ +.highlight .vc { color: #19177C } /* Name.Variable.Class */ +.highlight .vg { color: #19177C } /* Name.Variable.Global */ +.highlight .vi { color: #19177C } /* Name.Variable.Instance */ +.highlight .il { color: #666666 } /* Literal.Number.Integer.Long */ diff --git a/renderer/image/__init__.py b/renderer/image/__init__.py new file mode 100644 index 000000000..6b7d77998 --- /dev/null +++ b/renderer/image/__init__.py @@ -0,0 +1,35 @@ +from .. import FileRenderer +from cStringIO import StringIO +import Image + +class ImageRenderer(FileRenderer): + + def __init__(self, max_width=None): + self.max_width = max_width + + def detect(self, fp): + fname = fp.name + for ext in ['jpg', 'png', 'tif']: + if fname.endswith(ext): + return True + return False + + def render(self, fp, path): + fname = fp.name + if self.max_width: + style = ' style="max-width: {}"'.format(self.max_width) + else: + style = '' + return ''.format(path, style) + + def export_gif(self, fp): + im = Image.open(fp) + sio = StringIO() + im.save(sio, format='gif') + return sio.getvalue(), '.gif' + + def export_tiff(self, fp): + im = Image.open(fp) + sio = StringIO() + im.save(sio, format='tiff') + return sio.getvalue(), '.tiff' diff --git a/renderer/pdf/__init__.py b/renderer/pdf/__init__.py index c160f910b..3fab41f08 100644 --- a/renderer/pdf/__init__.py +++ b/renderer/pdf/__init__.py @@ -1,2 +1,23 @@ -class PdfRenderer(object): - pass \ No newline at end of file +from .. import FileRenderer + +class PdfRenderer(FileRenderer): + + def detect(self, fp): + return fp.name.endswith('pdf') + + def render(self, fp, path): + return ''' + + + + + + + +
+ '''.format( + url='path/to/pdf' + ) diff --git a/renderer/pdf/static/js/pdf.js b/renderer/pdf/static/js/pdf.js new file mode 100644 index 000000000..47846b329 --- /dev/null +++ b/renderer/pdf/static/js/pdf.js @@ -0,0 +1 @@ +someone set up us the bomb diff --git a/renderer/text/__init__.py b/renderer/text/__init__.py new file mode 100644 index 000000000..667ac5329 --- /dev/null +++ b/renderer/text/__init__.py @@ -0,0 +1,20 @@ +from .. import FileRenderer + +class TextRenderer(FileRenderer): + + def detect(self, fp): + fname = fp.name + for ext in ['txt']: + if fname.endswith(ext): + return True + return False + + def render(self, fp, path): + fname = fp.name + return '
{}
'.format(fp.read()) + + def export_text(self, fp): + return fp.read(), '.txt' + + def export_blah(self, fp): + return fp.read(), '.bla' diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 000000000..345f55a6f --- /dev/null +++ b/requirements.txt @@ -0,0 +1,8 @@ +Flask==0.10.1 +Jinja2==2.7.1 +MarkupSafe==0.18 +PIL==1.1.7 +Pygments==1.6 +Werkzeug==0.9.4 +itsdangerous==0.23 +wsgiref==0.1.2