initial commit
This commit is contained in:
22
LICENSE.md
Normal file
22
LICENSE.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
Copyright 2018 Walter Oggioni
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person
|
||||||
|
obtaining a copy of this software and associated documentation
|
||||||
|
files (the "Software"), to deal in the Software without
|
||||||
|
restriction, including without limitation the rights to use,
|
||||||
|
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the
|
||||||
|
Software is furnished to do so, subject to the following
|
||||||
|
conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
OTHER DEALINGS IN THE SOFTWARE.
|
156
md2html/md2html.py
Normal file
156
md2html/md2html.py
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import sys
|
||||||
|
import markdown
|
||||||
|
from os.path import basename, dirname
|
||||||
|
import json
|
||||||
|
|
||||||
|
TEMPLATE = """
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link href="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.3.0/css/bootstrap-combined.min.css" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
body {{
|
||||||
|
font-family: sans-serif;
|
||||||
|
}}
|
||||||
|
code, pre {{
|
||||||
|
font-family: monospace;
|
||||||
|
}}
|
||||||
|
h1 code,
|
||||||
|
h2 code,
|
||||||
|
h3 code,
|
||||||
|
h4 code,
|
||||||
|
h5 code,
|
||||||
|
h6 code {{
|
||||||
|
font-size: inherit;
|
||||||
|
}}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script type=\"text/javascript\">
|
||||||
|
let eventSource = new EventSource("/stream");
|
||||||
|
eventSource.addEventListener('reload', function(e) {{
|
||||||
|
window.location.reload(true);
|
||||||
|
}});
|
||||||
|
</script>
|
||||||
|
<div class="container">
|
||||||
|
{content}
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class ServerSentEvent(object):
|
||||||
|
|
||||||
|
def __init__(self, id=None, event=None, data=None, retry=1000):
|
||||||
|
self.id = id
|
||||||
|
self.event = event
|
||||||
|
self.data = json.dumps(data)
|
||||||
|
self.retry = retry
|
||||||
|
|
||||||
|
def encode(self):
|
||||||
|
if not self.data:
|
||||||
|
return ""
|
||||||
|
lines = [f"{key}: {value}" for key, value in vars(self).items() if value]
|
||||||
|
return "%s\n\n" % "\n".join(lines)
|
||||||
|
|
||||||
|
def parse_args(args=None):
|
||||||
|
parser = argparse.ArgumentParser(description='Make a complete, styled HTML document from a Markdown file.')
|
||||||
|
parser.add_argument('mdfile', help='File to convert. Defaults to stdin.')
|
||||||
|
parser.add_argument('-o', '--out', help='Output file name. Defaults to stdout.')
|
||||||
|
parser.add_argument('-r', '--raw', action='store_true',
|
||||||
|
help='Just output a raw html fragment, as returned from the markdown module')
|
||||||
|
parser.add_argument('-e', '--extensions', nargs='+', default=['extra', 'smarty', 'tables'],
|
||||||
|
help='Activate specified markdown extensions (defaults to "extra smarty tables")')
|
||||||
|
|
||||||
|
try:
|
||||||
|
import inotify
|
||||||
|
import flask
|
||||||
|
import gevent
|
||||||
|
import signal
|
||||||
|
parser.add_argument('-w', '--watch', action='store_true',
|
||||||
|
help='Watch specified source file and rerun the compilation for every time it changes')
|
||||||
|
parser.add_argument('-p', '--port', default=5000, type=int,
|
||||||
|
help='Specify http server port (defaults to 5000)')
|
||||||
|
parser.add_argument('-i', '--interface', default='',
|
||||||
|
help='Specify http server listen interface (defaults to localhost)')
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
return parser.parse_args(args)
|
||||||
|
|
||||||
|
def compile_html(mdfile=None, extensions=None, raw=None, **kwargs):
|
||||||
|
html = None
|
||||||
|
with mdfile and open(mdfile, 'r') or sys.stdin as instream:
|
||||||
|
html = markdown.markdown(instream.read(), extensions=extensions, output_format='html5')
|
||||||
|
if raw:
|
||||||
|
doc = html
|
||||||
|
else:
|
||||||
|
doc = TEMPLATE.format(**dict(content=html))
|
||||||
|
return doc
|
||||||
|
|
||||||
|
def write_html(out=None, **kwargs):
|
||||||
|
doc = compile_html(**kwargs)
|
||||||
|
with (out and open(out, 'w')) or sys.stdout as outstream:
|
||||||
|
outstream.write(doc)
|
||||||
|
|
||||||
|
def main(args=None):
|
||||||
|
import signal
|
||||||
|
args = parse_args(args)
|
||||||
|
exit = False
|
||||||
|
def sigint_handler(signum, frame):
|
||||||
|
nonlocal exit
|
||||||
|
exit = True
|
||||||
|
handlers = (sigint_handler, signal.getsignal(signal.SIGINT))
|
||||||
|
signal.signal(signal.SIGINT, lambda signum, frame: [handler(signum, frame) for handler in handlers])
|
||||||
|
if hasattr(args, 'watch') and args.watch:
|
||||||
|
import threading
|
||||||
|
from flask import Flask, Response
|
||||||
|
from gevent.pywsgi import WSGIServer
|
||||||
|
condition_variable = threading.Condition()
|
||||||
|
def watch_file():
|
||||||
|
import inotify.adapters
|
||||||
|
nonlocal condition_variable, exit
|
||||||
|
watcher = inotify.adapters.Inotify()
|
||||||
|
watcher.add_watch(dirname(args.mdfile))
|
||||||
|
target_file = basename(args.mdfile)
|
||||||
|
while True:
|
||||||
|
if exit:
|
||||||
|
break
|
||||||
|
for event in watcher.event_gen(yield_nones=True, timeout_s=1):
|
||||||
|
if not event:
|
||||||
|
continue
|
||||||
|
(_, event_type, path, filename) = event
|
||||||
|
if filename == target_file and len(set(event_type).intersection(
|
||||||
|
{'IN_CREATE', 'IN_MODIFY', 'IN_CLOSE_WRITE'})):
|
||||||
|
condition_variable.acquire()
|
||||||
|
condition_variable.notify_all()
|
||||||
|
condition_variable.release()
|
||||||
|
|
||||||
|
file_watcher = threading.Thread(target=watch_file)
|
||||||
|
file_watcher.start()
|
||||||
|
app = Flask(__name__)
|
||||||
|
@app.route('/')
|
||||||
|
def get():
|
||||||
|
return Response(compile_html(**vars(args)), mimetype='text/html')
|
||||||
|
|
||||||
|
@app.route("/stream")
|
||||||
|
def stream():
|
||||||
|
nonlocal condition_variable
|
||||||
|
def gen():
|
||||||
|
while True:
|
||||||
|
condition_variable.acquire()
|
||||||
|
condition_variable.wait()
|
||||||
|
sse = ServerSentEvent(event='reload')
|
||||||
|
return sse.encode()
|
||||||
|
return Response(gen(), mimetype="text/event-stream")
|
||||||
|
|
||||||
|
server = WSGIServer((args.interface, args.port), app, environ={'wsgi.multithread': True})
|
||||||
|
server.serve_forever()
|
||||||
|
else:
|
||||||
|
write_html(**vars(args))
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
40
setup.py
Normal file
40
setup.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
from os.path import join, dirname
|
||||||
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
|
|
||||||
|
def read(fname):
|
||||||
|
return open(join(dirname(__file__), fname)).read()
|
||||||
|
|
||||||
|
|
||||||
|
config = {
|
||||||
|
'name': "md2html",
|
||||||
|
'version': "0.1",
|
||||||
|
'author': "Walter Oggioni",
|
||||||
|
'author_email': "oggioni.walter@gmail.com",
|
||||||
|
'description': ("Various development utility scripts"),
|
||||||
|
'long_description': '',
|
||||||
|
'license': "MIT",
|
||||||
|
'keywords': "build",
|
||||||
|
'url': "https://github.com/oggio88/md2html",
|
||||||
|
'packages': ['md2html'],
|
||||||
|
'include_package_data': True,
|
||||||
|
'classifiers': [
|
||||||
|
'Development Status :: 3 - Alpha',
|
||||||
|
'Topic :: Utilities',
|
||||||
|
'License :: OSI Approved :: MIT License',
|
||||||
|
'Intended Audience :: System Administrators',
|
||||||
|
'Intended Audience :: Developers',
|
||||||
|
'Environment :: Console',
|
||||||
|
'License :: OSI Approved :: MIT License',
|
||||||
|
'Programming Language :: Python :: 3',
|
||||||
|
],
|
||||||
|
'install_requires': [
|
||||||
|
'markdown'
|
||||||
|
],
|
||||||
|
"entry_points": {
|
||||||
|
'console_scripts': [
|
||||||
|
'md2html=md2html.md2html:main',
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setup(**config)
|
Reference in New Issue
Block a user