From 085b236d9a169a1815a6117caab5894521e59672 Mon Sep 17 00:00:00 2001 From: bozokopic Date: Mon, 24 Apr 2017 23:40:00 +0200 Subject: heroku init --- .gitignore | 16 +-- Procfile | 1 + app.json | 9 ++ bin/compile | 6 ++ bin/detech | 8 ++ dodo.py | 28 +++--- playground/server/run.sh | 8 +- runtime.txt | 1 + src_js/opcut/common.js | 6 +- src_js/opcut/lenses.js | 42 ++++---- src_js/opcut/renderer.js | 252 +++++++++++++++++++++++------------------------ src_js/opcut/vt.js | 16 +-- src_py/opcut/main.py | 160 +++++++++++++++--------------- src_py/opcut/util.py | 80 +++++++-------- 14 files changed, 329 insertions(+), 304 deletions(-) create mode 100644 Procfile create mode 100644 app.json create mode 100644 bin/compile create mode 100644 bin/detech create mode 100644 runtime.txt diff --git a/.gitignore b/.gitignore index f0ef978..03cceb2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,8 @@ -__pycache__ -/build -/dist -/.doit.* -/yarn.lock -/node_modules -/src_py/opcut/json_validator.py -/src_js/opcut/validator.js +__pycache__ +/build +/dist +/.doit.* +/yarn.lock +/node_modules +/src_py/opcut/json_validator.py +/src_js/opcut/validator.js diff --git a/Procfile b/Procfile new file mode 100644 index 0000000..28fe25a --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web: cd dist && python -m opcut.main --ui-addr http://0.0.0.0:$PORT diff --git a/app.json b/app.json new file mode 100644 index 0000000..201d708 --- /dev/null +++ b/app.json @@ -0,0 +1,9 @@ +{ + "name": "opcut", + "website": "http://opcut.heroku.com", + "repository": "https://github.com/bozokopic/opcut", + "buildpacks": [ + {"url": "heroku/python"}, + {"url": "https://github.com/bozokopic/opcut"} + ] +} diff --git a/bin/compile b/bin/compile new file mode 100644 index 0000000..6971f42 --- /dev/null +++ b/bin/compile @@ -0,0 +1,6 @@ +#!/bin/sh + +cd $1 +pip install -r requirements.pip.txt +doit clean_all +doit diff --git a/bin/detech b/bin/detech new file mode 100644 index 0000000..6c1362c --- /dev/null +++ b/bin/detech @@ -0,0 +1,8 @@ +#!/bin/sh + +if [ -f $1/dodo.py ]; then + echo "opcut build" + exit 0 +else + exit 1 +fi diff --git a/dodo.py b/dodo.py index f1a6eb5..a465808 100644 --- a/dodo.py +++ b/dodo.py @@ -1,14 +1,14 @@ -import sys -import os - -sys.path += ['src_py'] - -os.environ['PYTHONPATH'] = os.pathsep.join(map( - os.path.abspath, ['src_py'])) - -DOIT_CONFIG = { - 'backend': 'sqlite3', - 'default_tasks': ['dist_build'], - 'verbosity': 2} - -from opcut.doit.main import * # NOQA +import sys +import os + +sys.path += ['src_py'] + +os.environ['PYTHONPATH'] = os.pathsep.join(map( + os.path.abspath, ['src_py'])) + +DOIT_CONFIG = { + 'backend': 'sqlite3', + 'default_tasks': ['dist_build'], + 'verbosity': 2} + +from opcut.doit.main import * # NOQA diff --git a/playground/server/run.sh b/playground/server/run.sh index 5b7cc98..0f4984a 100644 --- a/playground/server/run.sh +++ b/playground/server/run.sh @@ -1,4 +1,4 @@ -#!/bin/bash - -export PYTHONPATH=../../src_py -python -m opcut.main --ui-path ../../build/jsopcut +#!/bin/bash + +export PYTHONPATH=../../src_py +python -m opcut.main --ui-path ../../build/jsopcut diff --git a/runtime.txt b/runtime.txt new file mode 100644 index 0000000..c91e43b --- /dev/null +++ b/runtime.txt @@ -0,0 +1 @@ +python-3.6.1 diff --git a/src_js/opcut/common.js b/src_js/opcut/common.js index adc7191..7e63d19 100644 --- a/src_js/opcut/common.js +++ b/src_js/opcut/common.js @@ -1,3 +1,3 @@ - - -export const defaultState = {}; + + +export const defaultState = {}; diff --git a/src_js/opcut/lenses.js b/src_js/opcut/lenses.js index 8458dbd..39da314 100644 --- a/src_js/opcut/lenses.js +++ b/src_js/opcut/lenses.js @@ -1,21 +1,21 @@ -import R from 'ramda'; - - -export const index = R.lensIndex; - -export const prop = R.lensProp; - -export function path(...xs) { - return R.reduce((acc, i) => R.compose(acc, pathParamToLens(i)), - R.identity, xs); -} - -function pathParamToLens(x) { - switch (typeof(x)) { - case 'function': return x; - case 'number': return index(x); - case 'string': return prop(x); - case 'object': if (Array.isArray(x)) return R.apply(path, x); - } - throw 'Invalid path parameter'; -} +import R from 'ramda'; + + +export const index = R.lensIndex; + +export const prop = R.lensProp; + +export function path(...xs) { + return R.reduce((acc, i) => R.compose(acc, pathParamToLens(i)), + R.identity, xs); +} + +function pathParamToLens(x) { + switch (typeof(x)) { + case 'function': return x; + case 'number': return index(x); + case 'string': return prop(x); + case 'object': if (Array.isArray(x)) return R.apply(path, x); + } + throw 'Invalid path parameter'; +} diff --git a/src_js/opcut/renderer.js b/src_js/opcut/renderer.js index 22a74c6..6738141 100644 --- a/src_js/opcut/renderer.js +++ b/src_js/opcut/renderer.js @@ -1,126 +1,126 @@ -import Delegator from 'dom-delegator'; -import R from 'ramda'; -import bean from 'bean'; -import vh from 'virtual-dom/h'; -import diff from 'virtual-dom/diff'; -import patch from 'virtual-dom/patch'; -import createElement from 'virtual-dom/create-element'; - -import * as l from 'opcut/lenses'; - - -const delegator = Delegator(); -const vhTypes = ['VirtualNode', 'Widget']; - - -function vhFromArray(node) { - if (!node) - return []; - if (typeof node == 'string' || vhTypes.includes(node.type)) - return node; - if (!Array.isArray(node)) - throw 'Invalid node structure'; - if (node.length < 1) - return []; - if (typeof node[0] != 'string') - return node.map(vhFromArray); - let hasProps = (node.length > 1 && - typeof node[1] == 'object' && - !Array.isArray(node[1]) && - !vhTypes.includes(node[1].type)); - let children = R.flatten(node.slice(hasProps ? 2 : 1).map(vhFromArray)); - let result = hasProps ? vh(node[0], node[1], children) : - vh(node[0], children); - - // disable SoftSetHook for input - if (result.tagName == 'INPUT' && - result.properties && - result.properties.value && - typeof(result.properties.value) === 'object') { - result.properties.value = result.properties.value.value; - } - - return result; -} - - -class VTreeRenderer { - - constructor(el) { - this._el = el; - this._vtree = null; - } - - render(vtree) { - let vt = vhFromArray(vtree); - if (vt.type == 'VirtualNode') { - if (this._vtree) { - let d = diff(this._vtree, vt); - patch(this._el.firstChild, d); - } else { - while (this._el.firstChild) - this._el.removeChild(this._el.firstChild); - this._el.appendChild(createElement(vt)); - } - this._vtree = vt; - } else { - this._vtree = null; - while (this._el.firstChild) - this._el.removeChild(this._el.firstChild); - } - } - -} - - -export class Renderer { - - constructor(el, initState, vtCb) { - this.init(el, initState, vtCb); - } - - init(el, initState, vtCb) { - this._state = null; - this._changeCbs = []; - this._vtCb = vtCb; - this._r = new VTreeRenderer(el || document.querySelector('body')); - if (initState) - this.change(R.identity, _ => initState); - } - - view(...lenses) { - return R.view(R.apply(l.path, lenses), this._state); - } - - set(lens, value) { - if (arguments.length < 2) { - value = lens; - lens = R.identity; - } - this.change(lens, _ => value); - } - - change(lens, cb) { - if (arguments.length < 2) { - cb = lens; - lens = R.identity; - } - if (this._changeCbs.push(cb) > 1) - return; - let startingSubState = this.view(lens); - while (this._changeCbs.length > 0) { - this._state = R.over(l.path(lens), this._changeCbs[0], this._state); - this._changeCbs.shift(); - } - if (!this._vtCb || - (this._state && R.equals(startingSubState, this.view(lens)))) - return; - this._r.render(this._vtCb(this._state)); - bean.fire(this, 'render', this._state); - } - -} - - -const defaultRenderer = new Renderer(); -export default defaultRenderer; +import Delegator from 'dom-delegator'; +import R from 'ramda'; +import bean from 'bean'; +import vh from 'virtual-dom/h'; +import diff from 'virtual-dom/diff'; +import patch from 'virtual-dom/patch'; +import createElement from 'virtual-dom/create-element'; + +import * as l from 'opcut/lenses'; + + +const delegator = Delegator(); +const vhTypes = ['VirtualNode', 'Widget']; + + +function vhFromArray(node) { + if (!node) + return []; + if (typeof node == 'string' || vhTypes.includes(node.type)) + return node; + if (!Array.isArray(node)) + throw 'Invalid node structure'; + if (node.length < 1) + return []; + if (typeof node[0] != 'string') + return node.map(vhFromArray); + let hasProps = (node.length > 1 && + typeof node[1] == 'object' && + !Array.isArray(node[1]) && + !vhTypes.includes(node[1].type)); + let children = R.flatten(node.slice(hasProps ? 2 : 1).map(vhFromArray)); + let result = hasProps ? vh(node[0], node[1], children) : + vh(node[0], children); + + // disable SoftSetHook for input + if (result.tagName == 'INPUT' && + result.properties && + result.properties.value && + typeof(result.properties.value) === 'object') { + result.properties.value = result.properties.value.value; + } + + return result; +} + + +class VTreeRenderer { + + constructor(el) { + this._el = el; + this._vtree = null; + } + + render(vtree) { + let vt = vhFromArray(vtree); + if (vt.type == 'VirtualNode') { + if (this._vtree) { + let d = diff(this._vtree, vt); + patch(this._el.firstChild, d); + } else { + while (this._el.firstChild) + this._el.removeChild(this._el.firstChild); + this._el.appendChild(createElement(vt)); + } + this._vtree = vt; + } else { + this._vtree = null; + while (this._el.firstChild) + this._el.removeChild(this._el.firstChild); + } + } + +} + + +export class Renderer { + + constructor(el, initState, vtCb) { + this.init(el, initState, vtCb); + } + + init(el, initState, vtCb) { + this._state = null; + this._changeCbs = []; + this._vtCb = vtCb; + this._r = new VTreeRenderer(el || document.querySelector('body')); + if (initState) + this.change(R.identity, _ => initState); + } + + view(...lenses) { + return R.view(R.apply(l.path, lenses), this._state); + } + + set(lens, value) { + if (arguments.length < 2) { + value = lens; + lens = R.identity; + } + this.change(lens, _ => value); + } + + change(lens, cb) { + if (arguments.length < 2) { + cb = lens; + lens = R.identity; + } + if (this._changeCbs.push(cb) > 1) + return; + let startingSubState = this.view(lens); + while (this._changeCbs.length > 0) { + this._state = R.over(l.path(lens), this._changeCbs[0], this._state); + this._changeCbs.shift(); + } + if (!this._vtCb || + (this._state && R.equals(startingSubState, this.view(lens)))) + return; + this._r.render(this._vtCb(this._state)); + bean.fire(this, 'render', this._state); + } + +} + + +const defaultRenderer = new Renderer(); +export default defaultRenderer; diff --git a/src_js/opcut/vt.js b/src_js/opcut/vt.js index e199bbe..2754877 100644 --- a/src_js/opcut/vt.js +++ b/src_js/opcut/vt.js @@ -1,8 +1,8 @@ - - -export function main() { - return ['div', - 'application in development - ', - ['a', {href: 'https://github.com/bozokopic/opcut'}, 'Github page'] - ]; -} + + +export function main() { + return ['div', + 'application in development - ', + ['a', {href: 'https://github.com/bozokopic/opcut'}, 'Github page'] + ]; +} diff --git a/src_py/opcut/main.py b/src_py/opcut/main.py index d6b42a9..917cbb2 100644 --- a/src_py/opcut/main.py +++ b/src_py/opcut/main.py @@ -1,80 +1,80 @@ -import sys -import argparse -import yaml -import logging.config -import urllib.parse -import aiohttp.web -import ssl -import asyncio -import contextlib - -from opcut import util -import opcut.json_validator - - -def main(): - args = _create_parser().parse_args() - - if args.log_conf_path: - with open(args.log_conf_path, encoding='utf-8') as log_conf_file: - log_conf = yaml.safe_load(log_conf_file) - opcut.json_validator.validate(log_conf, 'opcut://logging.yaml#') - logging.config.dictConfig(log_conf) - - util.run_until_complete_without_interrupt(async_main(args)) - - -async def async_main(args): - - addr = urllib.parse.urlparse(args.ui_addr) - - if addr.scheme == 'https': - ssl_ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23) - ssl_ctx.load_cert_chain(args.ui_pem_path) - else: - ssl_ctx = None - - app = aiohttp.web.Application() - app.router.add_route('GET', '/', - lambda req: aiohttp.web.HTTPFound('/index.html')) - app.router.add_static('/', args.ui_path) - app_handler = app.make_handler() - - srv = await asyncio.get_event_loop().create_server( - app_handler, host=addr.hostname, port=addr.port, ssl=ssl_ctx) - - with contextlib.suppress(asyncio.CancelledError): - await asyncio.Future() - - srv.close() - await srv.wait_closed() - await app.shutdown() - await app_handler.finish_connections(0) - await app.cleanup() - - -def _create_parser(): - parser = argparse.ArgumentParser(prog='opcut') - parser.add_argument( - '--ui-addr', default='http://0.0.0.0:8080', - metavar='addr', dest='ui_addr', - help="address of listening web ui socket formated as " - "'://:' - is 'http' or 'https'; " - " is hostname; is tcp port number " - "(default http://0.0.0.0:8080)") - parser.add_argument( - '--ui-path', default='web', - metavar='path', dest='ui_path', - help="web front-end path (default web)") - parser.add_argument( - '--ui-pem', default=None, - metavar='path', dest='ui_pem_path', - help="web front-end pem file path - required for https") - parser.add_argument( - '--log', default=None, metavar='path', dest='log_conf_path', - help="logging configuration") - return parser - - -if __name__ == '__main__': - sys.exit(main()) +import sys +import argparse +import yaml +import logging.config +import urllib.parse +import aiohttp.web +import ssl +import asyncio +import contextlib + +from opcut import util +import opcut.json_validator + + +def main(): + args = _create_parser().parse_args() + + if args.log_conf_path: + with open(args.log_conf_path, encoding='utf-8') as log_conf_file: + log_conf = yaml.safe_load(log_conf_file) + opcut.json_validator.validate(log_conf, 'opcut://logging.yaml#') + logging.config.dictConfig(log_conf) + + util.run_until_complete_without_interrupt(async_main(args)) + + +async def async_main(args): + + addr = urllib.parse.urlparse(args.ui_addr) + + if addr.scheme == 'https': + ssl_ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23) + ssl_ctx.load_cert_chain(args.ui_pem_path) + else: + ssl_ctx = None + + app = aiohttp.web.Application() + app.router.add_route('GET', '/', + lambda req: aiohttp.web.HTTPFound('/index.html')) + app.router.add_static('/', args.ui_path) + app_handler = app.make_handler() + + srv = await asyncio.get_event_loop().create_server( + app_handler, host=addr.hostname, port=addr.port, ssl=ssl_ctx) + + with contextlib.suppress(asyncio.CancelledError): + await asyncio.Future() + + srv.close() + await srv.wait_closed() + await app.shutdown() + await app_handler.finish_connections(0) + await app.cleanup() + + +def _create_parser(): + parser = argparse.ArgumentParser(prog='opcut') + parser.add_argument( + '--ui-addr', default='http://0.0.0.0:8080', + metavar='addr', dest='ui_addr', + help="address of listening web ui socket formated as " + "'://:' - is 'http' or 'https'; " + " is hostname; is tcp port number " + "(default http://0.0.0.0:8080)") + parser.add_argument( + '--ui-path', default='web', + metavar='path', dest='ui_path', + help="web front-end path (default web)") + parser.add_argument( + '--ui-pem', default=None, + metavar='path', dest='ui_pem_path', + help="web front-end pem file path - required for https") + parser.add_argument( + '--log', default=None, metavar='path', dest='log_conf_path', + help="logging configuration") + return parser + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/src_py/opcut/util.py b/src_py/opcut/util.py index 4855bc5..3ceb843 100644 --- a/src_py/opcut/util.py +++ b/src_py/opcut/util.py @@ -1,40 +1,40 @@ -import contextlib -import asyncio -import sys - - -def run_until_complete_without_interrupt(future): - """Run event loop until future or coroutine is done - - Args: - future (Awaitable): future or coroutine - - Returns: - Any: provided future's result - - KeyboardInterrupt is suppressed (while event loop is running) and is mapped - to single cancelation of running task. If multipple KeyboardInterrupts - occur, task is canceled only once. - - """ - async def ping_loop(): - with contextlib.suppress(asyncio.CancelledError): - while True: - await asyncio.sleep(1) - - task = asyncio.ensure_future(future) - if sys.platform == 'win32': - ping_loop_task = asyncio.ensure_future(ping_loop()) - with contextlib.suppress(KeyboardInterrupt): - asyncio.get_event_loop().run_until_complete(task) - asyncio.get_event_loop().call_soon(task.cancel) - if sys.platform == 'win32': - asyncio.get_event_loop().call_soon(ping_loop_task.cancel) - while not task.done(): - with contextlib.suppress(KeyboardInterrupt): - asyncio.get_event_loop().run_until_complete(task) - if sys.platform == 'win32': - while not ping_loop_task.done(): - with contextlib.suppress(KeyboardInterrupt): - asyncio.get_event_loop().run_until_complete(ping_loop_task) - return task.result() +import contextlib +import asyncio +import sys + + +def run_until_complete_without_interrupt(future): + """Run event loop until future or coroutine is done + + Args: + future (Awaitable): future or coroutine + + Returns: + Any: provided future's result + + KeyboardInterrupt is suppressed (while event loop is running) and is mapped + to single cancelation of running task. If multipple KeyboardInterrupts + occur, task is canceled only once. + + """ + async def ping_loop(): + with contextlib.suppress(asyncio.CancelledError): + while True: + await asyncio.sleep(1) + + task = asyncio.ensure_future(future) + if sys.platform == 'win32': + ping_loop_task = asyncio.ensure_future(ping_loop()) + with contextlib.suppress(KeyboardInterrupt): + asyncio.get_event_loop().run_until_complete(task) + asyncio.get_event_loop().call_soon(task.cancel) + if sys.platform == 'win32': + asyncio.get_event_loop().call_soon(ping_loop_task.cancel) + while not task.done(): + with contextlib.suppress(KeyboardInterrupt): + asyncio.get_event_loop().run_until_complete(task) + if sys.platform == 'win32': + while not ping_loop_task.done(): + with contextlib.suppress(KeyboardInterrupt): + asyncio.get_event_loop().run_until_complete(ping_loop_task) + return task.result() -- cgit v1.2.3-70-g09d2