honix / pyno Goto Github PK
View Code? Open in Web Editor NEWPython-based visual programming
License: MIT License
Python-based visual programming
License: MIT License
See below how to make it run on Python 3.11 and why it fails with pyglet version 2.0.7:
import pyglet #o#0#o# requires pyglet version 1.5.27
# With pyglet 2.0.7 there is an error:
# File "~/.local/lib/python3.11/site-packages/pyno/draw.py", line 7, in <module>
# class UIGroup(pyglet.graphics.OrderedGroup):
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
# AttributeError: module 'pyglet.graphics' has no attribute 'OrderedGroup'
# Because:
# pyglet-2.0.7/doc/programming_guide/migration.rst:50:`OrderedGroup` has been removed. Instead, all Groups now have an `order`
from inspect import getfullargspec as getargspec #o#0#o# OK on Python 3.11
# from inspect import getargspec # fails on Python 3.11 with:
# from inspect import getargspec
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
# ImportError: cannot import name 'getargspec' from 'inspect' (/usr/local/lib/python3.11/inspect.py)
Im considering to develop flow-control mechanics for pyno.
Right now pyno do the update of all nodes 60 times per second. It is good for real-time systems, but bad for other classes of programs, for example calculator. It is also a waste of processor time.
This aproach also seen in vvvv visual language. It also reminds me functional nature of algorithms. In some cases it seems more natural to just define operators, put some data and dynamicly (!) see the process. Dynamic nature is one of main goals of pyno.
Another approach is flow-control - the way to execute nodes in order you need. It will allow to do condition branches and loops (can be main loop done this way?).
Examples is UnrealEngine's blueprints, cables.gl and VL from vvvv guys.
They differently solve this concept. I mostly like cables.gl approach, it bulds around signal concept as i understand it. There is signal and data wires. Signal wire activates execution of node. The semantics of signal wire may vary from node to node.
My goal is to do easy to understand but powerfull flow-control concept, so pyno will be used for broader classes of programming.
Ideas are welcome.
Python 3.6.3, OS X 10.11.6 (15G18013)
apollov@apollov-macbook-local-en0 ~/work/Pyno % python Pyno.py
Loading...
2017-12-19 18:26:19.620 python[94919:2273428] -[NSApplication _setup:]: unrecognized selector sent to instance 0x7fdf63113e40
2017-12-19 18:26:19.623 python[94919:2273428] An uncaught exception was raised
2017-12-19 18:26:19.623 python[94919:2273428] -[NSApplication _setup:]: unrecognized selector sent to instance 0x7fdf63113e40
2017-12-19 18:26:19.623 python[94919:2273428] (
0 CoreFoundation 0x00007fff96e34452 __exceptionPreprocess + 178
1 libobjc.A.dylib 0x00007fff93b4b73c objc_exception_throw + 48
2 CoreFoundation 0x00007fff96e9e18d -[NSObject(NSObject) doesNotRecognizeSelector:] + 205
3 CoreFoundation 0x00007fff96da44c1 ___forwarding___ + 1009
4 CoreFoundation 0x00007fff96da4048 _CF_forwarding_prep_0 + 120
5 Tk 0x0000000106c61948 TkpInit + 476
6 Tk 0x0000000106bdca6e Tk_Init + 1799
7 _tkinter.cpython-36m-darwin.so 0x0000000106ab9dd2 Tcl_AppInit + 82
8 _tkinter.cpython-36m-darwin.so 0x0000000106ab4d48 _tkinter_create + 1112
9 python 0x000000010029212c _PyCFunction_FastCallDict + 172
10 python 0x000000010031eb0f call_function + 479
11 python 0x000000010031b4f2 _PyEval_EvalFrameDefault + 26162
12 python 0x000000010031fa19 _PyEval_EvalCodeWithName + 3641
13 python 0x0000000100320552 _PyFunction_FastCallDict + 802
14 python 0x0000000100248018 _PyObject_FastCallDict + 360
15 python 0x0000000100248135 _PyObject_Call_Prepend + 149
16 python 0x0000000100247d65 PyObject_Call + 101
17 python 0x00000001002aa89e slot_tp_init + 158
18 python 0x00000001002a69e9 type_call + 313
19 python 0x0000000100247fe5 _PyObject_FastCallDict + 309
20 python 0x000000010031ea09 call_function + 217
21 python 0x000000010031b4f2 _PyEval_EvalFrameDefault + 26162
22 python 0x000000010032016e fast_function + 574
23 python 0x000000010031eae9 call_function + 441
24 python 0x000000010031b4f2 _PyEval_EvalFrameDefault + 26162
25 python 0x000000010032016e fast_function + 574
26 python 0x000000010031eae9 call_function + 441
27 python 0x000000010031b4f2 _PyEval_EvalFrameDefault + 26162
28 python 0x00000001003205ec _PyFunction_FastCallDict + 956
29 python 0x0000000100248018 _PyObject_FastCallDict + 360
30 python 0x0000000100248135 _PyObject_Call_Prepend + 149
31 python 0x0000000100247d65 PyObject_Call + 101
32 python 0x000000010031b80a _PyEval_EvalFrameDefault + 26954
33 python 0x000000010031fa19 _PyEval_EvalCodeWithName + 3641
34 python 0x0000000100314e6b PyEval_EvalCodeEx + 107
35 python 0x0000000100270c1d function_call + 381
36 python 0x0000000100247d65 PyObject_Call + 101
37 python 0x000000010031b80a _PyEval_EvalFrameDefault + 26954
38 python 0x000000010031fa19 _PyEval_EvalCodeWithName + 3641
39 python 0x0000000100320216 fast_function + 742
40 python 0x000000010031eae9 call_function + 441
41 python 0x000000010031b4f2 _PyEval_EvalFrameDefault + 26162
42 python 0x000000010031fa19 _PyEval_EvalCodeWithName + 3641
43 python 0x0000000100314e6b PyEval_EvalCodeEx + 107
44 python 0x0000000100270c1d function_call + 381
45 python 0x0000000100247d65 PyObject_Call + 101
46 python 0x000000010031b80a _PyEval_EvalFrameDefault + 26954
47 python 0x000000010031fa19 _PyEval_EvalCodeWithName + 3641
48 python 0x0000000100314e6b PyEval_EvalCodeEx + 107
49 python 0x0000000100270c1d function_call + 381
50 python 0x0000000100247d65 PyObject_Call + 101
51 _ctypes.cpython-36m-darwin.so 0x0000000100c380d0 closure_fcn + 448
52 _ctypes.cpython-36m-darwin.so 0x0000000100c3dec2 ffi_closure_unix64_inner + 674
53 _ctypes.cpython-36m-darwin.so 0x0000000100c3d3f6 ffi_closure_unix64 + 70
54 AppKit 0x00007fff877f93ad -[NSWindow _reallySendEvent:isDelayedEvent:] + 212
55 AppKit 0x00007fff87238539 -[NSWindow sendEvent:] + 517
56 AppKit 0x00007fff871b8a38 -[NSApplication sendEvent:] + 2540
57 _ctypes.cpython-36m-darwin.so 0x0000000100c3d277 ffi_call_unix64 + 79
58 ??? 0x00007fff5f9c17e0 0x0 + 140734797453280
)
2017-12-19 18:26:19.624 python[94919:2273428] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSApplication _setup:]: unrecognized selector sent to instance 0x7fdf63113e40'
*** First throw call stack:
(
0 CoreFoundation 0x00007fff96e34452 __exceptionPreprocess + 178
1 libobjc.A.dylib 0x00007fff93b4b73c objc_exception_throw + 48
2 CoreFoundation 0x00007fff96e9e18d -[NSObject(NSObject) doesNotRecognizeSelector:] + 205
3 CoreFoundation 0x00007fff96da44c1 ___forwarding___ + 1009
4 CoreFoundation 0x00007fff96da4048 _CF_forwarding_prep_0 + 120
5 Tk 0x0000000106c61948 TkpInit + 476
6 Tk 0x0000000106bdca6e Tk_Init + 1799
7 _tkinter.cpython-36m-darwin.so 0x0000000106ab9dd2 Tcl_AppInit + 82
8 _tkinter.cpython-36m-darwin.so 0x0000000106ab4d48 _tkinter_create + 1112
9 python 0x000000010029212c _PyCFunction_FastCallDict + 172
10 python 0x000000010031eb0f call_function + 479
11 python 0x000000010031b4f2 _PyEval_EvalFrameDefault + 26162
12 python 0x000000010031fa19 _PyEval_EvalCodeWithName + 3641
13 python 0x0000000100320552 _PyFunction_FastCallDict + 802
14 python 0x0000000100248018 _PyObject_FastCallDict + 360
15 python 0x0000000100248135 _PyObject_Call_Prepend + 149
16 python 0x0000000100247d65 PyObject_Call + 101
17 python 0x00000001002aa89e slot_tp_init + 158
18 python 0x00000001002a69e9 type_call + 313
19 python 0x0000000100247fe5 _PyObject_FastCallDict + 309
20 python 0x000000010031ea09 call_function + 217
21 python 0x000000010031b4f2 _PyEval_EvalFrameDefault + 26162
22 python 0x000000010032016e fast_function + 574
23 python 0x000000010031eae9 call_function + 441
24 python 0x000000010031b4f2 _PyEval_EvalFrameDefault + 26162
25 python 0x000000010032016e fast_function + 574
26 python 0x000000010031eae9 call_function + 441
27 python 0x000000010031b4f2 _PyEval_EvalFrameDefault + 26162
28 python 0x00000001003205ec _PyFunction_FastCallDict + 956
29 python 0x0000000100248018 _PyObject_FastCallDict + 360
30 python 0x0000000100248135 _PyObject_Call_Prepend + 149
31 python 0x0000000100247d65 PyObject_Call + 101
32 python 0x000000010031b80a _PyEval_EvalFrameDefault + 26954
33 python 0x000000010031fa19 _PyEval_EvalCodeWithName + 3641
34 python 0x0000000100314e6b PyEval_EvalCodeEx + 107
35 python 0x0000000100270c1d function_call + 381
36 python 0x0000000100247d65 PyObject_Call + 101
37 python 0x000000010031b80a _PyEval_EvalFrameDefault + 26954
38 python 0x000000010031fa19 _PyEval_EvalCodeWithName + 3641
39 python 0x0000000100320216 fast_function + 742
40 python 0x000000010031eae9 call_function + 441
41 python 0x000000010031b4f2 _PyEval_EvalFrameDefault + 26162
42 python 0x000000010031fa19 _PyEval_EvalCodeWithName + 3641
43 python 0x0000000100314e6b PyEval_EvalCodeEx + 107
44 python 0x0000000100270c1d function_call + 381
45 python 0x0000000100247d65 PyObject_Call + 101
46 python 0x000000010031b80a _PyEval_EvalFrameDefault + 26954
47 python 0x000000010031fa19 _PyEval_EvalCodeWithName + 3641
48 python 0x0000000100314e6b PyEval_EvalCodeEx + 107
49 python 0x0000000100270c1d function_call + 381
50 python 0x0000000100247d65 PyObject_Call + 101
51 _ctypes.cpython-36m-darwin.so 0x0000000100c380d0 closure_fcn + 448
52 _ctypes.cpython-36m-darwin.so 0x0000000100c3dec2 ffi_closure_unix64_inner + 674
53 _ctypes.cpython-36m-darwin.so 0x0000000100c3d3f6 ffi_closure_unix64 + 70
54 AppKit 0x00007fff877f93ad -[NSWindow _reallySendEvent:isDelayedEvent:] + 212
55 AppKit 0x00007fff87238539 -[NSWindow sendEvent:] + 517
56 AppKit 0x00007fff871b8a38 -[NSApplication sendEvent:] + 2540
57 _ctypes.cpython-36m-darwin.so 0x0000000100c3d277 ffi_call_unix64 + 79
58 ??? 0x00007fff5f9c17e0 0x0 + 140734797453280
)
libc++abi.dylib: terminating with uncaught exception of type NSException
[1] 94919 abort python Pyno.py ../moduleq/daf/exceptions.py
Resaving causes meaningless diff because of reordering of nodes.
[ . . . ]
@@ -104,7 +104,7 @@
"#"
],
"connects": [],
- "parent": 23
+ "parent": 3
},
{
"type": "field",
@@ -118,7 +118,7 @@
"20"
],
"connects": [],
- "parent": 24
+ "parent": 4
},
{
"type": "field",
@@ -134,7 +134,7 @@
"connects": [
{
"output": {
- "node": 21,
+ "node": 1,
"put": {
"name": "result"
}
@@ -146,7 +146,7 @@
}
}
],
- "parent": 25
+ "parent": 5
},
{
"type": "node",
@@ -171,7 +171,7 @@
"connects": [
{
"output": {
- "node": 27,
+ "node": 7,
"put": {
"name": "output"
}
@@ -184,7 +184,7 @@
},
{
"output": {
- "node": 21,
+ "node": 1,
"put": {
"name": "result"
}
[ . . . ]
Read conversation here #17
Would it be possible for the code editor to have line numbering to make it easier to find errors in the code?
Originally posted by @GurePortela in #9 (comment)
Code highlighting of keywords.
Everything in node's code executed once, in "read time".
If there was 'call' binding, this 'call' function will be called as main node process - "run time".
I'm considering to add 'cleanup' binding which will be called on node deletion or restart. For example close opened graphical window.
Maybe there is more userfull bindings (callbacks) to add..
From time to time I do get this error:
Traceback (most recent call last):
File "./Pyno.py", line 19, in
pyglet.app.run()
File "/usr/local/lib/python3.4/dist-packages/pyglet/app/init.py", line 138, in run
event_loop.run()
File "/usr/local/lib/python3.4/dist-packages/pyglet/app/base.py", line 142, in run
self._run()
File "/usr/local/lib/python3.4/dist-packages/pyglet/app/base.py", line 155, in _run
platform_event_loop.step(timeout)
File "/usr/local/lib/python3.4/dist-packages/pyglet/app/xlib.py", line 125, in step
device.select()
File "/usr/local/lib/python3.4/dist-packages/pyglet/canvas/xlib.py", line 168, in select
dispatch(e)
File "/usr/local/lib/python3.4/dist-packages/pyglet/window/xlib/init.py", line 922, in dispatch_platform_event_view
event_handler(e)
File "/usr/local/lib/python3.4/dist-packages/pyglet/window/xlib/init.py", line 1234, in _event_button
x, y, button, modifiers)
File "/usr/local/lib/python3.4/dist-packages/pyglet/window/init.py", line 1220, in dispatch_event
if EventDispatcher.dispatch_event(self, *args) != False:
File "/usr/local/lib/python3.4/dist-packages/pyglet/event.py", line 357, in dispatch_event
if handler(*args):
File "/home/osboxes/Downloads/Pyno-drtrigon/field.py", line 204, in on_mouse_press
self.lost_focus()
File "/home/osboxes/Downloads/Pyno-drtrigon/field.py", line 274, in lost_focus
self.caret.mark = self.caret.position = 0
File "/usr/local/lib/python3.4/dist-packages/pyglet/text/caret.py", line 212, in _set_mark
self._update(line=self._ideal_line)
File "/usr/local/lib/python3.4/dist-packages/pyglet/text/caret.py", line 398, in _update
x, y = self._layout.get_point_from_position(self._position, line)
File "/usr/local/lib/python3.4/dist-packages/pyglet/text/layout.py", line 2307, in get_point_from_position
baseline = self._document.get_style('baseline', max(0, position - 1))
AttributeError: 'NoneType' object has no attribute 'get_style'
I was able to track it down to the point that it must be related to loading a pyno file, clicking around on some field and changing its value, load another pyno file and attempt to do something in there. Sadly I was not yet able to track it down further...
I made a mistake, I wanted to mention when I said that the contents of the input and output data are stored in the node, but in fact I meant to be stored in the pyno file. pn (using this filename as test) So if it were possible to make the modification so that the input and output data were stored in an input and output text file added to the program created (pyno .pn), this would avoid overloading of the computational capacity of the system, would run the program faster and would create an interface with more options for the user.
Currently there are two elements: node and field.
Can you add a third element that represents a pyno-file? That way it would be possible to group complex files/flows into several files or to reuse pyno-files for various purposes. I would propose to use either fields or the open connections of the pyno-file as connections for the new object.
The instructions given in https://github.com/honix/Pyno#how-to-run are confusing (very brief). Am I right that they mean something like:
$ git clone https://github.com/honix/Pyno.git
$ cd Pyno
$ pip install .
... and the instructions given in https://github.com/honix/Pyno#how-to-modify in that case:
$ git clone https://github.com/honix/Pyno.git
$ cd Pyno
$ pip install -e .
I like the approach using a clone with pip install .
I wonder what happens if I omit the pip install and just do a clone?
The README should be enhanced to include a bit more specific step-by-step install instructions.
I guess node means callable; while field the opposite?
Right now, the pattern to define the code of a node is, if I understand correctly:
def helper(a, b):
return a + b
def add(a=0, b=0):
result = helper(a, b)
return result
call = add
or
import calendar
call = calendar.isleap
It would be nice if this additional assignment to the arbitrarily named call
object could be hidden from the user. I am thinking that it should be possible using python decorators to register the wanted callable with the node it belongs to, e.g. like this:
@node_target
def helper(a, b):
return a + b
def add(a=0, b=0):
result = helper(a, b)
return result
or
import calendar
node_target(calendar.isleap)
Note how it's possible to pick out which callable you want assigned from several candidates, and how you don't rely on having a specifically-named callback object that just hangs around in that scope. It would also maybe be easier to handle when creating many nodes from another module.
Does this sound attractive?
Is there a option convert .py file to .pn or other way around?
PYNO_HOME will point to the Pyno folder with examples, nodes and etc., so user can target them regardless initial working directory.
Hello!
The whole content of the knot is stored in his internal code. When vc. works with high sequences of values, this content provokes a latência in the load of the knot. Would it have as it will prevent which data of entry and when it went out they were stored in the code of the knot, talvés, creating a textual association of entry and exit for each knot launched in the scheme of the program?
When vc. tries to carve the exit of a knot for an archive the same thing is overloaded due to operation of repetition of the fraps that I still did not stop to notice regarding the new version, that now it has the buttons of pause, to stop, to execute the block of code.
Would it be possible also to converge all the exits of the knots when values and or answers were necessary to the visualization, to be direcionadas for a specific field that it could have several entries when it was a field of exit or several exits when it was a field of entry of data?
I love your work, it grieves that I do not know planning, congratulations.
Seeing some of the huge diffs of .pn files for small changes, and seeing the structure of the contents, I am wondering if it would be worthwhile to make the file format json instead (I think it's already quite near?), and store it in a prettyfied/line-broken form.
If you couple it with OrderedDict, this should preserve insertion order and minimise the diffs between saves.
YAML would of course also be an option.
[
{
"size": [
300,
150
],
"color": [
121,
100,
104
],
"code": "def s1(a=0, b=0,modo=''):\n result1 = a + b\n return result1\ndef s2(a=0,b=0,mod=''):\n result2=abs(a-b)\n return result2\ncall = s1\ncall = s2",
"parent": 21,
"y": 43,
"connects": [
{
"input": {
"put": {
"name": "b"
}
},
"output": {
"node": 24,
"put": {
"name": "output"
}
}
},
{
"input": {
"put": {
"name": "a"
}
},
"output": {
"node": 25,
"put": {
"name": "output"
}
}
},
{
"input": {
"put": {
"name": "mod"
}
},
"output": {
"node": 23,
"put": {
"name": "output"
}
}
}
],
"type": "node",
"x": -44
}
]
The example https://github.com/honix/Pyno/blob/master/examples/serial_monitor.pn was build for an Arduino. May be we should have even more examples and docu for Arduino as that might increase popularity quite a bit?
Is there any way for an modo input to define which call function will call.
type: modo = 's1' -> calls def s1
modo = 's2' -> calls def s2
it would be possible to create a key to execute different nunctions on the same node
When launching pyno (Ubuntu 16.04, Python 3.5 or 3.6), I get the following error:
(py3sci) plas@hg201pc58:~/dev$ pyno
Loading...
Can't load file: [Errno 2] No such file or directory: '.auto-saved.pn'
File /home/plas/dev/py3sci/lib/python3.5/site-packages/pyno/examples/welcome.pn loaded!
New pyno!
New window!
Traceback (most recent call last):
File "/home/plas/dev/py3sci/bin/pyno", line 11, in <module>
sys.exit(run())
File "/home/plas/dev/py3sci/lib/python3.5/site-packages/pyno/runner.py", line 9, in run
pyglet.app.run()
File "/home/plas/dev/py3sci/lib/python3.5/site-packages/pyglet/app/__init__.py", line 138, in run
event_loop.run()
File "/home/plas/dev/py3sci/lib/python3.5/site-packages/pyglet/app/base.py", line 142, in run
self._run()
File "/home/plas/dev/py3sci/lib/python3.5/site-packages/pyglet/app/base.py", line 154, in _run
timeout = self.idle()
File "/home/plas/dev/py3sci/lib/python3.5/site-packages/pyglet/app/base.py", line 275, in idle
redraw_all = self.clock.call_scheduled_functions(dt)
File "/home/plas/dev/py3sci/lib/python3.5/site-packages/pyglet/clock.py", line 346, in call_scheduled_functions
item.func(now - item.last_ts, *item.args, **item.kwargs)
File "/home/plas/dev/py3sci/lib/python3.5/site-packages/pyno/window.py", line 86, in update
self.nodes_update()
File "/home/plas/dev/py3sci/lib/python3.5/site-packages/pyno/process.py", line 31, in nodes_update
node.processor()
File "/home/plas/dev/py3sci/lib/python3.5/site-packages/pyno/node.py", line 50, in processor
return Processor.processor(self, self.connected_to, self.outputs)
File "/home/plas/dev/py3sci/lib/python3.5/site-packages/pyno/processor.py", line 54, in processor
if isinstance(result, Tuple) and len(outputs) > 1:
File "/usr/lib/python3.5/typing.py", line 725, in __instancecheck__
raise TypeError("Tuples cannot be used with isinstance().")
TypeError: Tuples cannot be used with isinstance().
Pyno should be turned into a package that you can install with pip
and/or conda
. This will also make maintenance (like definining dependencies etc) easier.
It would also be sound practice to add tests (via pytest) and continuous integration (e.g. via travis-ci).
I already have an idea about the necessary steps (have created packages before), and can do that if desired.
Now that there is a pyno command, it would probably be useful to add a minimal command line interface, so that a user can execute pyno somefile.pn
to directly open that file.
I suggest implementing this with the argparse module from the standard library.
This was just an idea - but can we maybe use the processor to merge and output the python code (instead of executing) and by that support saving to python script? That would allow to export complex code into a python script and import it into a compact (and may be faster) single node again. Would also allow to re-use code in other programs etc.
Now that it's possible to run pytest on the code, it would be wise to add a Continuos Integration service, which will run tests on all commits and PRs to minimise the amount of breakage introduced. This will also hopefully incentivise writing of further tests for the codebase.
I would recommend Travis (www.travis-ci.com) as you can just register with your github credentials, and it's free for open source projects. If not familiar with how this works for a Python project, see https://docs.travis-ci.com/user/languages/python.
Hey!
I do not know programming but was trying to do a commercial, something that entering a given, when this was disconnected to keep the incoming data on output, I don't know how I did it or how it works, if you guys can look!
Hello !
It would be possible that the node would be freer to make the
connections of the input and output pins as in the nodes made in
the Flow-Based Programming (FBP) by J Paul Morrison ?
this would make it easier to improve the layout of the project.
Olá !
Seria possível que o nó fosse mais livre para fazer o
conexões dos pinos de entrada e saída como nos nós feitos em
a programação baseada em fluxo (FBP) por J Paul Morrison?
Isso tornaria mais fácil melhorar o layout do projeto.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.