Skip to content

Add basic renderers #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Oct 16, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added examples/cat.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions examples/test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import this
foo = 'lol'
68 changes: 62 additions & 6 deletions main.py
Original file line number Diff line number Diff line change
@@ -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/<module/static
@app.route('/static/<module>/<path:filepath>')
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/<renderer>/<filename>/', 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 += '<a href="/example/{filename}">{filename}</a><br />'.format(
html += '<a href="/render/{filename}">{filename}</a><br />'.format(
filename=fn)
return html

@app.route('/example/<filename>')
def examples(filename):
@app.route('/render/<filename>')
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)
app.run(debug=True, port=5001)
86 changes: 86 additions & 0 deletions renderer/__init__.py
Original file line number Diff line number Diff line change
@@ -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 = [
'<option value="{}">{}</option>'.format(
exporter, exporter.capitalize()
)
for exporter in self.exporters
]

return '''
<form method="post" action="/export/{klass}/{filename}/">
<select name="exporter">
{options}
</select>
<input type="submit" value="Submit" />
</form>'''.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
25 changes: 25 additions & 0 deletions renderer/code/__init__.py
Original file line number Diff line number Diff line change
@@ -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 '<link rel="stylesheet" href="/static/code/css/style.css" />' + '\n' + highlight
62 changes: 62 additions & 0 deletions renderer/code/static/css/style.css
Original file line number Diff line number Diff line change
@@ -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 */
35 changes: 35 additions & 0 deletions renderer/image/__init__.py
Original file line number Diff line number Diff line change
@@ -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 '<img src="{}"{} />'.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'
25 changes: 23 additions & 2 deletions renderer/pdf/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,23 @@
class PdfRenderer(object):
pass
from .. import FileRenderer

class PdfRenderer(FileRenderer):

def detect(self, fp):
return fp.name.endswith('pdf')

def render(self, fp, path):
return '''

<!-- Include PDF.JS -->
<script type="text/javascript src="/static/pdf/js/pdf.js"></script>
<script type="text/javascript src="/static/pdf/js/???"></script>

<script type="text/javascript">
var pdf = PDFJS.getDocument('{url}');
pdf.then(renderPage);
</script>

<div id="pdfContainer"></div>
'''.format(
url='path/to/pdf'
)
1 change: 1 addition & 0 deletions renderer/pdf/static/js/pdf.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
someone set up us the bomb
20 changes: 20 additions & 0 deletions renderer/text/__init__.py
Original file line number Diff line number Diff line change
@@ -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 '<pre>{}</pre>'.format(fp.read())

def export_text(self, fp):
return fp.read(), '.txt'

def export_blah(self, fp):
return fp.read(), '.bla'
8 changes: 8 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -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