diff options
| author | bozokopic <bozo.kopic@gmail.com> | 2017-04-24 23:40:00 +0200 |
|---|---|---|
| committer | bozokopic <bozo.kopic@gmail.com> | 2017-04-24 23:40:00 +0200 |
| commit | 085b236d9a169a1815a6117caab5894521e59672 (patch) | |
| tree | 3d3220936a877fbe75206e2effc581c548d568ec | |
| parent | ed9f4f8991f4e8b31270dbbf17215732d73c9e37 (diff) | |
heroku init
| -rw-r--r-- | .gitignore | 16 | ||||
| -rw-r--r-- | Procfile | 1 | ||||
| -rw-r--r-- | app.json | 9 | ||||
| -rw-r--r-- | bin/compile | 6 | ||||
| -rw-r--r-- | bin/detech | 8 | ||||
| -rw-r--r-- | dodo.py | 28 | ||||
| -rw-r--r-- | playground/server/run.sh | 8 | ||||
| -rw-r--r-- | runtime.txt | 1 | ||||
| -rw-r--r-- | src_js/opcut/common.js | 6 | ||||
| -rw-r--r-- | src_js/opcut/lenses.js | 42 | ||||
| -rw-r--r-- | src_js/opcut/renderer.js | 252 | ||||
| -rw-r--r-- | src_js/opcut/vt.js | 16 | ||||
| -rw-r--r-- | src_py/opcut/main.py | 160 | ||||
| -rw-r--r-- | src_py/opcut/util.py | 80 |
14 files changed, 329 insertions, 304 deletions
@@ -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 @@ -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 "
- "'<type>://<host>:<port>' - <type> is 'http' or 'https'; "
- "<host> is hostname; <port> 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 " + "'<type>://<host>:<port>' - <type> is 'http' or 'https'; " + "<host> is hostname; <port> 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() |
