├── .gitignore ├── .jscsrc ├── .jshintrc ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Gruntfile.js ├── LICENSE ├── NOTICE ├── README.rst ├── docs ├── basic-usage.rst ├── bundled-plugins.rst ├── coding-style-guide.rst ├── conf.py ├── developing-visualizations.rst ├── images │ ├── correlationPlotFull.png │ ├── correlationPlotHalf.png │ ├── geodots-small.png │ ├── geonodelink-small.png │ ├── mapdots-small.png │ └── timeline.png ├── index.rst ├── installation.rst ├── plugins.rst ├── python-services.rst ├── releasing-tangelo.rst ├── setup.rst ├── static │ ├── fiddling │ │ └── config.yaml.txt │ ├── tangelo-sphinx.css │ ├── tangelo.ico │ ├── tng.zip │ └── tng │ │ ├── barchart.json │ │ ├── build-db.py │ │ ├── build-db.yaml │ │ ├── episodes.csv │ │ ├── index.html │ │ ├── index.js │ │ ├── people.csv │ │ ├── startrek.py │ │ ├── startrek.yaml │ │ ├── writers.py │ │ └── writers.yaml ├── tangelo-js.rst ├── tangelo-manpage.rst ├── tangelo-passwd-manpage.rst ├── tangelo-py.rst ├── templates │ └── layout.html └── tutorials │ ├── building-an-app.rst │ ├── db-vis.rst │ └── fiddling.rst ├── js ├── src │ ├── core.js │ └── util.js └── tests │ ├── .jshintrc │ ├── absoluteUrl.js │ ├── accessor.js │ ├── bin.js │ ├── config.js │ ├── data │ └── config.yaml │ ├── distance-clustering.js │ ├── index.html │ ├── jade │ └── qunitHarness.jade │ ├── lib │ └── blanket.js │ ├── plugin.js │ ├── queryArguments.js │ ├── service │ └── finite.py │ ├── smooth.js │ ├── stream.js │ ├── tangelo-exists.js │ └── tangelo-version.js ├── package.json ├── setup.cfg ├── tangelo ├── MANIFEST.in ├── assets │ ├── conf │ │ ├── tangelo.global.conf │ │ └── tangelo.local.conf │ └── data │ │ └── get-flickr-data.py ├── setup.py └── tangelo │ ├── __init__.py │ ├── __main__.py │ ├── pkgdata │ ├── plugin │ │ ├── bokeh │ │ │ ├── python │ │ │ │ └── __init__.py │ │ │ ├── requirements.txt │ │ │ └── web │ │ │ │ ├── bokeh.js │ │ │ │ └── examples │ │ │ │ └── iris │ │ │ │ ├── index.html │ │ │ │ └── iris.py │ │ ├── config │ │ │ └── web │ │ │ │ ├── config.js │ │ │ │ └── config.py │ │ ├── data │ │ │ └── web │ │ │ │ ├── bin.js │ │ │ │ ├── distanceCluster.js │ │ │ │ ├── smooth.js │ │ │ │ └── tree.js │ │ ├── girder │ │ │ ├── control.py │ │ │ ├── info.txt │ │ │ ├── requirements.txt │ │ │ └── web │ │ │ │ └── girderBrowser.js │ │ ├── impala │ │ │ └── web │ │ │ │ └── impala.py │ │ ├── mapping │ │ │ └── web │ │ │ │ ├── examples │ │ │ │ ├── flickr │ │ │ │ │ ├── config.yaml │ │ │ │ │ ├── geo.ext.min.js │ │ │ │ │ ├── geo.ext.min.js.map │ │ │ │ │ ├── geo.min.js │ │ │ │ │ ├── geo.min.js.map │ │ │ │ │ ├── index.html │ │ │ │ │ ├── index.js │ │ │ │ │ └── wait.gif │ │ │ │ ├── geodots │ │ │ │ │ ├── index.html │ │ │ │ │ └── index.js │ │ │ │ ├── geojsMap │ │ │ │ │ ├── geo.ext.min.js │ │ │ │ │ ├── geo.min.js │ │ │ │ │ ├── index.html │ │ │ │ │ └── index.js │ │ │ │ ├── geojsdots │ │ │ │ │ ├── index.html │ │ │ │ │ └── index.js │ │ │ │ ├── geonodelink │ │ │ │ │ ├── index.html │ │ │ │ │ └── index.js │ │ │ │ ├── lib │ │ │ │ │ ├── geo.min.js │ │ │ │ │ └── vgl.min.js │ │ │ │ ├── mapdots │ │ │ │ │ ├── index.html │ │ │ │ │ └── index.js │ │ │ │ └── world-110m.json │ │ │ │ ├── geodots.js │ │ │ │ ├── geojsMap.js │ │ │ │ ├── geojsdots.js │ │ │ │ ├── geonodelink.js │ │ │ │ ├── geovis.js │ │ │ │ ├── lib │ │ │ │ ├── geo.min.js │ │ │ │ └── vgl.min.js │ │ │ │ └── mapdots.js │ │ ├── mongo │ │ │ ├── requirements.txt │ │ │ └── web │ │ │ │ └── mongo.py │ │ ├── stream │ │ │ └── web │ │ │ │ ├── examples │ │ │ │ ├── index.html │ │ │ │ ├── index.js │ │ │ │ └── primes.py │ │ │ │ ├── stream.js │ │ │ │ └── stream.py │ │ ├── svg2pdf │ │ │ └── web │ │ │ │ └── svg2pdf.py │ │ ├── tangelo │ │ │ └── web │ │ │ │ └── version.py │ │ ├── ui │ │ │ └── web │ │ │ │ ├── controlPanel.js │ │ │ │ └── svgColorLegend.js │ │ ├── vis │ │ │ └── web │ │ │ │ ├── barChart.js │ │ │ │ ├── correlationPlot.js │ │ │ │ ├── dendrogram.js │ │ │ │ ├── donutChart.js │ │ │ │ ├── examples │ │ │ │ ├── barchart │ │ │ │ │ ├── index.html │ │ │ │ │ └── index.js │ │ │ │ ├── correlation │ │ │ │ │ ├── index.html │ │ │ │ │ └── index.js │ │ │ │ ├── dendrogram │ │ │ │ │ ├── index.html │ │ │ │ │ └── index.js │ │ │ │ ├── donutchart │ │ │ │ │ ├── index.html │ │ │ │ │ └── index.js │ │ │ │ ├── nodelink │ │ │ │ │ ├── index.html │ │ │ │ │ └── index.js │ │ │ │ ├── parallelcoords │ │ │ │ │ ├── cars.json │ │ │ │ │ ├── index.html │ │ │ │ │ └── index.js │ │ │ │ └── timeline │ │ │ │ │ ├── index.html │ │ │ │ │ └── index.js │ │ │ │ ├── nodelink.js │ │ │ │ ├── parallelCoords.js │ │ │ │ └── timeline.js │ │ ├── vtkweb │ │ │ ├── config.yaml │ │ │ ├── control.py │ │ │ ├── include │ │ │ │ └── vtkweb-launcher.py │ │ │ ├── requirements.txt │ │ │ └── web │ │ │ │ ├── examples │ │ │ │ └── cone │ │ │ │ │ ├── index.html │ │ │ │ │ ├── index.js │ │ │ │ │ └── vtkweb_cone.py │ │ │ │ ├── lib │ │ │ │ ├── autobahn.min.js │ │ │ │ └── vtkweb-all.min.js │ │ │ │ ├── vtkweb.js │ │ │ │ └── vtkweb.py │ │ └── watch │ │ │ └── python │ │ │ └── __init__.py │ ├── tangelo.ico │ └── web │ │ ├── img │ │ └── Tangelo_Mark_256.png │ │ └── index.html │ ├── server.py │ ├── util.py │ └── websocket.py └── tests ├── 200-ok.py ├── 404-not-found.py ├── analyze-url.py ├── bundled-plugins.yaml ├── commandline-version.py ├── config ├── bad-config.yaml └── list-config.yaml ├── echo-service.py ├── error-report-code.py ├── fixture └── __init__.py ├── get_free_port.py ├── plugin-import.py ├── plugins ├── moduletest │ └── python │ │ ├── __init__.py │ │ └── server.py ├── pluginorder │ └── python │ │ └── __init__.py └── pythonfile │ └── python.py ├── redirection.py ├── rest.py ├── server-identity.py ├── service-config.py ├── service-cwd.py ├── service-import.py ├── static_file.txt ├── staticfile.py ├── stream.py ├── tangelo-config-settings.py ├── tangelo-config.py ├── tangelo-verbose.py ├── tangelo-version.py ├── tangelo-watch.py └── web ├── analyze-url ├── analyze-url.py ├── has-index │ └── index.html ├── open.py ├── open.yaml └── standalone.yaml ├── bad.py ├── configured.py ├── configured.yaml ├── cwd.py ├── echo.py ├── finite.py ├── import.py ├── primes.py ├── redirect ├── content.txt ├── internal_redirect.py └── redirect.py ├── restful.py ├── settings.py ├── static_file.py ├── sub └── importpath.py ├── watch_a.py ├── watch_b.py ├── watch_c.py ├── watch_d.py └── watch_e.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.swo 3 | *.pyc 4 | node_modules 5 | venv 6 | sdist 7 | configuration.json 8 | tangelo/MANIFEST 9 | tangelo/README 10 | tangelo/tangelo.egg-info 11 | tangelo/tangelo/pkgdata/plugin/tangelo/web/tangelo.js 12 | tangelo/tangelo/pkgdata/plugin/tangelo/web/tangelo.min.js 13 | tangelo/tangelo/pkgdata/plugin/docs 14 | js/tests/results 15 | .coverage 16 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "requireCurlyBraces": true, 3 | "requireSpaceAfterKeywords": true, 4 | "requireSpaceBeforeBlockStatements": true, 5 | "requireParenthesesAroundIIFE": true, 6 | "requireSpacesInConditionalExpression": true, 7 | "requireSpacesInAnonymousFunctionExpression": { 8 | "beforeOpeningRoundBrace": true, 9 | "beforeOpeningCurlyBrace": true 10 | }, 11 | "requireSpacesInNamedFunctionExpression": { 12 | "beforeOpeningCurlyBrace": true 13 | }, 14 | "requireSpacesInFunctionDeclaration": { 15 | "beforeOpeningCurlyBrace": true 16 | }, 17 | "requireMultipleVarDecl": true, 18 | "requireBlocksOnNewline": true, 19 | "disallowPaddingNewlinesInBlocks": true, 20 | "disallowEmptyBlocks": true, 21 | "disallowQuotedKeysInObjects": true, 22 | "disallowSpaceAfterObjectKeys": true, 23 | "requireSpaceBeforeObjectValues": true, 24 | "requireCommaBeforeLineBreak": true, 25 | "requireOperatorBeforeLineBreak": true, 26 | "disallowSpaceAfterPrefixUnaryOperators": true, 27 | "disallowSpaceBeforePostfixUnaryOperators": true, 28 | "disallowImplicitTypeConversion": ["numeric", "boolean", "binary", "string"], 29 | "disallowMultipleLineStrings": true, 30 | "disallowMultipleLineBreaks": true, 31 | "disallowMixedSpacesAndTabs": true, 32 | "disallowTrailingWhitespace": true, 33 | "disallowTrailingComma": true, 34 | "disallowKeywordsOnNewLine": ["else if", "else"], 35 | "requireLineFeedAtFileEnd": true, 36 | "requireCapitalizedConstructors": true, 37 | "requireDotNotation": true, 38 | "requireSpaceAfterLineComment": true, 39 | "disallowNewlineBeforeBlockStatements": true, 40 | "validateIndentation": 4, 41 | "validateParameterSeparator": ", ", 42 | "safeContextKeyword": ["that"] 43 | } 44 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise": true, 3 | "camelcase": true, 4 | "curly": true, 5 | "eqeqeq": true, 6 | "forin": true, 7 | "immed": true, 8 | "latedef": true, 9 | "newcap": true, 10 | "noempty": false, 11 | "nonbsp": true, 12 | "nonew": true, 13 | "plusplus": false, 14 | "quotmark": "double", 15 | "undef": true, 16 | "unused": true, 17 | "strict": true, 18 | "maxparams": false, 19 | "maxdepth": false, 20 | "maxstatements": false, 21 | "maxcomplexity": false, 22 | "maxlen": false, 23 | "eqnull": true, 24 | "browser": true, 25 | "globals": { 26 | "console": false 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | sudo: false 4 | 5 | python: 6 | - 2.7 7 | 8 | script: 9 | - npm install -g grunt-cli 10 | - npm install 11 | - grunt 12 | - grunt test:server 13 | - grunt test:client 14 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | How to Contribute 2 | ================= 3 | 4 | We love hearing from Tangelo users, whether by email, on the issue tracker, or 5 | via pull request. Here are some guidelines for contributing: 6 | 7 | - Please email us at if you have any 8 | questions or concerns or if you need help with any usage problems you 9 | encounter while using Tangelo. 10 | - Consider [subscribing](http://public.kitware.com/mailman/listinfo/tangelo-users) 11 | to the *tangelo-users* list to receive important updates about our progress 12 | and announcements about releases. 13 | - If you have feature requests or bug reports, go ahead and open a new issue on 14 | the [tracker](https://github.com/Kitware/tangelo/issues). 15 | - If you would like to contribute actual code, follow these steps: 16 | 17 | 1. Get a [GitHub account](https://github.com/join). 18 | 2. [Fork](https://github.com/Kitware/tangelo/fork) the Tangelo repository. 19 | 3. Create a topic branch for your changes. 20 | 4. Make commits as needed, and include informative commit messages. Try to 21 | structure your commits logically, so that each one has a clear-cut purpose. 22 | 5. When your topic is complete, edit the CHANGELOG.md file to describe what 23 | changes your topic introduces. 24 | 6. Send us a pull request according to the guidelines 25 | [here](https://help.github.com/articles/using-pull-requests). 26 | 27 | The Tangelo documentation includes a section on [coding 28 | guidelines](http://tangelo.readthedocs.org/en/latest/coding-style-guide.html) to 29 | help ease your contributions into the codebase. Refer to the rest of the 30 | [documentation](http://tangelo.readthedocs.org/en/latest/index.html) as needed, 31 | and let us know if anything is unclear. We're looking forward to hearing from 32 | you! 33 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | --------------------------------------------------------------------------- 2 | Tangelo web application framework 3 | Copyright 2012-2013 Kitware, Inc. 4 | --------------------------------------------------------------------------- 5 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | .. image:: https://badge.fury.io/py/tangelo.svg 2 | :target: http://badge.fury.io/py/tangelo 3 | 4 | .. image:: https://pypip.in/d/tangelo/badge.svg 5 | :target: https://pypi.python.org/pypi/tangelo 6 | 7 | .. image:: https://pypip.in/license/tangelo/badge.svg 8 | :target: http://www.apache.org/licenses/LICENSE-2.0.html 9 | :alt: License 10 | 11 | .. image:: https://travis-ci.org/Kitware/tangelo.svg?branch=master 12 | :target: https://travis-ci.org/Kitware/tangelo 13 | 14 | .. image:: https://readthedocs.org/projects/pip/badge/ 15 | :target: http://tangelo.readthedocs.org/en/latest/ 16 | 17 | ============================================================ 18 | Tangelo: A Web Application Platform for Python Programmers 19 | ============================================================ 20 | 21 | Tangelo reimagines the "web application" by bringing Python into the fold: in 22 | addition to serving standard components such as HTML, CSS, and JavaScript files 23 | in the usual way, Tangelo also manufactures serverside web services from Python 24 | files in your application. These services might provide custom adapters to 25 | databases, launch complex jobs on a cluster to retrieve the results later, 26 | perform image analysis, or really anything that can be done in a Python script. 27 | The Python standard library is extensive, and the galaxy of third-party 28 | libraries even more so. Instead of demanding that you adapt your code to a 29 | complex web framework, worrying about routing and scaffolding along the way, 30 | Tangelo adapts to you, effortlessly integrating your Python code right into 31 | your web application. 32 | 33 | Tangelo runs these expanded web applications with a special purpose webserver, 34 | built on top of `CherryPy `_, which runs the Python 35 | scripts on demand, allowing your HTML and JavaScript to retrieve content from 36 | the scripts. The result is a rich web application that pairs your data with 37 | cutting-edge visual interfaces. 38 | 39 | Tangelo comes bundled with some great examples to get you started. Mix and match 40 | from the following to create your own breed: 41 | 42 | * `Bootstrap `_ to put your app's style on 43 | a solid footing. 44 | 45 | * `D3 `_ for constructing all manner of dynamic and animated 46 | charts. 47 | 48 | * `Vega `_, a brand new declarative language 49 | for defining visual interfaces. 50 | 51 | * `MongoDB `_ for a flexible, speedy NoSQL backend to 52 | feed data to your apps. 53 | 54 | * `Bundled Tangelo plugins 55 | `_, providing 56 | utilities such as streaming of big data, basic visualization elements such as 57 | interactive charts, and user interface elements. 58 | 59 | Get Started 60 | =========== 61 | 62 | Quick Start 63 | ----------- 64 | 65 | To get started with Tangelo's example application pack, run the following: :: 66 | 67 | $ pip install tangelo 68 | $ tangelo --examples 69 | 70 | and then visit http://localhost:8080 in your favorite web browser. 71 | 72 | Hello World 73 | ----------- 74 | 75 | Follow these steps to create an extremely simple Tangelo application: :: 76 | 77 | $ mkdir hello 78 | $ cd hello 79 | $ vim helloworld.py 80 | 81 | .. code-block:: python 82 | 83 | import datetime 84 | 85 | def run(): 86 | return "hello, world - the current time and date is: %s\n" % (datetime.datetime.now()) 87 | 88 | .. code-block:: none 89 | 90 | $ tangelo --port 8080 91 | $ curl http://localhost:8080/helloworld 92 | 93 | hello, world - the current time and date is: 2015-03-31 14:29:44.29411 94 | 95 | Learn More 96 | ========== 97 | 98 | See Tangelo's `documentation `_ for a getting 99 | started guide, advanced usage manual, step-by-step tutorials, and API descriptions. 100 | 101 | Read our ongoing `blog series `_ for 102 | some in-depth discussion of Tangelo and its uses. 103 | 104 | Visit the `website `_ to learn about 105 | Tangelo and its sibling software projects in the TangeloHub platform, and about 106 | how Kitware can help you make the most of your data, computational resources, 107 | and web applications. 108 | 109 | Get Involved 110 | ============ 111 | 112 | Please join our `mailing list `_ 113 | to ask questions about setting up and using Tangelo. 114 | 115 | Fork our repository and do great things. At `Kitware `_, 116 | we've been contributing to open-source software for 15 years and counting, and 117 | want to make Tangelo as useful to as many as possible. 118 | 119 | Acknowledgement 120 | =============== 121 | 122 | Tangelo development is sponsored by the Air Force Research Laboratory and DARPA XDATA program. 123 | -------------------------------------------------------------------------------- /docs/developing-visualizations.rst: -------------------------------------------------------------------------------- 1 | ================================= 2 | Developing Visualizations 3 | ================================= 4 | 5 | .. _jquery-widgets: 6 | 7 | Creating jQuery Widgets 8 | ======================= 9 | 10 | Tangelo visualizations can be implemented as jQuery widgets. They extend the 11 | base jQuery UI widget class, but otherwise do not need to depend on anything 12 | else from jQuery UI. 13 | 14 | Visualization Options 15 | ===================== 16 | 17 | Basic Options 18 | ------------- 19 | 20 | * `data` - The data associated with the visualization, normally 21 | an array. 22 | * `width`, `height` - The width and height of the visualization, in pixels. 23 | If omitted, the visualization should resize to fit the DOM element. 24 | 25 | Visualization Mapping Options 26 | ----------------------------- 27 | 28 | The following options are optional, but if your visualization is able to map 29 | data element properties to visual attributes like size, color, and label, you 30 | should use this standard naming convention. If you have multiple sets of visual 31 | elements (such as nodes and links in a graph), prefix these attributes as 32 | appropriate (e.g. `nodeSize`, `nodeStrokeWidth`). 33 | 34 | * `size` - The size of the visual element as a number of pixels. For example, 35 | if drawing a square for each data element, the squares should have sizes 36 | equal to the square-root of what the `size` option returns for each 37 | data element. 38 | * `color` - The main color of the visual element, specified as a CSS color string. 39 | * `symbol` - The symbol to use for the visual element. 40 | This should use D3's standard set of symbol names. 41 | * `label` - The label for the visual element (a string). 42 | * `stroke` - The color of the stroke (outline) of the visual element specified 43 | in pixels. 44 | * `strokeWidth` - The width of the stroke of the visual element in pixels. 45 | * `opacity` - The opacity of the entire visual element, as a number between 0 to 1. 46 | 47 | .. _accessor: 48 | 49 | Accessor Specifications 50 | ======================= 51 | 52 | AccessorSpec 53 | ------------ 54 | 55 | Each visual mapping should take an `AccessorSpec` for a value. 56 | Accessor specifications work much like `DataRef` specs do in Vega, 57 | though they also allow programmatic ways to generate arbitrary 58 | accessors and scales. 59 | 60 | * ``function (d) { ... }`` - The most general purpose way 61 | to generate a visual mapping. The argument is the data element and the return 62 | value is the value for the visual property. 63 | 64 | * ``{value: v}`` - Sets the visual property to the same constant 65 | value `v` for all data elements. 66 | 67 | * ``{index: true}`` - Evaluates to the index of the data item within its 68 | array. 69 | 70 | * ``{field: "dot.separated.name"}`` - Retrieves the specified field 71 | or subfield from the data element and passes it through the 72 | visualization's default scale for that visual property. 73 | Unlike Vega, fields from the original data do not need to be 74 | prefixed by ``"data."``. The special field name ``"."`` 75 | refers to the entire data element. 76 | 77 | * ``{field: "dot.separated.name", scale: ScaleSpec}`` - Overrides the default scale 78 | using a scale specification. Set `scale` to ``tangelo.identity`` to use 79 | a field directly as the visual property. 80 | 81 | * ``{}`` - The *undefined accessor*. This is a function that, if called, 82 | throws an exception. The function also has a property ``undefined`` set to 83 | *true*. This is meant as a stand-in for the case when an accessor must be 84 | assigned but there is no clear choice for a default. It is also used when 85 | creating Tangelo jQuery widgets to mark a property as being an accessor. 86 | Calling :js:func:`tangelo.accessor()` with no arguments also results in an 87 | undefined accessor being created and returned. 88 | 89 | ScaleSpec 90 | --------- 91 | 92 | A scale specification defines how to map data properties to visual properties. 93 | For example, if you want to color your visual elements using a data field 94 | `continent` containing values such as North America, Europe, Asia, etc. 95 | you will need a scale that maps North America to ``"blue"``, 96 | Europe to ``"green"``, etc. Vega has a number of built-in named scales that 97 | together define the `ScaleSpec`. In Tangelo, a `ScaleSpec` may also be an 98 | arbitrary function. 99 | -------------------------------------------------------------------------------- /docs/images/correlationPlotFull.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kitware/tangelo/470034ee9b3d7a01becc1ce5fddc7adc1d5263ef/docs/images/correlationPlotFull.png -------------------------------------------------------------------------------- /docs/images/correlationPlotHalf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kitware/tangelo/470034ee9b3d7a01becc1ce5fddc7adc1d5263ef/docs/images/correlationPlotHalf.png -------------------------------------------------------------------------------- /docs/images/geodots-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kitware/tangelo/470034ee9b3d7a01becc1ce5fddc7adc1d5263ef/docs/images/geodots-small.png -------------------------------------------------------------------------------- /docs/images/geonodelink-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kitware/tangelo/470034ee9b3d7a01becc1ce5fddc7adc1d5263ef/docs/images/geonodelink-small.png -------------------------------------------------------------------------------- /docs/images/mapdots-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kitware/tangelo/470034ee9b3d7a01becc1ce5fddc7adc1d5263ef/docs/images/mapdots-small.png -------------------------------------------------------------------------------- /docs/images/timeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kitware/tangelo/470034ee9b3d7a01becc1ce5fddc7adc1d5263ef/docs/images/timeline.png -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. Tangelo Web Framework documentation master file, created by 2 | sphinx-quickstart on Thu Apr 11 11:42:23 2013. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | ============================================================================== 7 | Welcome to the Tangelo, the Web Application Platform for Python Programmers! 8 | ============================================================================== 9 | 10 | Tangelo is a web application driver, implemented as a special-purpose webserver 11 | built on top of `CherryPy `_. Tangelo paves the way 12 | for you to use HTML5, CSS, JavaScript, and other web technologies such as 13 | jQuery, D3, Bootstrap, WebGL, Canvas, and `Vega 14 | `_ to create rich web applications - from 15 | traditional, static pages, to cutting-edge, visual, dynamic displays. 16 | 17 | But **Tangelo also considers Python scripts to be part of your application, 18 | alongside your HTML and JavaScript files**, running them on your behalf to do 19 | anything from retrieving a few database results for display, to engaging with 20 | powerful computational engines such as Hadoop to compute complex results. In 21 | other words, **Tangelo reimagines the web application by making Python a 22 | first-class citizen**, effortlessly integrating your Python code into your web 23 | applications without your having to adapt to complex web frameworks, worry about 24 | routing, or engage in the busy work of putting up application scaffolding. 25 | 26 | To help in creating these newly enriched web applications, Tangelo exports the 27 | Tangelo API, a collection of Python and JavaScript functions, standard plugins, 28 | and the means to create custom plugins of your own, to create your own Tangelo 29 | applications. 30 | 31 | This document describes all the pieces that fit together to make Tangelo work. 32 | 33 | Please visit the `Tangelo homepage `_ and `GitHub 34 | repository `_, and read our `ongoing blog 35 | series `_ for more information. 36 | 37 | .. _quickstart: 38 | 39 | Getting Started 40 | =============== 41 | 42 | Quick Start 43 | ----------- 44 | 45 | 1. Make sure you have Python 2.7 and Pip installed (on Linux and OS X systems, 46 | your local package manager should do the trick; for Windows, see `here 47 | `_). 48 | 49 | 2. Open a shell (e.g. Terminal on OS X; Bash on Linux; or Command Prompt on 50 | Windows) and issue this command to install the Tangelo package: :: 51 | 52 | pip install tangelo 53 | 54 | (On UNIX systems you may need to do this as root, or with ``sudo``.) 55 | 56 | 3. Issue this command to start Tangelo, serving the example pack: :: 57 | 58 | tangelo --examples 59 | 60 | 4. Visit your Tangelo instance at http://localhost:8080. 61 | 62 | Hello World 63 | ----------- 64 | 65 | Follow these steps to create an extremely simple Tangelo application: :: 66 | 67 | $ mkdir hello 68 | $ cd hello 69 | $ vim helloworld.py 70 | 71 | .. code-block:: python 72 | 73 | import datetime 74 | 75 | def run(): 76 | return "hello, world - the current time and date is: %s\n" % (datetime.datetime.now()) 77 | 78 | .. code-block:: none 79 | 80 | $ tangelo --port 8080 81 | $ curl http://localhost:8080/helloworld 82 | 83 | hello, world - the current time and date is: 2015-03-31 14:29:44.29411 84 | 85 | Diving Deeper 86 | ------------- 87 | 88 | * To tinker with the Tangelo examples directly, see :ref:`fiddling`. 89 | 90 | * To learn how to create your own Tangelo application from scratch, see 91 | :ref:`build-app` and the other :ref:`tutorials `. 92 | 93 | .. _here: http://docs.python-guide.org/en/latest/starting/install/win/ 94 | 95 | Using Tangelo 96 | ============= 97 | 98 | .. toctree:: 99 | :maxdepth: 2 100 | 101 | installation 102 | setup 103 | basic-usage 104 | python-services 105 | plugins 106 | 107 | .. _tutorials: 108 | 109 | Tutorials 110 | ========= 111 | 112 | .. toctree:: 113 | :maxdepth: 2 114 | 115 | tutorials/building-an-app 116 | tutorials/db-vis 117 | tutorials/fiddling 118 | 119 | Command Line Utilities 120 | ====================== 121 | 122 | .. toctree:: 123 | :maxdepth: 2 124 | 125 | tangelo-manpage 126 | tangelo-passwd-manpage 127 | 128 | The Tangelo API 129 | =============== 130 | 131 | .. toctree:: 132 | :maxdepth: 2 133 | 134 | tangelo-py 135 | tangelo-js 136 | bundled-plugins 137 | 138 | Information for Developers 139 | ========================== 140 | 141 | .. toctree:: 142 | :maxdepth: 2 143 | 144 | coding-style-guide 145 | releasing-tangelo 146 | developing-visualizations 147 | 148 | Indices and tables 149 | ================== 150 | 151 | * :ref:`genindex` 152 | * :ref:`search` 153 | -------------------------------------------------------------------------------- /docs/static/fiddling/config.yaml.txt: -------------------------------------------------------------------------------- 1 | plugins: 2 | - name: config 3 | path: plugins/config 4 | 5 | - name: data 6 | path: plugins/data 7 | 8 | - name: docs 9 | path: plugins/docs 10 | 11 | - name: mapping 12 | path: plugins/mapping 13 | 14 | - name: mongo 15 | path: plugins/mongo 16 | 17 | - name: stream 18 | path: plugins/stream 19 | 20 | - name: tangelo 21 | path: plugins/tangelo 22 | 23 | - name: ui 24 | path: plugins/ui 25 | 26 | - name: vis 27 | path: plugins/vis 28 | -------------------------------------------------------------------------------- /docs/static/tangelo-sphinx.css: -------------------------------------------------------------------------------- 1 | p.admonition-title { 2 | display: inline; 3 | } 4 | 5 | .admonition-todo { 6 | color: red; 7 | font-weight: bold; 8 | } 9 | -------------------------------------------------------------------------------- /docs/static/tangelo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kitware/tangelo/470034ee9b3d7a01becc1ce5fddc7adc1d5263ef/docs/static/tangelo.ico -------------------------------------------------------------------------------- /docs/static/tng.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kitware/tangelo/470034ee9b3d7a01becc1ce5fddc7adc1d5263ef/docs/static/tng.zip -------------------------------------------------------------------------------- /docs/static/tng/barchart.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "barchart", 3 | "width": 4000, 4 | "height": 500, 5 | "data": [ 6 | { 7 | "name": "table", 8 | "url": "writers?sort=true" 9 | } 10 | ], 11 | "scales": [ 12 | { 13 | "name": "y", 14 | "type": "linear", 15 | "range": "height", 16 | "domain": { 17 | "data": "table", 18 | "field": "data.count" 19 | } 20 | }, 21 | { 22 | "name": "x", 23 | "type": "ordinal", 24 | "range": "width", 25 | "domain": { 26 | "data": "table", 27 | "field": "data.name" 28 | } 29 | } 30 | ], 31 | "axes": [ 32 | { 33 | "type": "x", 34 | "scale": "x", 35 | "values": [] 36 | }, 37 | { 38 | "type": "y", 39 | "scale": "y", 40 | "grid": false 41 | } 42 | ], 43 | "marks": [ 44 | { 45 | "type": "rect", 46 | "from": { 47 | "data": "table" 48 | }, 49 | "properties": { 50 | "enter": { 51 | "x": { 52 | "scale": "x", 53 | "field": "data.name", 54 | "offset": -1 55 | }, 56 | "width": { 57 | "scale": "x", 58 | "band": true, 59 | "offset": -1 60 | }, 61 | "y": { 62 | "scale": "y", 63 | "field": "data.count" 64 | }, 65 | "y2": { 66 | "scale": "y", 67 | "value": 0}, 68 | "fill": { 69 | "value": "steelblue" 70 | } 71 | }, 72 | "update": { 73 | "fill": { 74 | "value": "steelblue" 75 | } 76 | }, 77 | "hover": { 78 | "fill": { 79 | "value": "firebrick" 80 | } 81 | } 82 | } 83 | }, 84 | { 85 | "type": "text", 86 | "from": { 87 | "data": "table" 88 | }, 89 | "properties": { 90 | "enter": { 91 | "x": { 92 | "scale": "x", 93 | "field": "data.name" 94 | }, 95 | "dx": { 96 | "value": 5 97 | }, 98 | "y": { 99 | "value": 505 100 | }, 101 | "angle": { 102 | "value": 45 103 | }, 104 | "fill": { 105 | "value": "black" 106 | }, 107 | "text": { 108 | "field": "data.name" 109 | }, 110 | "font": { 111 | "value": "Helvetica Neue" 112 | }, 113 | "fontSize": { 114 | "value": 15 115 | } 116 | } 117 | } 118 | } 119 | ] 120 | } 121 | -------------------------------------------------------------------------------- /docs/static/tng/build-db.py: -------------------------------------------------------------------------------- 1 | import csv 2 | import datetime 3 | import sys 4 | 5 | from startrek import Base 6 | from startrek import engine 7 | from startrek import DBSession 8 | from startrek import Episode 9 | from startrek import Person 10 | 11 | # Read CSV files into list. 12 | try: 13 | with open("episodes.csv") as episodes_f: 14 | with open("people.csv") as people_f: 15 | episodes = list(csv.reader(episodes_f)) 16 | people = list(csv.reader(people_f)) 17 | except IOError as e: 18 | print >>sys.stderr, "error: %s" % (e) 19 | sys.exit(1) 20 | 21 | # Start ORM classes up, and get a connection to the DB. 22 | Base.metadata.create_all(engine) 23 | session = DBSession() 24 | 25 | # Create a table of id-to-Person based on the contents of the people file. 26 | people_rec = {} 27 | for i, name in people[1:]: 28 | i = int(i) 29 | 30 | p = Person(id=i, name=name.decode("utf-8")) 31 | 32 | people_rec[i] = p 33 | session.add(p) 34 | 35 | # Construct an Episode for each record in the episode file, and add it to the 36 | # databse. 37 | for i, season, ep, title, airdate, teleplay, story, director, stardate, url in episodes[1:]: 38 | # Extract the fields that exist more or less as is. 39 | i = int(i) 40 | season = int(season) 41 | ep = int(ep) 42 | 43 | # Parse the (American-style) dates from the airdate field, creating a Python 44 | # datetime object. 45 | month, day, year = airdate.split("/") 46 | month = "%02d" % (int(month)) 47 | day = "%02d" % (int(day)) 48 | airdate = datetime.datetime.strptime("%s/%s/%s" % (month, day, year), "%m/%d/%Y") 49 | 50 | # Create lists of writers, story developers, and directors from the 51 | # comma-separated people ids in these fields. 52 | teleplay = map(lambda writer: people_rec[int(writer)], teleplay.split(",")) 53 | story = map(lambda writer: people_rec[int(writer)], story.split(",")) 54 | director = map(lambda writer: people_rec[int(writer)], director.split(",")) 55 | 56 | # Construct an Episode object, and add it to the live session. 57 | ep = Episode(id=int(i), season=season, episode=ep, title=title.decode("utf-8"), airdate=airdate, stardate=stardate, teleplay=teleplay, story=story, director=director, url=url) 58 | session.add(ep) 59 | 60 | # Commit the changes to the session. 61 | session.commit() 62 | sys.exit(0) 63 | -------------------------------------------------------------------------------- /docs/static/tng/build-db.yaml: -------------------------------------------------------------------------------- 1 | show-py: True 2 | -------------------------------------------------------------------------------- /docs/static/tng/index.html: -------------------------------------------------------------------------------- 1 | 2 | Star Trek: The Next Generation Episode Writers 3 | 4 | 5 | 6 | 7 | 8 |
9 | -------------------------------------------------------------------------------- /docs/static/tng/index.js: -------------------------------------------------------------------------------- 1 | window.onload = function () { 2 | d3.json("barchart.json", function (spec) { 3 | vg.parse.spec(spec, function (chart) { 4 | chart({ 5 | el: "#chart" 6 | }).update(); 7 | }); 8 | }); 9 | }; 10 | -------------------------------------------------------------------------------- /docs/static/tng/people.csv: -------------------------------------------------------------------------------- 1 | Index,Name 2 | 0,Gene Roddenberry 3 | 1,D.C. Fontana 4 | 2,Corey Allen 5 | 3,John D.F. Black 6 | 4,J. Michael Bingham 7 | 5,Paul Lynch 8 | 6,Katharyn Powers 9 | 7,Michael Baron 10 | 8,Russ Mayberry 11 | 9,Les Landau 12 | 10,Herbert Wright 13 | 11,Richard Krzemien 14 | 12,Richard Colla 15 | 13,Diane Duane 16 | 14,Michael Reaves 17 | 15,Rob Bowman 18 | 16,Michael Halperin 19 | 17,Cliff Bole 20 | 18,Worley Thorne 21 | 19,Ralph Wills 22 | 20,James L. Conway 23 | 21,Larry Forrester 24 | 22,C.J. Holland 25 | 23,Tracy Torme 26 | 24,Lan O'Kun 27 | 25,Richard Compton 28 | 26,Joseph L. Scanlan 29 | 27,Robert Lewin 30 | 28,Maurice Hurley 31 | 29,Patrick Barry 32 | 30,Michael Rhodes 33 | 31,Michael Michaelian 34 | 32,Hannah Lousie Shearer 35 | 33,Kim Manners 36 | 34,Robert Sabaroff 37 | 35,Karl Guers 38 | 36,Ralph Sanchez 39 | 37,Sandy Fries 40 | 38,Mike Vejar 41 | 39,Richard Manning 42 | 40,Hans Beimler 43 | 41,Win Phelps 44 | 42,Joseph Stefano 45 | 43,Deborah Dean Davis 46 | 44,Robert Becker 47 | 45,Deborah McIntyre 48 | 46,Mona Clee 49 | 47,Jaron Summers 50 | 48,Jon Povill 51 | 49,Jack B. Sowards 52 | 50,Winrich Kolbe 53 | 51,Brian Alan Lane 54 | 52,Burton Armus 55 | 53,Les Menchen 56 | 54,Lance Dickson 57 | 55,David Landsberg 58 | 56,Robert Becker 59 | 57,Joseph Zambrano 60 | 58,Larry Shaw 61 | 59,John Mason 62 | 60,Mike Gray 63 | 61,Wanda M. Haight 64 | 62,Gregory Amos 65 | 63,Melinda M. Snodgrass 66 | 64,Robert Scheerer 67 | 65,Scott Rubenstein 68 | 66,Leonard Mlodinow 69 | 67,Steve Gerber 70 | 68,Beth Woods 71 | 69,Keith Mills 72 | 70,Kurt Michael Bensmiller 73 | 71,David Assael 74 | 72,Robert L. McCullough 75 | 73,Robert Iscove 76 | 74,Terry Devereaux 77 | 75,Thomas H. Calder 78 | 76,David Kemper 79 | 77,Michael Piller 80 | 78,Michael Wagner 81 | 79,Robert Wiemer 82 | 80,Ronald D. Moore 83 | 81,Ron Roman 84 | 82,Richard Danus 85 | 83,Gabrielle Beaumont 86 | 84,David Carson 87 | 85,Sam Rolfe 88 | 86,Timothy Bond 89 | 87,Robin Bernheim 90 | 88,Ed Zuckerman 91 | 89,Ira Steven Behr 92 | 90,Trent Christopher Ganino 93 | 91,Eric A. Stillwell 94 | 92,René Echevarria 95 | 93,Jonathan Frakes 96 | 94,W. Reed Moran 97 | 95,Drew Deighan 98 | 96,Chip Chalmers 99 | 97,Dennis Putnam Bailey 100 | 98,David Bischoff 101 | 99,Sally Caves 102 | 100,Shari Goodhartz 103 | 101,Peter S. Beagle 104 | 102,Marc Cushman 105 | 103,Jake Jacobs 106 | 104,Fred Bronson 107 | 105,Susan Sackett 108 | 106,Robert Legato 109 | 107,Tom Benko 110 | 108,Rick Berman 111 | 109,John Whelpley 112 | 110,Jeri Taylor 113 | 111,Ralph Phillips 114 | 112,Lee Sheldon 115 | 113,Joe Menosky 116 | 114,Thomas Perry 117 | 115,Jo Perry 118 | 116,Brannon Braga 119 | 117,J. Larry Carroll 120 | 118,David Bennett Carren 121 | 119,Kacey Arnold-Ince 122 | 120,Hilary J. Bader 123 | 121,Alan J. Adler 124 | 122,Vanessa Greene 125 | 123,Harold Apter 126 | 124,Stuart Charno 127 | 125,Sara Charno 128 | 126,Cy Chermak 129 | 127,Philip LaZebnik 130 | 128,William Douglas Lansford 131 | 129,Bruce D. Arthurs 132 | 130,Marc Scott Zicree 133 | 131,Thomas Kartozian 134 | 132,Pamela Douglas 135 | 133,Timothy DeHaas 136 | 134,Randee Russell 137 | 135,Peter Allan Fields 138 | 136,Ted Roberts 139 | 137,Michel Horvat 140 | 138,Marvin V. Rush 141 | 139,Ken Schafer 142 | 140,David Livingston 143 | 141,Patrick Stewart 144 | 142,Lawrence V. Conley 145 | 143,Ron Jarvis 146 | 144,Philip A. Scorza 147 | 145,Grant Rosenberg 148 | 146,Pamela Gray 149 | 147,T. Michael 150 | 148,Adam Belanoff 151 | 149,James Kahn 152 | 150,Barry Schkolnick 153 | 151,Paul Schiffer 154 | 152,Rene Balcer 155 | 153,Paul Ruben 156 | 154,Naren Shankar 157 | 155,Gary Perconte 158 | 156,Edithe Swensen 159 | 157,Jean Louise Matthias 160 | 158,Ronald Wilkerson 161 | 159,Richard Fliegel 162 | 160,Robert Lederman 163 | 161,Morgan Gendel 164 | 162,Peter Lauritson 165 | 163,Frank Abatemarco 166 | 164,Alexander Singer 167 | 165,Allison Hock 168 | 166,Ward Botsford 169 | 167,Diana Dru Botsford 170 | 168,Adam Nimoy 171 | 169,Robert Hewitt Wolfe 172 | 170,Dan Curry 173 | 171,James E. Brooks 174 | 172,Michael Medlock 175 | 173,LeVar Burton 176 | 174,Jeanne Carrigan Fauci 177 | 175,Lisa Rich 178 | 176,Roger Eschbacher 179 | 177,Jaq Greenspon 180 | 178,Christopher Hatton 181 | 179,Nicholas Sagan 182 | 180,Dan Koeppel 183 | 181,Spike Steingasser 184 | 182,Jeanna F. Gallo 185 | 183,Gates McFadden 186 | 184,Mark Kalbfeld 187 | 185,Jonathan West -------------------------------------------------------------------------------- /docs/static/tng/startrek.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import create_engine 2 | engine = create_engine("sqlite:///tngeps.db", echo=True, convert_unicode=True) 3 | 4 | from sqlalchemy.ext.declarative import declarative_base 5 | Base = declarative_base() 6 | 7 | from sqlalchemy.orm import sessionmaker 8 | DBSession = sessionmaker(bind=engine) 9 | 10 | # Column types. 11 | from sqlalchemy import Column 12 | from sqlalchemy import Date 13 | from sqlalchemy import Integer 14 | from sqlalchemy import String 15 | 16 | # Other database necessities. 17 | from sqlalchemy import Table 18 | from sqlalchemy import ForeignKey 19 | from sqlalchemy.orm import relationship 20 | 21 | episode_teleplays = Table("episode_teleplays", Base.metadata, 22 | Column("episode_id", Integer, ForeignKey("episodes.id")), 23 | Column("teleplay_id", Integer, ForeignKey("people.id"))) 24 | 25 | episode_stories = Table("episode_stories", Base.metadata, 26 | Column("episode_id", Integer, ForeignKey("episodes.id")), 27 | Column("story_id", Integer, ForeignKey("people.id"))) 28 | 29 | episode_directors = Table("episode_directors", Base.metadata, 30 | Column("episode_id", Integer, ForeignKey("episodes.id")), 31 | Column("director_id", Integer, ForeignKey("people.id"))) 32 | 33 | 34 | class Episode(Base): 35 | __tablename__ = "episodes" 36 | 37 | id = Column(Integer, primary_key=True) 38 | season = Column(Integer) 39 | episode = Column(Integer) 40 | title = Column(String) 41 | airdate = Column(Date) 42 | teleplay = relationship("Person", secondary=episode_teleplays, backref="teleplays") 43 | story = relationship("Person", secondary=episode_stories, backref="stories") 44 | director = relationship("Person", secondary=episode_directors, backref="directors") 45 | stardate = Column(String) 46 | url = Column(String) 47 | 48 | def __repr__(self): 49 | return (u"Episode('%s')" % (self.title)).encode("utf-8") 50 | 51 | 52 | class Person(Base): 53 | __tablename__ = "people" 54 | 55 | id = Column(Integer, primary_key=True) 56 | name = Column(String) 57 | 58 | def __repr__(self): 59 | return (u"Person('%s')" % (self.name)).encode("utf-8") 60 | -------------------------------------------------------------------------------- /docs/static/tng/startrek.yaml: -------------------------------------------------------------------------------- 1 | show-py: True 2 | -------------------------------------------------------------------------------- /docs/static/tng/writers.py: -------------------------------------------------------------------------------- 1 | import json 2 | import tangelo 3 | 4 | from startrek import DBSession 5 | from startrek import Episode 6 | 7 | 8 | @tangelo.types(sort=json.loads) 9 | def run(sort=False): 10 | session = DBSession() 11 | 12 | count = {} 13 | episodes = session.query(Episode) 14 | for ep in episodes: 15 | seen = set() 16 | for writer in ep.teleplay: 17 | count[writer] = count.get(writer, 0) + 1 18 | seen.add(writer) 19 | 20 | for writer in ep.story: 21 | if writer not in seen: 22 | count[writer] = count.get(writer, 0) + 1 23 | 24 | results = [{"name": r.name, "count": count[r]} for r in sorted(count.keys(), key=lambda x: x.id)] 25 | if sort: 26 | results.sort(key=lambda x: x["count"], reverse=True) 27 | 28 | return results 29 | -------------------------------------------------------------------------------- /docs/static/tng/writers.yaml: -------------------------------------------------------------------------------- 1 | show-py: True 2 | -------------------------------------------------------------------------------- /docs/tangelo-manpage.rst: -------------------------------------------------------------------------------- 1 | =================== 2 | ``tangelo`` 3 | =================== 4 | 5 | | tangelo [-h] [-c FILE] [-nc] [-a] [-na] [-p] [-np] 6 | | [--hostname HOSTNAME] [--port PORT] [-u USERNAME] 7 | | [-g GROUPNAME] [-r DIR] [--vtkpython FILE] [--verbose] [--quiet] 8 | | [--version] [--key FILE] [--cert FILE] 9 | 10 | Start a Tangelo server. 11 | 12 | ================================= ============================================================================================================================ 13 | Optional argument Effect 14 | ================================= ============================================================================================================================ 15 | -h, --help show this help message and exit 16 | -c FILE, --config FILE specifies configuration file or json string to use 17 | -nc, --no-config skips looking for and using a configuration file 18 | -a, --access-auth enable HTTP authentication (i.e. processing of .htaccess files) (default) 19 | -na, --no-access-auth disable HTTP authentication (i.e. processing of .htaccess files) 20 | -p, --drop-privileges enable privilege drop when started as superuser (default) 21 | -np, --no-drop-privileges disable privilege drop when started as superuser 22 | -s, --sessions enable server-side session tracking (default) 23 | -ns, --no-drop-privileges disable server-side session tracking 24 | --hostname HOSTNAME overrides configured hostname on which to run Tangelo 25 | --port PORT overrides configured port number on which to run Tangelo 26 | -u USERNAME, --user USERNAME specifies the user to run as when root privileges are dropped 27 | -g GROUPNAME, --group GROUPNAME specifies the group to run as when root privileges are dropped 28 | -r DIR, --root DIR the directory from which Tangelo will serve content 29 | --examples serve the Tangelo example applications 30 | --verbose, -v display extra information as Tangelo runs 31 | --quiet, -q reduce the amount of information displayed 32 | --version display Tangelo version number 33 | --key FILE the path to the SSL key. You must also specify --cert to serve content over https. 34 | --cert FILE the path to the SSL certificate. You must also specify --key to serve content over https. 35 | ================================= ============================================================================================================================ 36 | 37 | Example Usage 38 | ============= 39 | 40 | To start a Tangelo server with the default configuration, serving from the 41 | current directory: :: 42 | 43 | tangelo 44 | 45 | This starts Tangelo on port 8080. 46 | 47 | To serve the example applications that come bundled with Tangelo: :: 48 | 49 | tangelo --examples 50 | 51 | To control particular options, such as the port number (overriding the value 52 | specified in the config) file: :: 53 | 54 | tangelo --port 9090 55 | -------------------------------------------------------------------------------- /docs/tangelo-passwd-manpage.rst: -------------------------------------------------------------------------------- 1 | ========================== 2 | ``tangelo-passwd`` 3 | ========================== 4 | 5 | tangelo-passwd [-h] [-c] passwordfile realm user 6 | 7 | Edit .htaccess files for Tangelo 8 | 9 | =================== ==================== 10 | Positional argument Meaning 11 | =================== ==================== 12 | passwordfile Password file 13 | realm Authentication realm 14 | user Username 15 | =================== ==================== 16 | 17 | ================= =============================== 18 | Optional argument Effect 19 | ================= =============================== 20 | -h, --help Show this help message and exit 21 | -c, --create Create new password file 22 | ================= =============================== 23 | 24 | Example Usage 25 | ============= 26 | 27 | To create a new password file: :: 28 | 29 | tangelo-passwd -c secret.txt romulus tomalak 30 | 31 | (Then type in the password as prompted.) 32 | 33 | To add a user to the file: :: 34 | 35 | tangelo-passwd secret.txt Qo\'noS martok 36 | 37 | (Again, type in password.) 38 | 39 | To overwrite a new password file on top of the old one: :: 40 | 41 | tangelo-passwd -c secret.txt betazed troi 42 | -------------------------------------------------------------------------------- /docs/templates/layout.html: -------------------------------------------------------------------------------- 1 | {% extends "!layout.html" %} 2 | 3 | {%- block extrahead %} 4 | 5 | {% endblock %} 6 | -------------------------------------------------------------------------------- /js/src/core.js: -------------------------------------------------------------------------------- 1 | // Export a global module. 2 | window.tangelo = {}; 3 | 4 | (function (tangelo) { 5 | "use strict"; 6 | 7 | // Tangelo version number. 8 | tangelo.version = function () { 9 | var version = "0.10.0-dev"; 10 | return version; 11 | }; 12 | 13 | // A namespace for plugins. 14 | tangelo.plugin = {}; 15 | 16 | // Create a plugin namespace if it does not exist; otherwise, do nothing. 17 | tangelo.ensurePlugin = function (plugin) { 18 | if (tangelo.plugin[plugin] === undefined) { 19 | tangelo.plugin[plugin] = {}; 20 | } 21 | }; 22 | 23 | // Standard way to access a plugin namespace. 24 | tangelo.getPlugin = function (plugin) { 25 | if (tangelo.plugin[plugin] === undefined) { 26 | tangelo.plugin[plugin] = {}; 27 | } 28 | 29 | return tangelo.plugin[plugin]; 30 | }; 31 | }(window.tangelo)); 32 | -------------------------------------------------------------------------------- /js/src/util.js: -------------------------------------------------------------------------------- 1 | (function (tangelo, _) { 2 | "use strict"; 3 | 4 | // A function to generate a Tangelo plugin url. 5 | tangelo.pluginUrl = function (plugin) { 6 | if (plugin === undefined) { 7 | throw new Error("argument 'plugin' is required"); 8 | } 9 | return [].concat("/plugin", plugin, Array.prototype.slice.call(arguments, 1)).join("/"); 10 | }; 11 | 12 | // Returns an object representing the query arguments (code taken from 13 | // https://developer.mozilla.org/en-US/docs/Web/API/window.location). 14 | tangelo.queryArguments = function () { 15 | var oGetVars = {}, 16 | aItKey, 17 | nKeyId, 18 | aCouples; 19 | 20 | if (window.location.search.length > 1) { 21 | for (nKeyId = 0, aCouples = window.location.search.substr(1).split("&"); nKeyId < aCouples.length; nKeyId += 1) { 22 | aItKey = aCouples[nKeyId].split("="); 23 | oGetVars[decodeURI(aItKey[0])] = aItKey.length > 1 ? decodeURI(aItKey[1]) : ""; 24 | } 25 | } 26 | 27 | return oGetVars; 28 | }; 29 | 30 | tangelo.absoluteUrl = function (path) { 31 | var trailingSlash, 32 | pathname; 33 | 34 | trailingSlash = window.location.pathname[window.location.pathname.length - 1] === "/"; 35 | 36 | // No trailing slash means the pathname references a file rather than a 37 | // directory, so strip off the final element. 38 | if (!trailingSlash) { 39 | pathname = window.location.pathname.split("/").slice(0, -1).join("/"); 40 | } else { 41 | pathname = window.location.pathname; 42 | } 43 | 44 | if (path.length > 0) { 45 | if (path[0] !== "/" && path[0] !== "~") { 46 | path = pathname + (trailingSlash ? "" : "/") + path; 47 | } 48 | } 49 | 50 | return path; 51 | }; 52 | 53 | tangelo.accessor = function (spec) { 54 | var parts, 55 | func; 56 | 57 | // Need a way to "clone" a function, so we can put properties on the 58 | // clone without affecting the original. Code adapted from 59 | // http://stackoverflow.com/a/11230005/1886928). 60 | Function.prototype.clone = function () { 61 | // jscs: disable safeContextKeyword, disallowDanglingUnderscores 62 | var cloneObj = this, 63 | temp, 64 | key; 65 | 66 | if (this.__isClone) { 67 | cloneObj = this.__clonedFrom; 68 | } 69 | 70 | temp = function () { 71 | return cloneObj.apply(this, arguments); 72 | }; 73 | 74 | for (key in this) { 75 | if (this.hasOwnProperty(key)) { 76 | temp[key] = this[key]; 77 | } 78 | } 79 | 80 | temp.__isClone = true; 81 | temp.__clonedFrom = cloneObj; 82 | 83 | return temp; 84 | // jscs: enable safeContextKeyword, disallowDanglingUnderscores 85 | }; 86 | 87 | if (spec === undefined || (_.isObject(spec) && !_.isFunction(spec) && !_.isArray(spec) && _.keys(spec).length === 0)) { 88 | func = function () { 89 | throw new Error("undefined accessor is not callable"); 90 | }; 91 | func.undefined = true; 92 | } else if (_.isFunction(spec)) { 93 | func = spec.clone(); 94 | } else if (spec.hasOwnProperty("value")) { 95 | func = function () { 96 | return spec.value; 97 | }; 98 | } else if (spec.hasOwnProperty("index")) { 99 | func = function (d, i) { 100 | return i; 101 | }; 102 | } else if (spec.hasOwnProperty("field")) { 103 | if (spec.field === ".") { 104 | func = function (d) { 105 | return d; 106 | }; 107 | } else { 108 | parts = spec.field.split("."); 109 | func = function (d) { 110 | var i; 111 | for (i = 0; i < parts.length; i += 1) { 112 | d = d[parts[i]]; 113 | if (d === undefined) { 114 | return undefined; 115 | } 116 | } 117 | return d; 118 | }; 119 | } 120 | } else { 121 | throw new Error("unknown accessor spec " + spec); 122 | } 123 | 124 | func.accessor = true; 125 | return func; 126 | }; 127 | }(window.tangelo, window._)); 128 | -------------------------------------------------------------------------------- /js/tests/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise": true, 3 | "camelcase": true, 4 | "curly": true, 5 | "eqeqeq": true, 6 | "forin": true, 7 | "immed": true, 8 | "latedef": true, 9 | "newcap": true, 10 | "noempty": false, 11 | "nonbsp": true, 12 | "nonew": true, 13 | "plusplus": false, 14 | "quotmark": "double", 15 | "undef": true, 16 | "unused": true, 17 | "strict": true, 18 | "maxparams": false, 19 | "maxdepth": false, 20 | "maxstatements": false, 21 | "maxcomplexity": false, 22 | "maxlen": false, 23 | "eqnull": true, 24 | "browser": true, 25 | "globals": { 26 | "console": false, 27 | "QUnit": false, 28 | "tangelo": false 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /js/tests/absoluteUrl.js: -------------------------------------------------------------------------------- 1 | QUnit.module("tangelo.absoluteUrl()"); 2 | 3 | QUnit.test("tangelo.absoluteUrl()", function (assert) { 4 | "use strict"; 5 | 6 | var abspath1, 7 | abspath2, 8 | relpath1, 9 | relpath2; 10 | 11 | abspath1 = "/one/two/three"; 12 | abspath2 = "~pulaski/medical/biobed"; 13 | assert.strictEqual(tangelo.absoluteUrl(abspath1), abspath1, "tangelo.absolutePath() returns absolute paths unchanged"); 14 | assert.strictEqual(tangelo.absoluteUrl(abspath2), abspath2, "tangelo.absolutePath() returns absolute paths unchanged"); 15 | 16 | relpath1 = "relative/path/warp.html"; 17 | relpath2 = "relative/path/warp.html?factor=9.2&intermix=1:1#power"; 18 | assert.strictEqual(tangelo.absoluteUrl(relpath1), "/results/js/" + relpath1, "returns relative paths appended to the current directory"); 19 | assert.strictEqual(tangelo.absoluteUrl(relpath2), "/results/js/" + relpath2, "should also work for paths with query arguments and fragment identifiers"); 20 | }); 21 | -------------------------------------------------------------------------------- /js/tests/accessor.js: -------------------------------------------------------------------------------- 1 | QUnit.module("tangelo.accessor"); 2 | 3 | var data = { 4 | oranges: "tangelos", 5 | lemons: { 6 | car: "jalopy", 7 | fruit: "citrus" 8 | } 9 | }; 10 | 11 | QUnit.test("Undefined accessors display 'undefined' property", function (assert) { 12 | "use strict"; 13 | 14 | var undef1 = tangelo.accessor(), 15 | undef2 = tangelo.accessor({}); 16 | 17 | assert.expect(2); 18 | 19 | assert.strictEqual(undef1.undefined, true); 20 | assert.strictEqual(undef2.undefined, true); 21 | }); 22 | 23 | QUnit.test("Undefined accessor throws exception when called", function (assert) { 24 | "use strict"; 25 | 26 | assert.expect(2); 27 | 28 | assert.throws(tangelo.accessor()); 29 | assert.throws(tangelo.accessor({})); 30 | }); 31 | 32 | QUnit.test("Defined accessors do not display 'undefined' property", function (assert) { 33 | "use strict"; 34 | 35 | var value = tangelo.accessor({value: 10}); 36 | 37 | assert.expect(1); 38 | 39 | assert.strictEqual(value.undefined, undefined); 40 | }); 41 | 42 | QUnit.test("Value spec produces accessor", function (assert) { 43 | "use strict"; 44 | 45 | var value = tangelo.accessor({value: 10}); 46 | 47 | assert.expect(1); 48 | 49 | assert.strictEqual(value(data), 10); 50 | }); 51 | 52 | QUnit.test("Index spec produces accessor", function (assert) { 53 | "use strict"; 54 | 55 | var index = tangelo.accessor({index: true}); 56 | 57 | assert.expect(1); 58 | 59 | assert.strictEqual(index(data, 5), 5); 60 | }); 61 | 62 | QUnit.test("Field spec produces accessor", function (assert) { 63 | "use strict"; 64 | 65 | var field1 = tangelo.accessor({field: "oranges"}), 66 | field2 = tangelo.accessor({field: "lemons.car"}), 67 | field3 = tangelo.accessor({field: "."}); 68 | 69 | assert.expect(4); 70 | 71 | assert.strictEqual(field1(data), "tangelos"); 72 | assert.strictEqual(field2(data), "jalopy"); 73 | assert.strictEqual(field2({}), undefined); 74 | assert.strictEqual(field3(4), 4); 75 | }); 76 | 77 | QUnit.test("Unknown spec throws exception", function (assert) { 78 | "use strict"; 79 | 80 | assert.expect(2); 81 | 82 | assert.throws(function () { 83 | return tangelo.accessor({invalid: "quux"}); 84 | }); 85 | assert.throws(tangelo.accessor(undefined)); 86 | }); 87 | 88 | QUnit.test("Clone a function (twice)", function (assert) { 89 | "use strict"; 90 | 91 | var id, 92 | clone1, 93 | clone2; 94 | 95 | id = function (d) { 96 | return d; 97 | }; 98 | clone1 = tangelo.accessor(id); 99 | clone2 = tangelo.accessor(clone1); 100 | 101 | assert.expect(2); 102 | 103 | assert.strictEqual(clone1(10), 10); 104 | assert.strictEqual(clone2(10), 10); 105 | }); 106 | -------------------------------------------------------------------------------- /js/tests/config.js: -------------------------------------------------------------------------------- 1 | QUnit.module("config plugin"); 2 | 3 | QUnit.test("Non-required non-existing config file", function (assert) { 4 | "use strict"; 5 | 6 | QUnit.stop(); 7 | 8 | tangelo.plugin.config.config("/data/doesntexist.yaml", false, function (config) { 9 | QUnit.start(); 10 | 11 | assert.deepEqual(config, {}, "Non required config file should return empty object when file doesn't exist"); 12 | }); 13 | }); 14 | 15 | QUnit.test("Required non-existing config file", function (assert) { 16 | "use strict"; 17 | 18 | QUnit.stop(); 19 | 20 | tangelo.plugin.config.config("/data/doesntexist.yaml", true, function (config, error) { 21 | QUnit.start(); 22 | 23 | assert.strictEqual(config, undefined, "Required config file should return undefined when file doesn't exist"); 24 | assert.ok(error.error, "'error' parameter contains an error message"); 25 | assert.strictEqual(error.file, "/data/doesntexist.yaml", "'error' parameter named the missing file"); 26 | }); 27 | }); 28 | 29 | QUnit.test("Non-required existing config file", function (assert) { 30 | "use strict"; 31 | 32 | QUnit.stop(); 33 | 34 | tangelo.plugin.config.config("/data/config.yaml", false, function (config) { 35 | QUnit.start(); 36 | 37 | assert.deepEqual(config, {captain: "picard", firstOfficer: "riker", counselor: "troi"}, "Non required config file should return correct config when file does exist"); 38 | }); 39 | }); 40 | 41 | QUnit.test("Required existing config file", function (assert) { 42 | "use strict"; 43 | 44 | QUnit.stop(); 45 | 46 | tangelo.plugin.config.config("/data/config.yaml", true, function (config) { 47 | QUnit.start(); 48 | 49 | assert.deepEqual(config, {captain: "picard", firstOfficer: "riker", counselor: "troi"}, "Required config file should return correct config when file does exist"); 50 | }); 51 | }); 52 | 53 | QUnit.test("default 'required' parameter value", function (assert) { 54 | "use strict"; 55 | 56 | QUnit.stop(2); 57 | 58 | tangelo.plugin.config.config("/data/doesntexist.yaml", function (config) { 59 | QUnit.start(); 60 | 61 | assert.deepEqual(config, {}, "'required' defaults to false (non-existing config file)"); 62 | }); 63 | 64 | tangelo.plugin.config.config("/data/config.yaml", function (config) { 65 | QUnit.start(); 66 | 67 | assert.deepEqual(config, {captain: "picard", firstOfficer: "riker", counselor: "troi"}, "'required' defaults to false (existing config file)"); 68 | }); 69 | }); 70 | 71 | QUnit.test("'url' parameter is required", function (assert) { 72 | "use strict"; 73 | 74 | assert.throws(tangelo.plugin.config.config); 75 | }); 76 | -------------------------------------------------------------------------------- /js/tests/data/config.yaml: -------------------------------------------------------------------------------- 1 | captain: picard 2 | firstOfficer: riker 3 | counselor: troi 4 | -------------------------------------------------------------------------------- /js/tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | Test Results 3 | 4 |

5 | JavaScript Unit and Coverage Tests 6 |

7 | 8 |

9 | Python Coverage Tests 10 |

11 | -------------------------------------------------------------------------------- /js/tests/jade/qunitHarness.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | head 3 | meta(charset="utf-8") 4 | title tangelo.js unit tests 5 | link(rel="stylesheet", href="//code.jquery.com/qunit/qunit-1.15.0.css") 6 | script(src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.7.0/underscore-min.js") 7 | script(src="//code.jquery.com/jquery-1.11.1.min.js") 8 | script(src="/plugin/tangelo/tangelo.js" data-cover) 9 | script(src="/plugin/config/config.js" data-cover) 10 | script(src="/plugin/data/bin.js" data-cover) 11 | script(src="/plugin/data/distanceCluster.js" data-cover) 12 | script(src="/plugin/data/smooth.js" data-cover) 13 | script(src="/plugin/stream/stream.js" data-cover) 14 | 15 | body 16 | div#qunit 17 | div#qunit-fixture 18 | script(src="//code.jquery.com/qunit/qunit-1.15.0.js") 19 | script. 20 | // This allows the correct coverage reporter to be used - the grunt 21 | // reporter if Phantom is being used to run the tests, and the default 22 | // HTML reporter otherwise. 23 | (function () { 24 | var phantom = window.navigator.userAgent.indexOf("PhantomJS") !== -1; 25 | if (phantom) { 26 | document.write(' 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 20 | -------------------------------------------------------------------------------- /tangelo/tangelo/pkgdata/plugin/bokeh/web/examples/iris/iris.py: -------------------------------------------------------------------------------- 1 | import tangelo.plugin.bokeh 2 | from bokeh.sampledata.iris import flowers 3 | from bokeh.plotting import scatter 4 | 5 | 6 | @tangelo.return_type(tangelo.plugin.bokeh.bokeh) 7 | def run(): 8 | colormap = {'setosa': 'red', 'versicolor': 'green', 'virginica': 'blue'} 9 | flowers['color'] = flowers['species'].map(lambda x: colormap[x]) 10 | return scatter(flowers["petal_length"], flowers["petal_width"], color=flowers["color"], fill_alpha=0.2, size=10, name="iris") 11 | -------------------------------------------------------------------------------- /tangelo/tangelo/pkgdata/plugin/config/web/config.js: -------------------------------------------------------------------------------- 1 | // Attempts to retrieve a JSON-encoded configuration from `url`. On success, 2 | // the JSON object is passed to `callback`. If there is not a file at `url`, 3 | // and `required` is set to false, `callback` is invoked with an empty object. 4 | // An error results in `callback` being invoked with undefined as the first 5 | // argument and the error string as the second. The `required` parameter can be 6 | // omitted, and defaults to false in that case. 7 | (function (tangelo, _, $) { 8 | "use strict"; 9 | 10 | tangelo.ensurePlugin("config"); 11 | 12 | tangelo.plugin.config.config = function (url, required, callback) { 13 | if (url === undefined) { 14 | throw new Error("'url' parameter is required"); 15 | } 16 | 17 | // This allows a default value for the `required` parameter. 18 | if (callback === undefined && _.isFunction(required)) { 19 | callback = required; 20 | required = false; 21 | } 22 | 23 | // Convert the URL to an absolute URL. 24 | url = tangelo.absoluteUrl(url); 25 | 26 | // Fire the request to the config service. 27 | $.ajax({ 28 | url: tangelo.pluginUrl("config", "config", url + (required ? "?required" : "")), 29 | dataType: "json", 30 | error: function (jqxhr) { 31 | switch (jqxhr.status) { 32 | case 400: { 33 | callback(undefined, jqxhr.responseJSON, jqxhr); 34 | break; 35 | } 36 | 37 | default: { 38 | callback(undefined, undefined, jqxhr); 39 | break; 40 | } 41 | } 42 | }, 43 | success: function (data) { 44 | if (data.result) { 45 | callback(data.result); 46 | } else { 47 | callback(undefined, data); 48 | } 49 | } 50 | }); 51 | }; 52 | }(window.tangelo, window._, window.jQuery)); 53 | -------------------------------------------------------------------------------- /tangelo/tangelo/pkgdata/plugin/config/web/config.py: -------------------------------------------------------------------------------- 1 | import tangelo 2 | import tangelo.util 3 | from tangelo.server import analyze_url 4 | from tangelo.server import Content 5 | 6 | 7 | def run(*path, **query): 8 | if len(path) == 0: 9 | tangelo.http_status(400, "Missing Path") 10 | return {"error": "missing path to config file"} 11 | 12 | required = query.get("required") is not None 13 | 14 | url = "/" + "/".join(path) 15 | content = analyze_url(url).content 16 | 17 | if content is None or content.type not in [Content.File, Content.NotFound]: 18 | tangelo.http_status(400, "Illegal Path") 19 | return {"error": "illegal web path (path does not point to a config file)"} 20 | elif content.type == Content.NotFound: 21 | if required: 22 | return {"error": "File not found", 23 | "file": url} 24 | else: 25 | return {"result": {}} 26 | 27 | try: 28 | config = tangelo.util.yaml_safe_load(content.path, dict) 29 | except IOError: 30 | tangelo.http_status(404) 31 | return {"error": "could not open file at %s" % (url)} 32 | except TypeError: 33 | tangelo.http_status(400, "Not An Associative Array") 34 | return {"error": "file at %s did not contain a top-level associative array" % (url)} 35 | except ValueError as e: 36 | tangelo.http_status(400, "YAML Error") 37 | return {"error": "could not parse YAML from file at %s: %s" % (url, e.message)} 38 | 39 | return {"result": config} 40 | -------------------------------------------------------------------------------- /tangelo/tangelo/pkgdata/plugin/data/web/bin.js: -------------------------------------------------------------------------------- 1 | (function (tangelo) { 2 | "use strict"; 3 | 4 | tangelo.ensurePlugin("data"); 5 | var dataPlugin = tangelo.plugin.data; 6 | 7 | function makeBins(data, value, minArg, maxArg, nBins) { 8 | var min = Number.POSITIVE_INFINITY, 9 | max = Number.NEGATIVE_INFINITY, 10 | bins = [], 11 | dx, 12 | i; 13 | 14 | // coerce values into numbers 15 | minArg = Number(minArg); 16 | maxArg = Number(maxArg); 17 | 18 | // create an array of bin objects 19 | if (!isFinite(minArg) || !isFinite(maxArg)) { 20 | // we need to calculate data extent 21 | data.forEach(function (d) { 22 | var v = Number(value(d)); 23 | if (!isFinite(v)) { 24 | throw new Error("Invalid numeric value in data array: " + v.toString()); 25 | } else { 26 | if (v < min) { 27 | min = v; 28 | } 29 | if (v > max) { 30 | max = v; 31 | } 32 | } 33 | }); 34 | 35 | if (!isFinite(minArg)) { 36 | minArg = min; 37 | } 38 | if (!isFinite(maxArg)) { 39 | maxArg = max; 40 | } 41 | } 42 | 43 | // degenerate case with only one value 44 | if (maxArg === minArg) { 45 | minArg = minArg - 0.5; 46 | maxArg = maxArg + 0.5; 47 | } 48 | 49 | // calculate bin width 50 | dx = (maxArg - minArg) / nBins; 51 | 52 | // generate the bins 53 | for (i = 0; i < nBins; i += 1) { 54 | bins.push({ 55 | min: minArg + i * dx, 56 | max: minArg + (i + 1) * dx, 57 | count: 0 58 | }); 59 | } 60 | 61 | return bins; 62 | } 63 | 64 | dataPlugin.bin = function (spec) { 65 | var maxBinValue; 66 | 67 | spec = spec || {}; 68 | spec.data = spec.data || []; 69 | spec.nBins = spec.nBins || 25; 70 | spec.value = tangelo.accessor(spec.value || {field: "value"}); 71 | 72 | if (!spec.data.length) { 73 | // there's no data, we can't do anything 74 | return []; 75 | } 76 | 77 | // create the bins if they aren't given by the user 78 | if (!spec.bins) { 79 | spec.bins = makeBins(spec.data, spec.value, spec.min, spec.max, spec.nBins); 80 | } 81 | 82 | // get the maximum value in the bins to account for the special 83 | // case when the data value is on the right edge 84 | maxBinValue = Number.NEGATIVE_INFINITY; 85 | spec.bins.forEach(function (b) { 86 | maxBinValue = Math.max(maxBinValue, b.max); 87 | }); 88 | 89 | // bin the data 90 | spec.data.forEach(function (d) { 91 | var v = Number(spec.value(d)); 92 | if (!isFinite(v)) { 93 | throw new Error("Invalid numeric value in data array: " + v.toString()); 94 | } 95 | 96 | // loop through the bins to find where the data belongs 97 | spec.bins.forEach(function (b) { 98 | // this statement adds the value to the bin if 99 | // 1. it is in [b.min, b.max) or 100 | // 2. it is the largest bin and in [b.min, b.max]. 101 | if (b.min <= v && (b.max > v || (b.max === maxBinValue && b.max === v))) { 102 | b.count = (b.count || 0) + 1; 103 | } 104 | }); 105 | }); 106 | 107 | return spec.bins; 108 | }; 109 | }(window.tangelo)); 110 | -------------------------------------------------------------------------------- /tangelo/tangelo/pkgdata/plugin/data/web/tree.js: -------------------------------------------------------------------------------- 1 | (function (tangelo) { 2 | "use strict"; 3 | 4 | tangelo.ensurePlugin("data"); 5 | var dataPlugin = tangelo.plugin.data; 6 | 7 | dataPlugin.tree = function (spec) { 8 | var id = tangelo.accessor(spec.id || {value: ""}), 9 | idChild = tangelo.accessor(spec.idChild || {value: ""}), 10 | children = tangelo.accessor(spec.children), 11 | data = spec.data, 12 | nodeMap = {}, 13 | root; 14 | 15 | if (children.undefined) { 16 | throw new Error("A 'children' accessor is required"); 17 | } 18 | 19 | data.forEach(function (d) { 20 | nodeMap[id(d)] = d; 21 | }); 22 | 23 | data.forEach(function (d) { 24 | if (children(d)) { 25 | d.children = []; 26 | children(d).forEach(function (c) { 27 | var child = nodeMap[idChild(c)]; 28 | child.hasParent = true; 29 | d.children.push(child); 30 | }); 31 | } 32 | }); 33 | 34 | data.forEach(function (d) { 35 | if (!d.hasParent) { 36 | root = d; 37 | } 38 | delete d.hasParent; 39 | }); 40 | 41 | return root; 42 | }; 43 | }(window.tangelo)); 44 | -------------------------------------------------------------------------------- /tangelo/tangelo/pkgdata/plugin/girder/control.py: -------------------------------------------------------------------------------- 1 | from girder.utility.server import configureServer 2 | 3 | 4 | def setup(config, store): 5 | config = {"server": {"api_root": "/plugin/girder/girder/api/v1", 6 | "static_root": "/plugin/girder/girder/static"}} 7 | girder_app, config = configureServer(curConfig=config) 8 | 9 | return {"apps": [(girder_app, config, "girder")]} 10 | -------------------------------------------------------------------------------- /tangelo/tangelo/pkgdata/plugin/girder/info.txt: -------------------------------------------------------------------------------- 1 | Before using this plugin, you will need to install the Girder web client by 2 | issuing the following command: 3 | 4 | girder-install web 5 | 6 | This installs the materials necessary to actually interact with the Girder 7 | server directly from the browser, or via a Tangelo application. 8 | -------------------------------------------------------------------------------- /tangelo/tangelo/pkgdata/plugin/girder/requirements.txt: -------------------------------------------------------------------------------- 1 | girder==1.1.0 2 | -------------------------------------------------------------------------------- /tangelo/tangelo/pkgdata/plugin/impala/web/impala.py: -------------------------------------------------------------------------------- 1 | import impala 2 | 3 | 4 | def convert(value, type): 5 | if type == "tinyint": 6 | return int(value) 7 | elif type == "int": 8 | return int(value) 9 | elif type == "double": 10 | return float(value) 11 | elif type == "string": 12 | return value 13 | elif type == "boolean": 14 | return True if value == "true" else False 15 | return None 16 | 17 | 18 | def convert_results(results, fields=False): 19 | schema = results.schema.fieldSchemas 20 | converted = [] 21 | for d in results.data: 22 | parts = d.split("\t") 23 | if fields: 24 | row = {} 25 | for i in range(len(parts)): 26 | row[schema[i].name] = convert(parts[i], schema[i].type) 27 | else: 28 | row = [] 29 | for i in range(len(parts)): 30 | row.append(convert(parts[i], schema[i].type)) 31 | converted.append(row) 32 | return converted 33 | 34 | 35 | def run(q="select 1 as testing", host="localhost", 36 | port="21000", fields="true"): 37 | client = impala.ImpalaBeeswaxClient(host + ':' + port) 38 | client.connect() 39 | results = client.execute(q) 40 | return convert_results(results, fields=(fields == "true")) 41 | -------------------------------------------------------------------------------- /tangelo/tangelo/pkgdata/plugin/mapping/web/examples/flickr/config.yaml: -------------------------------------------------------------------------------- 1 | mongodb-server: localhost 2 | mongodb-db: tangelo 3 | mongodb-coll: flickr_paris 4 | -------------------------------------------------------------------------------- /tangelo/tangelo/pkgdata/plugin/mapping/web/examples/flickr/wait.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kitware/tangelo/470034ee9b3d7a01becc1ce5fddc7adc1d5263ef/tangelo/tangelo/pkgdata/plugin/mapping/web/examples/flickr/wait.gif -------------------------------------------------------------------------------- /tangelo/tangelo/pkgdata/plugin/mapping/web/examples/geodots/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | geodots 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 30 | 31 | 49 | 50 | 60 | 61 |
62 | -------------------------------------------------------------------------------- /tangelo/tangelo/pkgdata/plugin/mapping/web/examples/geodots/index.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | "use strict"; 3 | 4 | var data = [ 5 | {lat: 0, lon: 0, value: 5, group: 'a'}, 6 | {lat: 5, lon: -5, value: 6, group: 'a'}, 7 | {lat: 10, lon: -10, value: 7, group: 'a'}, 8 | {lat: 15, lon: -15, value: 8, group: 'b'}, 9 | {lat: 20, lon: -20, value: 9, group: 'b'}, 10 | {lat: 25, lon: -25, value: 10, group: 'b'}, 11 | {lat: 30, lon: -30, value: 11, group: 'c'}, 12 | {lat: 35, lon: -35, value: 12, group: 'c'}, 13 | {lat: 40, lon: -40, value: 13, group: 'c'}, 14 | {lat: 45, lon: -45, value: 14, group: 'd'}, 15 | {lat: 50, lon: -50, value: 15, group: 'd'}, 16 | {lat: 55, lon: -55, value: 16, group: 'd'}, 17 | {lat: 60, lon: -60, value: 17, group: 'e'}, 18 | {lat: 65, lon: -65, value: 18, group: 'e'}, 19 | {lat: 70, lon: -70, value: 19, group: 'e'} 20 | ]; 21 | 22 | $("#content").geodots({ 23 | data: data, 24 | worldGeometry: "../world-110m.json", 25 | latitude: tangelo.accessor({field: "lat"}), 26 | longitude: tangelo.accessor({field: "lon"}), 27 | size: tangelo.accessor({field: "value"}), 28 | color: tangelo.accessor({field: "group"}) 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /tangelo/tangelo/pkgdata/plugin/mapping/web/examples/geojsMap/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | geojsMap 5 | 6 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 48 | 49 | 59 | 60 |
61 | -------------------------------------------------------------------------------- /tangelo/tangelo/pkgdata/plugin/mapping/web/examples/geojsMap/index.js: -------------------------------------------------------------------------------- 1 | /*jshint jquery: true */ 2 | 3 | $(function () { 4 | "use strict"; 5 | 6 | var data = [ 7 | ["New York", "NY", 40.757929, -73.985506], 8 | ["Los Angeles", "CA", 34.052187, -118.243425], 9 | ["Denver", "CO", 39.755092, -104.988123], 10 | ["Portland", "OR", 45.523104, -122.670132], 11 | ["Honolulu", "HI", 21.291982, -157.821856], 12 | ["Anchorage", "AK", 61.216583, -149.899597], 13 | ["Dallas", "TX", 32.781078, -96.797111], 14 | ["Salt Lake City", "UT", 40.771592, -111.888189], 15 | ["Miami", "FL", 25.774252, -80.190262], 16 | ["Phoenix", "AZ", 33.448263, -112.073821], 17 | ["Chicago", "IL", 41.879535, -87.624333], 18 | ["Washington", "DC", 38.892091, -77.024055], 19 | ["Seattle", "WA", 47.620716, -122.347533], 20 | ["New Orleans", "LA", 30.042487, -90.025126], 21 | ["San Francisco", "CA", 37.775196, -122.419204], 22 | ["Atlanta", "GA", 33.754487, -84.389663] 23 | ]; 24 | 25 | $("#content").geojsMap({ 26 | tileUrl: "http://otile1.mqcdn.com/tiles/1.0.0/map///.png", 27 | center: { 28 | x: -(98 + 35/60), 29 | y: 39 + 50/60 30 | }, 31 | zoom: 0, 32 | data: data, 33 | layers: [{ 34 | renderer: "d3", 35 | features: [{ 36 | type: "point", 37 | position: function (d) { 38 | return { 39 | x: d[3], 40 | y: d[2] 41 | }; 42 | }, 43 | radius: 10, 44 | fillColor: function (d) { 45 | return d[1]; 46 | }, 47 | fillOpacity: 1.0, 48 | stroke: false 49 | }] 50 | }] 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /tangelo/tangelo/pkgdata/plugin/mapping/web/examples/geojsdots/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | geojsdots 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 35 | 36 | 54 | 55 | 65 | 66 |
67 | -------------------------------------------------------------------------------- /tangelo/tangelo/pkgdata/plugin/mapping/web/examples/geojsdots/index.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | "use strict"; 3 | 4 | var data, 5 | color; 6 | 7 | data = [ 8 | {lat: 0, lon: 0, value: 5, group: 'a'}, 9 | {lat: 5, lon: -5, value: 6, group: 'a'}, 10 | {lat: 10, lon: -10, value: 7, group: 'a'}, 11 | {lat: 15, lon: -15, value: 8, group: 'b'}, 12 | {lat: 20, lon: -20, value: 9, group: 'b'}, 13 | {lat: 25, lon: -25, value: 10, group: 'b'}, 14 | {lat: 30, lon: -30, value: 11, group: 'c'}, 15 | {lat: 35, lon: -35, value: 12, group: 'c'}, 16 | {lat: 40, lon: -40, value: 13, group: 'c'}, 17 | {lat: 45, lon: -45, value: 14, group: 'd'}, 18 | {lat: 50, lon: -50, value: 15, group: 'd'}, 19 | {lat: 55, lon: -55, value: 16, group: 'd'}, 20 | {lat: 60, lon: -60, value: 17, group: 'e'}, 21 | {lat: 65, lon: -65, value: 18, group: 'e'}, 22 | {lat: 70, lon: -70, value: 19, group: 'e'} 23 | ]; 24 | 25 | $("#content").geojsdots({ 26 | data: data, 27 | latitude: tangelo.accessor({field: "lat"}), 28 | longitude: tangelo.accessor({field: "lon"}), 29 | size: tangelo.accessor({field: "value"}), 30 | color: tangelo.accessor({field: "group"}) 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /tangelo/tangelo/pkgdata/plugin/mapping/web/examples/geonodelink/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | geonodelink 5 | 6 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 50 | 51 | 61 | 62 |
63 | -------------------------------------------------------------------------------- /tangelo/tangelo/pkgdata/plugin/mapping/web/examples/geonodelink/index.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | "use strict"; 3 | 4 | var data = { 5 | nodes: [ 6 | {lat: 0, lon: 0, value: 10, group: 'a', name: 'node 1'}, 7 | {lat: 10, lon: 10, value: 20, group: 'a', name: 'node 2'}, 8 | {lat: 20, lon: 20, value: 30, group: 'b', name: 'node 3'}, 9 | {lat: 30, lon: 30, value: 40, group: 'b', name: 'node 4'}, 10 | {lat: 40, lon: 20, value: 50, group: 'c', name: 'node 5'}, 11 | {lat: 50, lon: 10, value: 60, group: 'c', name: 'node 6'}, 12 | {lat: 60, lon: 0, value: 70, group: 'd', name: 'node 7'} 13 | ], 14 | links: [ 15 | {s: 0, t: 1}, 16 | {s: 1, t: 2}, 17 | {s: 2, t: 3}, 18 | {s: 3, t: 4}, 19 | {s: 4, t: 5}, 20 | {s: 5, t: 6}, 21 | {s: 6, t: 0} 22 | ] 23 | }; 24 | 25 | $("#content").geonodelink({ 26 | data: data, 27 | worldGeometry: "../world-110m.json", 28 | linkSource: tangelo.accessor({field: "s"}), 29 | linkTarget: tangelo.accessor({field: "t"}), 30 | nodeLatitude: tangelo.accessor({field: "lat"}), 31 | nodeLongitude: tangelo.accessor({field: "lon"}), 32 | nodeSize: tangelo.accessor({field: "value"}), 33 | nodeColor: tangelo.accessor({field: "group"}) 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /tangelo/tangelo/pkgdata/plugin/mapping/web/examples/mapdots/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | mapdots 5 | 6 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 53 | 54 | 64 | 65 |
66 | -------------------------------------------------------------------------------- /tangelo/tangelo/pkgdata/plugin/mapping/web/examples/mapdots/index.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | "use strict"; 3 | 4 | var data = [ 5 | {lat: 0, lon: 0, value: 5, group: 'a'}, 6 | {lat: 5, lon: -5, value: 6, group: 'a'}, 7 | {lat: 10, lon: -10, value: 7, group: 'a'}, 8 | {lat: 15, lon: -15, value: 8, group: 'b'}, 9 | {lat: 20, lon: -20, value: 9, group: 'b'}, 10 | {lat: 25, lon: -25, value: 10, group: 'b'}, 11 | {lat: 30, lon: -30, value: 11, group: 'c'}, 12 | {lat: 35, lon: -35, value: 12, group: 'c'}, 13 | {lat: 40, lon: -40, value: 13, group: 'c'}, 14 | {lat: 45, lon: -45, value: 14, group: 'd'}, 15 | {lat: 50, lon: -50, value: 15, group: 'd'}, 16 | {lat: 55, lon: -55, value: 16, group: 'd'}, 17 | {lat: 60, lon: -60, value: 17, group: 'e'}, 18 | {lat: 65, lon: -65, value: 18, group: 'e'}, 19 | {lat: 70, lon: -70, value: 19, group: 'e'} 20 | ]; 21 | 22 | $("#content").mapdots({ 23 | data: data, 24 | latitude: tangelo.accessor({field: "lat"}), 25 | longitude: tangelo.accessor({field: "lon"}), 26 | size: tangelo.accessor({field: "value"}), 27 | color: tangelo.accessor({field: "group"}) 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /tangelo/tangelo/pkgdata/plugin/mapping/web/geodots.js: -------------------------------------------------------------------------------- 1 | (function (tangelo, $, vg) { 2 | "use strict"; 3 | 4 | $.widget("tangelo.geodots", { 5 | options: { 6 | latitude: tangelo.accessor({value: 0}), 7 | longitude: tangelo.accessor({value: 0}), 8 | size: tangelo.accessor({value: 20}), 9 | color: tangelo.accessor({value: 0}), 10 | worldGeometry: null, 11 | data: null 12 | }, 13 | 14 | _create: function () { 15 | var vegaspec = tangelo.plugin.mapping.geovis(this.options.worldGeometry); 16 | 17 | vg.parse.spec(vegaspec, _.bind(function (chart) { 18 | this.vis = chart; 19 | 20 | this._update(); 21 | }, this)); 22 | }, 23 | 24 | _update: function () { 25 | if (this.options.data) { 26 | this.options.data.forEach(_.bind(function (d) { 27 | d.latitude = this.options.latitude(d); 28 | d.longitude = this.options.longitude(d); 29 | d.size = this.options.size(d); 30 | d.color = this.options.color(d); 31 | }, this)); 32 | 33 | if (this.vis) { 34 | this.vis({ 35 | el: this.element.get(0), 36 | data: { 37 | table: this.options.data, 38 | links: [] 39 | } 40 | }).update(); 41 | } 42 | } 43 | } 44 | }); 45 | }(window.tangelo, window.jQuery, window.vg)); 46 | -------------------------------------------------------------------------------- /tangelo/tangelo/pkgdata/plugin/mapping/web/geojsMap.js: -------------------------------------------------------------------------------- 1 | (function (tangelo, geo, d3, $, _) { 2 | "use strict"; 3 | 4 | $.widget("tangelo.geojsMap", { 5 | options: { 6 | // baseLayer, 7 | // initial center, 8 | // etc. 9 | zoom: 3, 10 | width: null, 11 | height: null 12 | }, 13 | latlng2display: function (pt) { 14 | return this.svgLayer.renderer().worldToDisplay(pt); 15 | }, 16 | display2latlng: function (pt) { 17 | return this.svgLayer.renderer().displayToWorld(pt); 18 | }, 19 | svg: function () { // interactive svg layer 20 | return this.svgGroup.node(); 21 | }, 22 | legend: function () { // non-interactive svg on top 23 | throw new Error("Legend layer not yet implemented"); 24 | }, 25 | map: function () { // return the geojs map object 26 | return this._map; 27 | }, 28 | scale: function () { // return the current map scale 29 | return this.svgLayer.renderer().scaleFactor(); 30 | }, 31 | _create: function () { 32 | var node = this.element.get(0), 33 | opts = { 34 | zoom: this.options.zoom, 35 | node: node, 36 | width: this.options.width, 37 | height: this.options.height 38 | }; 39 | this._map = geo.map(opts); 40 | this._map.createLayer("osm"); 41 | this.svgLayer = this._map.createLayer("feature", {renderer: "d3Renderer"}); 42 | this.svgGroup = this.svgLayer.renderer().canvas(); 43 | 44 | this._resize(); 45 | $(window).resize(_.bind(function () { 46 | this._resize(); 47 | }, this)); 48 | this.svgLayer.on(geo.event.d3Rescale, function (arg) { 49 | $(node).trigger("rescale", arg.scale); 50 | }); 51 | }, 52 | _update: $.noop, 53 | _resize: function () { 54 | var w = this.options.width || 55 | this.element.width(), 56 | h = this.options.height || 57 | this.element.height(); 58 | 59 | if (!this._map) { 60 | return; 61 | } 62 | 63 | this._map.resize(0, 0, w, h); 64 | }, 65 | _setOption: function (key, value) { 66 | this.options[key] = value; 67 | if (key === "width" || key === "height") { 68 | this._resize(); 69 | } 70 | this._update(); 71 | } 72 | }); 73 | /* 74 | * User listens to events and respondes to them as necessary. 75 | * "rescale"... the map zoomed in or out, so rescale features if necessary 76 | */ 77 | }(window.tangelo, window.geo, window.d3, window.jQuery, window._)); 78 | -------------------------------------------------------------------------------- /tangelo/tangelo/pkgdata/plugin/mapping/web/geojsdots.js: -------------------------------------------------------------------------------- 1 | (function (tangelo, $, d3, geo, _) { 2 | "use strict"; 3 | 4 | $.widget("tangelo.geojsdots", $.tangelo.geojsMap, { 5 | options: { 6 | latitude: tangelo.accessor({value: 0}), 7 | longitude: tangelo.accessor({value: 0}), 8 | size: tangelo.accessor({value: 20}), 9 | color: tangelo.accessor({value: 0}), 10 | data: null 11 | }, 12 | 13 | _create: function () { 14 | this.colorScale = d3.scale.category10(); 15 | this._super(); 16 | this.element.on("rescale", _.bind(function () { 17 | this._rescale(); 18 | }, this)); 19 | this._update(); 20 | }, 21 | 22 | _rescale: function () { 23 | var scale; 24 | if (this.options.data && this.map()) { 25 | scale = this.scale(); 26 | d3.select(this.svg()) 27 | .selectAll(".point") 28 | .data(this.options.data) 29 | .attr("r", _.bind(function (d) { 30 | return tangelo.accessor(this.options.size)(d) / scale; 31 | }, this)); 32 | } 33 | }, 34 | 35 | _update: function () { 36 | var svg = this.svg(), 37 | lat = tangelo.accessor(this.options.latitude), 38 | lng = tangelo.accessor(this.options.longitude), 39 | pt, 40 | selection, 41 | enter, 42 | exit; 43 | 44 | if (this.options.data && this.map()) { 45 | _.each(this.options.data, function (d) { 46 | pt = geo.latlng(lat(d), lng(d)); 47 | d._georef = this.latlng2display(pt); 48 | }, this); 49 | selection = d3.select(svg).selectAll(".point").data(this.options.data); 50 | enter = selection.enter(); 51 | exit = selection.exit(); 52 | 53 | enter.append("circle") 54 | .attr("class", "point"); 55 | 56 | selection.attr("cx", tangelo.accessor({field: "_georef.x"})) 57 | .attr("cy", tangelo.accessor({field: "_georef.y"})) 58 | .style("fill", _.bind(function (d) { 59 | return this.colorScale(this.options.color(d)); 60 | }, this)); 61 | 62 | exit.remove(); 63 | 64 | this._rescale(); 65 | } 66 | } 67 | }); 68 | }(window.tangelo, window.jQuery, window.d3, window.geo, window._)); 69 | -------------------------------------------------------------------------------- /tangelo/tangelo/pkgdata/plugin/mapping/web/geonodelink.js: -------------------------------------------------------------------------------- 1 | (function (tangelo, $, vg, _) { 2 | "use strict"; 3 | 4 | $.widget("tangelo.geonodelink", { 5 | options: { 6 | nodeLatitude: tangelo.accessor({value: 0}), 7 | nodeLongitude: tangelo.accessor({value: 0}), 8 | nodeSize: tangelo.accessor({value: 20}), 9 | nodeColor: tangelo.accessor({value: 0}), 10 | linkColor: tangelo.accessor({value: 0}), 11 | linkSource: tangelo.accessor({value: 0}), 12 | linkTarget: tangelo.accessor({value: 0}), 13 | data: null 14 | }, 15 | 16 | _create: function () { 17 | var that = this, 18 | vegaspec = tangelo.plugin.mapping.geovis(that.options.worldGeometry); 19 | 20 | vg.parse.spec(vegaspec, function (chart) { 21 | that.vis = chart; 22 | 23 | that._update(); 24 | }); 25 | }, 26 | 27 | _update: function () { 28 | $.each(this.options.data.nodes, _.bind(function (i) { 29 | var d = this.options.data.nodes[i]; 30 | 31 | d.latitude = this.options.nodeLatitude(d); 32 | d.longitude = this.options.nodeLongitude(d); 33 | d.size = this.options.nodeSize(d); 34 | d.color = this.options.nodeColor(d); 35 | }, this)); 36 | 37 | $.each(this.options.data.links, _.bind(function (i) { 38 | var d = this.options.data.links[i]; 39 | 40 | d.color = this.options.linkColor(d); 41 | d.source = this.options.linkSource(d); 42 | d.target = this.options.linkTarget(d); 43 | }, this)); 44 | 45 | if (this.vis) { 46 | this.vis({ 47 | el: this.element.get(0), 48 | data: { 49 | table: this.options.data.nodes, 50 | links: this.options.data.links 51 | } 52 | }).update(); 53 | } 54 | } 55 | }); 56 | }(window.tangelo, window.jQuery, window.vg, window._)); 57 | -------------------------------------------------------------------------------- /tangelo/tangelo/pkgdata/plugin/mongo/requirements.txt: -------------------------------------------------------------------------------- 1 | pymongo==3.2 2 | -------------------------------------------------------------------------------- /tangelo/tangelo/pkgdata/plugin/mongo/web/mongo.py: -------------------------------------------------------------------------------- 1 | import bson.json_util 2 | import pymongo 3 | 4 | 5 | def decode(s, argname, resp): 6 | try: 7 | return bson.json_util.loads(s) 8 | except ValueError as e: 9 | resp['error'] = e.message + " (argument '%s' was '%s')" % (argname, s) 10 | raise 11 | 12 | 13 | def run(server, db, coll, method='find', query=None, limit=1000, 14 | skip=0, fields=None, sort=None, fill=None): 15 | # Create an empty response object. 16 | response = {} 17 | 18 | # Check the requested method. 19 | if method not in ['find', 'insert']: 20 | response['error'] = "Unsupported MongoDB operation '%s'" % (method) 21 | return bson.json_util.dumps(response) 22 | 23 | # Decode the query strings into Python objects. 24 | try: 25 | if query is not None: 26 | query = decode(query, 'query', response) 27 | if fields is not None: 28 | fields = decode(fields, 'fields', response) 29 | if sort is not None: 30 | sort = decode(sort, 'sort', response) 31 | if fill is not None: 32 | fill = decode(fill, 'fill', response) 33 | else: 34 | fill = True 35 | except ValueError: 36 | return bson.json_util.dumps(response) 37 | 38 | # Cast the limit value to an int. 39 | try: 40 | limit = int(limit) 41 | except ValueError: 42 | response['error'] = ("Argument 'limit' ('%s') could " + 43 | "not be converted to int.") % (limit) 44 | return bson.json_util.dumps(response) 45 | 46 | # Cast the skip value to an int. 47 | try: 48 | skip = int(skip) 49 | except ValueError: 50 | response['error'] = ("Argument 'skip' ('%s') could " + 51 | "not be converted to int.") % (skip) 52 | return bson.json_util.dumps(response) 53 | 54 | # Create database connection. 55 | try: 56 | c = pymongo.MongoClient(server)[db][coll] 57 | except (pymongo.errors.AutoReconnect, pymongo.errors.ConnectionFailure): 58 | response['error'] = ("Could not connect to " + 59 | "MongoDB server '%s'") % (server) 60 | return bson.json_util.dumps(response) 61 | 62 | # Perform the requested action. 63 | if method == 'find': 64 | # Do a find operation with the passed arguments. 65 | it = c.find(filter=query, projection=fields, skip=skip, 66 | limit=limit, sort=sort) 67 | 68 | # Create a list of the results. 69 | if fill: 70 | results = [x for x in it] 71 | else: 72 | results = [] 73 | 74 | # Create an object to structure the results. 75 | retobj = {} 76 | retobj['count'] = it.count() 77 | retobj['data'] = results 78 | 79 | # Pack the results into the response object, and return it. 80 | response['result'] = retobj 81 | else: 82 | raise RuntimeError("illegal method '%s' in module 'mongo'") 83 | 84 | # Return the response object. 85 | return bson.json_util.dumps(response) 86 | -------------------------------------------------------------------------------- /tangelo/tangelo/pkgdata/plugin/stream/web/examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Prime Stream 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 34 | 35 | 52 | 53 | 63 | 64 |
65 |
66 |
67 | 68 |
69 |
70 |
71 |
72 | -------------------------------------------------------------------------------- /tangelo/tangelo/pkgdata/plugin/stream/web/examples/index.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | "use strict"; 3 | 4 | var redprime, 5 | blueprime, 6 | running = true, 7 | delay = 10, 8 | tablerow, 9 | other = blueprime, 10 | count = 0, 11 | key; 12 | 13 | tablerow = d3.select("#content") 14 | .append("tr"); 15 | 16 | d3.select("#pause") 17 | .on("click", function () { 18 | var text; 19 | 20 | running = !running; 21 | text = running ? "Stop" : "Resume"; 22 | 23 | d3.select(this) 24 | .text(text); 25 | 26 | if (running) { 27 | tangelo.plugin.stream.run(key, other); 28 | } else { 29 | delay = 10; 30 | } 31 | }); 32 | 33 | redprime = function (value) { 34 | tablerow.append("td") 35 | .style("background-color", "#de5d5d") 36 | .text(value); 37 | 38 | if (count++ === 10) { 39 | tablerow = d3.select("#content") 40 | .append("tr"); 41 | count = 0; 42 | } 43 | 44 | if (delay < 500) { 45 | delay *= 1.1; 46 | } 47 | 48 | other = blueprime; 49 | 50 | return { 51 | callback: blueprime, 52 | delay: delay, 53 | continue: running 54 | }; 55 | }; 56 | 57 | blueprime = function (value) { 58 | tablerow.append("td") 59 | .style("background-color", "#90ade0") 60 | .text(value); 61 | 62 | if (count++ === 10) { 63 | tablerow = d3.select("#content") 64 | .append("tr"); 65 | count = 0; 66 | } 67 | 68 | if (delay < 1000) { 69 | delay *= 2; 70 | } 71 | 72 | other = redprime; 73 | 74 | return { 75 | callback: redprime, 76 | delay: delay, 77 | continue: running 78 | }; 79 | }; 80 | 81 | tangelo.plugin.stream.start("primes", function (stream_key) { 82 | key = stream_key; 83 | tangelo.plugin.stream.run(key, redprime); 84 | }); 85 | }); 86 | -------------------------------------------------------------------------------- /tangelo/tangelo/pkgdata/plugin/stream/web/examples/primes.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | import math 3 | 4 | import tangelo 5 | 6 | 7 | def is_prime(v): 8 | for i in range(2, int(math.ceil(math.sqrt(v)) + 1)): 9 | if v % i == 0: 10 | return False 11 | return True 12 | 13 | 14 | def stream(): 15 | yield 2 16 | yield 3 17 | 18 | for i in itertools.count(start=5, step=2): 19 | if is_prime(i): 20 | yield i 21 | 22 | 23 | @tangelo.types(n=int) 24 | def run(n): 25 | return list(itertools.islice(stream(), n, n + 1))[0] 26 | -------------------------------------------------------------------------------- /tangelo/tangelo/pkgdata/plugin/svg2pdf/web/svg2pdf.py: -------------------------------------------------------------------------------- 1 | from svglib.svglib import svg2rlg 2 | from reportlab.graphics import renderPDF 3 | import tangelo 4 | import tempfile 5 | import random 6 | 7 | converted = {} 8 | 9 | 10 | @tangelo.restful 11 | def post(): 12 | # drawing = svg2rlg(StringIO.StringIO(tangelo.request_body())) 13 | body = tangelo.request_body().read() 14 | f = tempfile.NamedTemporaryFile(delete=False) 15 | f.write(body) 16 | f.close() 17 | # return open(f.name).read() 18 | drawing = svg2rlg(f.name) 19 | # drawing.renderScale = 1 20 | id = '%030x' % random.randrange(16 ** 30) 21 | converted[id] = renderPDF.drawToString(drawing) 22 | return {"result": id, "error": None} 23 | 24 | 25 | @tangelo.restful 26 | def get(id=None): 27 | tangelo.content_type("application/pdf") 28 | tangelo.header("Content-disposition", "attachment; filename=figure.pdf") 29 | return converted[id] 30 | -------------------------------------------------------------------------------- /tangelo/tangelo/pkgdata/plugin/tangelo/web/version.py: -------------------------------------------------------------------------------- 1 | import tangelo 2 | 3 | 4 | def run(): 5 | tangelo.content_type("text/plain") 6 | version = "0.10.0-dev" 7 | return version 8 | -------------------------------------------------------------------------------- /tangelo/tangelo/pkgdata/plugin/ui/web/controlPanel.js: -------------------------------------------------------------------------------- 1 | (function (tangelo, $, d3, _) { 2 | "use strict"; 3 | 4 | function drawerToggle(divsel, buttonsel, height) { 5 | var div, 6 | button, 7 | state, 8 | iconheight; 9 | 10 | height = height || null; 11 | 12 | // Use the selectors to grab the DOM elements. 13 | div = d3.select(divsel); 14 | button = d3.select(buttonsel); 15 | 16 | // Initially, the panel is open. 17 | state = "uncollapsed"; 18 | 19 | // The glyphicon halfings are around 20 pixels tall. 20 | iconheight = "20px"; 21 | 22 | // Compute the "standing" height of the control panel. This is done by 23 | // clearing the current height style directive, then asking what its 24 | // height is, then replacing the height directive. This should not 25 | // result in any visible flicker. 26 | function getFullHeight() { 27 | var styleheight = div.style("height"), 28 | fullheight; 29 | 30 | div.style("height", null); 31 | fullheight = $(div.node()).height() + "px"; 32 | div.style("height", styleheight); 33 | 34 | return fullheight; 35 | } 36 | 37 | // This function, when called, will toggle the state of the panel. 38 | return function () { 39 | if (state === "uncollapsed") { 40 | div.transition() 41 | .duration(500) 42 | .style("height", iconheight); 43 | 44 | button.classed("glyphicon-chevron-down", false) 45 | .classed("glyphicon-chevron-up", true); 46 | 47 | state = "collapsed"; 48 | } else if (state === "collapsed") { 49 | // This transition computes the full height of the panel, grows 50 | // the panel to that height, then tosses out the height style 51 | // directive. This allows new, dynamic content to automatically 52 | // grow the element, while also allowing the smooth transition 53 | // effect here. 54 | div.transition() 55 | .duration(500) 56 | .style("height", height || getFullHeight()) 57 | .each("end", function () { 58 | if (!height) { 59 | div.style("height", null); 60 | } 61 | }); 62 | 63 | button.classed("glyphicon-chevron-down", true) 64 | .classed("glyphicon-chevron-up", false); 65 | 66 | state = "uncollapsed"; 67 | } else { 68 | throw new Error("illegal state: " + state); 69 | } 70 | }; 71 | } 72 | 73 | $.fn.controlPanel = function (cfg) { 74 | var toggle, 75 | s, 76 | id, 77 | tag; 78 | 79 | cfg = cfg || {}; 80 | 81 | // Make a d3 selection out of the target element. 82 | s = d3.select(this[0]); 83 | 84 | // Bail out silently if the selection is empty. 85 | if (s.empty()) { 86 | return; 87 | } 88 | 89 | // Create a unique identifier to use with the various control panel 90 | // components. 91 | tag = _.uniqueId(); 92 | id = s.attr("id"); 93 | if (!id) { 94 | id = "tangelo-control-panel-" + tag; 95 | s.attr("id", id); 96 | } 97 | 98 | // Style the control panel div appropriately, then add a div as the 99 | // first child to act as the drawer handle (and place an appropriate 100 | // icon in the mtagdle of it). 101 | s.style("position", "fixed") 102 | .style("bottom", "0px") 103 | .style("width", "100%") 104 | .style("height", function () { 105 | return cfg.height || "auto"; 106 | }) 107 | .insert("div", ":first-child") 108 | .attr("id", "tangelo-drawer-handle-" + tag) 109 | .style("text-align", "center") 110 | .style("cursor", "pointer") 111 | .on("mouseenter", function () { 112 | d3.select(this) 113 | .style("background", "gray"); 114 | }) 115 | .on("mouseleave", function () { 116 | d3.select(this) 117 | .style("background", null); 118 | }) 119 | .append("span") 120 | .attr("id", "tangelo-drawer-icon-" + tag) 121 | .classed("glyphicon", true) 122 | .classed("glyphicon-chevron-down", true); 123 | 124 | toggle = drawerToggle("#" + id, "#tangelo-drawer-icon-" + tag, cfg.height || null); 125 | d3.select("#tangelo-drawer-handle-" + tag) 126 | .on("click", toggle); 127 | }; 128 | }(window.tangelo, window.jQuery, window.d3, window._)); 129 | -------------------------------------------------------------------------------- /tangelo/tangelo/pkgdata/plugin/ui/web/svgColorLegend.js: -------------------------------------------------------------------------------- 1 | (function ($, d3) { 2 | "use strict"; 3 | 4 | $.fn.svgColorLegend = function (cfg) { 5 | var bbox, 6 | bg, 7 | bottom, 8 | height, 9 | heightfunc, 10 | left, 11 | maxheight, 12 | maxwidth, 13 | right, 14 | text, 15 | top, 16 | totalheight, 17 | totalwidth, 18 | width, 19 | legend, 20 | cmapFunc, 21 | xoffset, 22 | yoffset, 23 | categories, 24 | heightPadding, 25 | widthPadding, 26 | textSpacing, 27 | legendMargins, 28 | clear; 29 | 30 | // Extract arguments from the config argument. 31 | cmapFunc = cfg.cmapFunc; 32 | xoffset = cfg.xoffset; 33 | yoffset = cfg.yoffset; 34 | categories = cfg.categories; 35 | heightPadding = cfg.heightPadding; 36 | widthPadding = cfg.widthPadding; 37 | textSpacing = cfg.textSpacing; 38 | legendMargins = cfg.legendMargins; 39 | clear = cfg.clear; 40 | 41 | // Create a d3 selection from the selection. 42 | legend = d3.select(this[0]); 43 | 44 | // Clear the svg element, if requested. 45 | clear = clear || false; 46 | if (clear) { 47 | legend.selectAll("*").remove(); 48 | } 49 | 50 | maxwidth = 0; 51 | maxheight = 0; 52 | 53 | // Place a rect that will serve as a container/background for the legend 54 | // list items. Leave its dimensions undefined for now (they will be 55 | // computed from the size of all the elements later). 56 | bg = legend.append("rect") 57 | .style("fill", "white") 58 | .style("opacity", 0.7); 59 | 60 | /*jslint unparam: true */ 61 | $.each(categories, function (i, d) { 62 | legend.append("rect") 63 | .classed("colorbox", true) 64 | .attr("x", xoffset) 65 | // "y", "width", and "height" intentionally left unset 66 | .style("fill", cmapFunc(d)); 67 | 68 | text = legend.append("text") 69 | .classed("legendtext", true) 70 | // "x" and "y" intentionally left unset 71 | .text(d); 72 | 73 | // Compute the max height and width out of all the text bgs. 74 | bbox = text[0][0].getBBox(); 75 | 76 | if (bbox.width > maxwidth) { 77 | maxwidth = bbox.width; 78 | } 79 | 80 | if (bbox.height > maxheight) { 81 | maxheight = bbox.height; 82 | } 83 | }); 84 | /*jslint unparam: false */ 85 | 86 | // Compute the height and width of each color swatch. 87 | height = maxheight + heightPadding; 88 | width = height; 89 | 90 | // Compute the total height and width of all the legend items together. 91 | totalheight = height * categories.length; 92 | totalwidth = width + widthPadding + maxwidth; 93 | 94 | // Get the user-supplied margin values. 95 | left = legendMargins.left || 0; 96 | top = legendMargins.top || 0; 97 | right = legendMargins.right || 0; 98 | bottom = legendMargins.bottom || 0; 99 | 100 | // Set the dimensions of the container rect, based on the height/width of 101 | // all the items, plus the user supplied margins. 102 | bg.attr("x", xoffset - left || 0) 103 | .attr("y", yoffset - top || 0) 104 | .attr("width", left + totalwidth + right) 105 | .attr("height", top + totalheight + bottom); 106 | 107 | /*jslint unparam: true */ 108 | heightfunc = function (d, i) { 109 | return yoffset + i * height; 110 | }; 111 | /*jslint unparam: false */ 112 | 113 | legend.selectAll(".colorbox") 114 | .attr("width", height) 115 | .attr("height", height) 116 | .attr("y", heightfunc); 117 | 118 | legend.selectAll(".legendtext") 119 | .attr("x", xoffset + width + widthPadding) 120 | .attr("y", function (d, i) { 121 | return textSpacing + heightfunc(d, i); 122 | }); 123 | }; 124 | }(window.jQuery, window.d3)); 125 | -------------------------------------------------------------------------------- /tangelo/tangelo/pkgdata/plugin/vis/web/examples/barchart/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Bar Chart 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 27 | 34 | 51 | 52 | 62 | 63 |
64 |
65 |

Bar Chart Options:

66 |
67 |
68 |
69 | 70 | 71 | 72 |
73 |
74 |
75 |
76 | 77 | 78 | 79 | 80 |
81 | Width: 82 | Height: 83 |
84 |
85 |
86 |
87 | 88 |
89 |
90 | -------------------------------------------------------------------------------- /tangelo/tangelo/pkgdata/plugin/vis/web/examples/correlation/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | correlation 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 42 | 43 | 62 | 63 | 73 | 74 |
75 |
76 |
77 |
78 | -------------------------------------------------------------------------------- /tangelo/tangelo/pkgdata/plugin/vis/web/examples/correlation/index.js: -------------------------------------------------------------------------------- 1 | /*globals d3, $, tangelo */ 2 | 3 | window.onload = function () { 4 | "use strict"; 5 | 6 | var nData = 50, 7 | data = [], 8 | i, 9 | iStyle = 0, 10 | color1 = d3.scale.linear() 11 | .domain([0,0.5,1]) 12 | .range(["red", "white", "blue"]), 13 | color2 = d3.scale.linear() 14 | .domain([0,1]) 15 | .range(["steelblue", "white"]); 16 | 17 | function generate() { 18 | for (i = 0; i < nData; i++) { 19 | data[i] = { 20 | r1: Math.random(), 21 | r2: Math.random(), 22 | r3: Math.random() 23 | }; 24 | } 25 | } 26 | generate(); 27 | 28 | function var1(d) { 29 | return d.r1; 30 | } 31 | var1.label = "Variable 1"; 32 | 33 | function var2(d) { 34 | return 0.7*d.r1 + 0.3*d.r2; 35 | } 36 | var2.label = "Variable 2"; 37 | 38 | function var3(d) { 39 | return 1 - d.r2; 40 | } 41 | var3.label = "Variable 3"; 42 | 43 | function var4(d) { 44 | return 0.4*d.r1 + 0.4*d.r2 + 0.2*d.r3; 45 | } 46 | var4.label = "Variable 4"; 47 | 48 | function var5(d) { 49 | return 1 - (0.9*d.r1 + 0.05*d.r2 + 0.05*d.r3); 50 | } 51 | var5.label = "Variable 5"; 52 | 53 | function c(d) { 54 | var v = (d.r1 + d.r2 + d.r3)/3.0; 55 | return iStyle % 2 ? color1(v) : color2((v <= 0.25) ? 0 : 1); 56 | } 57 | 58 | $("#content1").correlationPlot({ 59 | variables: [ 60 | var1, 61 | var2, 62 | var3, 63 | var4, 64 | var5 65 | ], 66 | data: data, 67 | color: tangelo.accessor(c), 68 | full: true 69 | }).trigger("draw"); 70 | $("#content2").correlationPlot({ 71 | variables: [ 72 | var1, 73 | var2, 74 | var3, 75 | var4, 76 | var5 77 | ], 78 | data: data, 79 | color: tangelo.accessor(c), 80 | full: false 81 | }).trigger("draw"); 82 | 83 | $(window).resize(function () { 84 | $("#content1").trigger("draw"); 85 | $("#content2").trigger("draw"); 86 | }); 87 | 88 | var styles = [ 89 | { 90 | variables: [ 91 | var1, 92 | var2, 93 | var3, 94 | var4, 95 | var5 96 | ], 97 | data: data 98 | }, 99 | { 100 | variables: [ 101 | var2, 102 | var3, 103 | var4, 104 | var5 105 | ], 106 | data: data 107 | }, 108 | { 109 | variables: [ 110 | var1, 111 | var2, 112 | var3 113 | ], 114 | data: data 115 | } 116 | ]; 117 | $(".content").click(function () { 118 | iStyle = (iStyle + 1) % styles.length; 119 | if (!iStyle) { 120 | generate(); 121 | } 122 | $("#content1").correlationPlot(styles[iStyle]).trigger("draw"); 123 | $("#content2").correlationPlot(styles[iStyle]).trigger("draw"); 124 | }); 125 | }; 126 | -------------------------------------------------------------------------------- /tangelo/tangelo/pkgdata/plugin/vis/web/examples/dendrogram/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 45 | 46 | 56 | 57 |
58 | -------------------------------------------------------------------------------- /tangelo/tangelo/pkgdata/plugin/vis/web/examples/dendrogram/index.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | "use strict"; 3 | 4 | var data = { 5 | name: "Picard", 6 | children: [{ 7 | name: "Riker", 8 | children: [ 9 | { 10 | name: "LaForge", 11 | children: [ 12 | { name: "Crusher, W.", children: [] }, 13 | { name: "Gomez", children: [] }, 14 | { name: "Barclay", children: [] } 15 | ] 16 | }, 17 | 18 | { 19 | name: "Worf", 20 | children: [ 21 | { name: "Sito", children: [] }, 22 | { name: "Rhodes", children: [] }, 23 | { name: "D'Sora", children: [] } 24 | ] 25 | }, 26 | 27 | { 28 | name: "Crusher, B.", 29 | children: [ 30 | { name: "Ogawa", children: [] }, 31 | { name: "Selar", children: [] } 32 | ] 33 | } 34 | ] 35 | }] 36 | }; 37 | 38 | $("#content").dendrogram({ 39 | data: data, 40 | id: tangelo.accessor({field: "name"}), 41 | label: tangelo.accessor({field: "name"}) 42 | }); 43 | $(window).resize(function () { 44 | $("#content").dendrogram("resize"); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /tangelo/tangelo/pkgdata/plugin/vis/web/examples/donutchart/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Donut Chart 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 38 | 39 | 56 | 57 | 67 | 68 |
69 |
70 |

Donut Chart Options:

71 |
72 |
73 | 74 | 75 | 76 | 77 |
78 | Width: 79 | Height: 80 | Inner Radius: % 81 |
82 |
83 |
84 | 85 |
86 |
87 | -------------------------------------------------------------------------------- /tangelo/tangelo/pkgdata/plugin/vis/web/examples/nodelink/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | nodelink 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 22 | 23 | 41 | 42 | 52 | 53 |
54 | -------------------------------------------------------------------------------- /tangelo/tangelo/pkgdata/plugin/vis/web/examples/nodelink/index.js: -------------------------------------------------------------------------------- 1 | window.onload = function () { 2 | var data = { 3 | nodes: [ 4 | {value: 10, group: 'a', name: 'node 1'}, 5 | {value: 20, group: 'a', name: 'node 2'}, 6 | {value: 30, group: 'b', name: 'node 3'}, 7 | {value: 40, group: 'b', name: 'node 4'}, 8 | {value: 50, group: 'c', name: 'node 5'}, 9 | {value: 60, group: 'c', name: 'node 6'}, 10 | {value: 70, group: 'd', name: 'node 7'} 11 | ], 12 | links: [ 13 | {s: 0, t: 1}, 14 | {s: 1, t: 2}, 15 | {s: 2, t: 3}, 16 | {s: 3, t: 4}, 17 | {s: 4, t: 5}, 18 | {s: 5, t: 6}, 19 | {s: 6, t: 0} 20 | ] 21 | }; 22 | 23 | $("#content").nodelink({ 24 | data: data, 25 | nodeCharge: tangelo.accessor({value: -400}), 26 | linkSource: tangelo.accessor({field: "s"}), 27 | linkTarget: tangelo.accessor({field: "t"}), 28 | nodeSize: tangelo.accessor({field: "value"}), 29 | nodeColor: tangelo.accessor({field: "group"}), 30 | nodeLabel: tangelo.accessor({field: "name"}) 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /tangelo/tangelo/pkgdata/plugin/vis/web/examples/parallelcoords/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Parallel Coords 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 34 | 35 | 52 | 53 | 63 | 64 |
65 |
66 |

Parallel Coordinates Options:

67 |
68 |
69 |
70 | 71 | 72 | 73 | 74 |
75 | Width: 76 | Height: 77 |
78 |
79 |
80 |
81 | 82 |
83 |
84 | 85 | -------------------------------------------------------------------------------- /tangelo/tangelo/pkgdata/plugin/vis/web/examples/parallelcoords/index.js: -------------------------------------------------------------------------------- 1 | /*globals $, d3 */ 2 | 3 | $(function () { 4 | "use strict"; 5 | 6 | var parallelCoords = { 7 | width: 0, 8 | height: 0, 9 | dataUrl: "cars.json", 10 | fields: ["cyl", "dsp", "lbs", "hp", "acc", "mpg", "year"] 11 | }, 12 | initSize = { 13 | width: 350, 14 | height: 200 15 | }, 16 | margin = { 17 | resizable: { 18 | width: 70, 19 | height: 50 20 | }, 21 | windows: { 22 | width: 100, 23 | height: 320 24 | } 25 | }; 26 | 27 | function updateSizeDisplay() { 28 | $("#width").val(Math.round(parallelCoords.width)); 29 | $("#height").val(Math.round(parallelCoords.height)); 30 | } 31 | 32 | function resizeChart(width, height, option) { 33 | parallelCoords.width = width; 34 | parallelCoords.height = height; 35 | $("#content").parallelCoords("resize", parallelCoords.width, parallelCoords.height); 36 | if (!option) { 37 | updateSizeDisplay(); 38 | } 39 | } 40 | 41 | function windowResizer() { 42 | var win = $(window); 43 | resizeChart(win.width() - margin.windows.width, win.height() - margin.windows.height); 44 | } 45 | 46 | function disableInput() { 47 | $("#width").prop("readonly", "readonly"); 48 | $("#height").prop("readonly", "readonly"); 49 | } 50 | 51 | function enableInput() { 52 | $("#width").prop("readonly", ""); 53 | $("#height").prop("readonly", ""); 54 | } 55 | 56 | function createResizableContainer() { 57 | $("#bar-chart-panel").remove(); 58 | $("#content").remove(); 59 | $("#content-wrapper").append("
"); 60 | $("#bar-chart-panel").resizable({ 61 | resize: function (eve, ui) { 62 | if (!eve) { 63 | return; 64 | } 65 | resizeChart(ui.size.width - margin.resizable.width, ui.size.height - margin.resizable.height); 66 | }, 67 | stop: function (eve, ui) { 68 | if (!eve) { 69 | return; 70 | } 71 | resizeChart(ui.size.width - margin.resizable.width, ui.size.height - margin.resizable.height); 72 | } 73 | }); 74 | } 75 | 76 | d3.json(parallelCoords.dataUrl, function (error, json) { 77 | if (error) { 78 | console.log(error); 79 | return; 80 | } 81 | parallelCoords.data = json; 82 | createResizableContainer(); 83 | $("#content").parallelCoords(parallelCoords); 84 | updateSizeDisplay(); 85 | }); 86 | 87 | function updateResizingOption(option) { 88 | if (option === "0") { 89 | disableInput(); 90 | $(window).off("resize", windowResizer); 91 | createResizableContainer(); 92 | parallelCoords.width = initSize.width; 93 | parallelCoords.height = initSize.height; 94 | $("#content").parallelCoords(parallelCoords); 95 | updateSizeDisplay(); 96 | } else if (option === "1") { 97 | disableInput(); 98 | $("#content").remove(); 99 | $("#bar-chart-panel").remove(); 100 | $("#content-wrapper").append("
"); 101 | parallelCoords.width = $(window).width() - margin.windows.width; 102 | parallelCoords.height = $(window).height() - margin.windows.height; 103 | $("#content").parallelCoords(parallelCoords); 104 | $(window).resize(windowResizer); 105 | updateSizeDisplay(); 106 | } else { 107 | $(window).off("resize", windowResizer); 108 | $("#content").remove(); 109 | $("#bar-chart-panel").remove(); 110 | $("#content-wrapper").append("
"); 111 | enableInput(); 112 | $("#content").parallelCoords(parallelCoords); 113 | } 114 | } 115 | 116 | $("input:radio[name='size-option']").change(function () { 117 | var value = $(this).val(); 118 | updateResizingOption(value); 119 | }); 120 | 121 | $("input:text[id='width']").change(function () { 122 | var value = $(this).val(); 123 | resizeChart(Math.round(value), parallelCoords.height, true); 124 | }); 125 | 126 | $("input:text[id='height']").change(function () { 127 | var value = $(this).val(); 128 | resizeChart(parallelCoords.width, Math.round(value), true); 129 | }); 130 | }); 131 | -------------------------------------------------------------------------------- /tangelo/tangelo/pkgdata/plugin/vis/web/examples/timeline/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | timeline 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 28 | 29 | 51 | 52 | 62 | 63 |
64 |
65 | 66 |
67 |
68 | 69 |
70 |
Box kernel
71 |
72 |
Gaussian kernel
73 |
74 | -------------------------------------------------------------------------------- /tangelo/tangelo/pkgdata/plugin/vis/web/examples/timeline/index.js: -------------------------------------------------------------------------------- 1 | /*globals $, tangelo */ 2 | 3 | window.onload = function () { 4 | "use strict"; 5 | 6 | var n = 1000, data = [], i, 7 | start = new Date(2010, 0, 1), 8 | end = new Date(2010, 11, 31), 9 | deltaTime = (end - start)/(n - 1), 10 | spec = { 11 | data: data, 12 | sorted: true, 13 | x: {field: "time"}, 14 | y: {field: "origValue"} 15 | }, 16 | iRadius = 0, 17 | radii = [ 0.01, 0.05, 0.10, 0.15, 0.20, 0.25 ]; 18 | 19 | function generate() { 20 | for (i = 0; i < n; i += 1) { 21 | data[i] = { 22 | time: i * deltaTime + start.valueOf(), 23 | origValue: Math.random() 24 | }; 25 | } 26 | } 27 | 28 | function smooth(radius) { 29 | $("#textBox").text("Smoothing radius: " + (radius * 100).toFixed() + "%"); 30 | tangelo.plugin.data.smooth( 31 | $.extend({ 32 | radius: radius, 33 | kernel: "box", 34 | set: function (v, d) { 35 | d.value1 = v; 36 | } 37 | }, spec) 38 | ); 39 | tangelo.plugin.data.smooth( 40 | $.extend({ 41 | radius: radius, 42 | kernel: "gaussian", 43 | set: function (v, d) { 44 | d.value2 = v; 45 | } 46 | }, spec) 47 | ); 48 | } 49 | 50 | function draw(transition) { 51 | $("#content1").timeline({ 52 | data: data, 53 | y: {field: "value1"}, 54 | transition: transition 55 | }); 56 | $("#content2").timeline({ 57 | data: data, 58 | y: {field: "value2"}, 59 | transition: transition 60 | }); 61 | } 62 | 63 | generate(); 64 | smooth(radii[iRadius]); 65 | draw(); 66 | $(window).resize(draw); 67 | $("#next").click(function () { 68 | var t = 500; 69 | iRadius = (iRadius + 1) % radii.length; 70 | if (iRadius === 0) { 71 | generate(); 72 | t = 0; 73 | } 74 | smooth(radii[iRadius]); 75 | draw(t); 76 | }); 77 | }; 78 | -------------------------------------------------------------------------------- /tangelo/tangelo/pkgdata/plugin/vtkweb/config.yaml: -------------------------------------------------------------------------------- 1 | vtkpython: null 2 | -------------------------------------------------------------------------------- /tangelo/tangelo/pkgdata/plugin/vtkweb/control.py: -------------------------------------------------------------------------------- 1 | import tangelo 2 | from twisted.internet import reactor, threads 3 | 4 | 5 | def teardown(config, store): 6 | if reactor.running: 7 | tangelo.log_info("VTKWEB", "Shutting down Twisted reactor") 8 | threads.blockingCallFromThread(reactor, reactor.stop) 9 | 10 | if "processes" in store: 11 | tangelo.log_info("VTKWEB", "Terminating VTKWeb processes") 12 | for p in store["processes"].values(): 13 | p["process"].terminate() 14 | p["process"].wait() 15 | -------------------------------------------------------------------------------- /tangelo/tangelo/pkgdata/plugin/vtkweb/include/vtkweb-launcher.py: -------------------------------------------------------------------------------- 1 | #!python 2 | 3 | r""" 4 | This module is a VTK Web server application. 5 | The following command line illustrate how to use it:: 6 | 7 | $ vtkpython .../vtk_web_tree.py 8 | 9 | Any VTK Web executable script come with a set of standard arguments that 10 | can be overriden if need be:: 11 | 12 | --port 8080 13 | Port number on which the HTTP server will listen to. 14 | 15 | --content /path-to-web-content/ 16 | Directory that you want to server as static web content. By 17 | default, this variable is empty which mean that we rely on another 18 | server to deliver the static content and the current process only 19 | focus on the WebSocket connectivity of clients. 20 | 21 | --authKey vtk-secret 22 | Secret key that should be provided by the client to allow it to 23 | make any WebSocket communication. The client will assume if none 24 | is given that the server expect "vtk-secret" as secret key. 25 | """ 26 | 27 | import imp 28 | 29 | # import to process args 30 | import sys 31 | 32 | # import vtk modules. 33 | from vtk.web import server, wamp, protocols 34 | 35 | try: 36 | import argparse 37 | except ImportError: 38 | # since Python 2.6 and earlier don't have argparse, we simply provide 39 | # the source for the same as _argparse and we use it instead. 40 | import _argparse as argparse 41 | 42 | # ============================================================================= 43 | # Create custom File Opener class to handle clients requests 44 | # ============================================================================= 45 | 46 | 47 | def VTKWebAppProtocol(args, usermod): 48 | class VTKWebApp(wamp.ServerProtocol): 49 | # Application configuration 50 | view = None 51 | authKey = "vtkweb-secret" 52 | 53 | def __init__(self): 54 | # Because the ServerProtocol constructor directly calls 55 | # initialize(), be sure to set these instance properties FIRST. 56 | self.args = args 57 | self.app = usermod 58 | 59 | wamp.ServerProtocol.__init__(self) 60 | 61 | def initialize(self): 62 | # Bring used components 63 | self.registerVtkWebProtocol(protocols.vtkWebMouseHandler()) 64 | self.registerVtkWebProtocol(protocols.vtkWebViewPort()) 65 | self.registerVtkWebProtocol( 66 | protocols.vtkWebViewPortImageDelivery()) 67 | self.registerVtkWebProtocol( 68 | protocols.vtkWebViewPortGeometryDelivery()) 69 | 70 | # Update authentication key to use 71 | self.updateSecret(VTKWebApp.authKey) 72 | 73 | if "initialize" in self.app.__dict__: 74 | self.app.initialize(self, VTKWebApp, self.args) 75 | 76 | return VTKWebApp 77 | 78 | # ============================================================================= 79 | # Main: Parse args and start server 80 | # ============================================================================= 81 | 82 | if __name__ == "__main__": 83 | # Get the full path to the user's application file. 84 | if len(sys.argv) < 2: 85 | print >>sys.stderr, ("usage: vtkweb-launcher.py " + 86 | "[tangelo-vtkweb-app] [arg1, arg2, arg3, ...]") 87 | sys.exit(1) 88 | userfile = sys.argv[1] 89 | 90 | # Import the user file as a module. 91 | try: 92 | usermod = imp.load_source("usermod", userfile) 93 | except IOError: 94 | print >>sys.stderr, "error: could not open file '%s'" % (userfile) 95 | sys.exit(2) 96 | except ImportError as e: 97 | print >>sys.stderr, "error: could not import module '%s'" % (userfile) 98 | print >>sys.stderr, "({})".format(e) 99 | sys.exit(2) 100 | 101 | # Create argument parser 102 | parser = argparse.ArgumentParser(description="Tangelo/VTKWeb application") 103 | 104 | # Add default arguments 105 | server.add_arguments(parser) 106 | 107 | # Add local arguments, if any are specified in the user module. 108 | if "add_arguments" in usermod.__dict__: 109 | usermod.add_arguments(parser) 110 | 111 | # Extract arguments (dropping the "usermodule" argument first). 112 | del sys.argv[1] 113 | args = parser.parse_args() 114 | 115 | # Configure our current application 116 | VTKWebApp = VTKWebAppProtocol(args, usermod) 117 | VTKWebApp.authKey = args.authKey 118 | 119 | # Start server 120 | server.start_webserver(options=args, protocol=VTKWebApp) 121 | -------------------------------------------------------------------------------- /tangelo/tangelo/pkgdata/plugin/vtkweb/requirements.txt: -------------------------------------------------------------------------------- 1 | autobahn==0.6.5 2 | Twisted==14.0.2 3 | -------------------------------------------------------------------------------- /tangelo/tangelo/pkgdata/plugin/vtkweb/web/examples/cone/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 28 | 29 | 48 | 49 | 59 | 60 |
61 |
62 |
63 |
64 | 65 |
66 | Launch 67 | Terminate 68 |
69 |
70 | -------------------------------------------------------------------------------- /tangelo/tangelo/pkgdata/plugin/vtkweb/web/examples/cone/index.js: -------------------------------------------------------------------------------- 1 | var app = {}; 2 | app.key = null; 3 | 4 | // This will end whatever process is currently running, if any, and clear the UI 5 | // elements. 6 | function terminate() { 7 | "use strict"; 8 | 9 | if (app.key) { 10 | tangelo.plugin.vtkweb.terminate(app.key); 11 | app.key = null; 12 | } 13 | 14 | d3.select("#launch") 15 | .classed("disabled", false); 16 | 17 | d3.select("#terminate") 18 | .classed("disabled", true); 19 | } 20 | 21 | function launch() { 22 | "use strict"; 23 | 24 | // Clear out any existing process. 25 | terminate(); 26 | 27 | tangelo.plugin.vtkweb.launch({ 28 | url: "vtkweb_cone.py", 29 | viewport: "#viewport", 30 | callback: function (key, error) { 31 | if (error) { 32 | console.warn("error!"); 33 | console.warn(error); 34 | return; 35 | } 36 | 37 | app.key = key; 38 | 39 | d3.select("#launch") 40 | .classed("disabled", true); 41 | 42 | d3.select("#terminate") 43 | .classed("disabled", false); 44 | } 45 | }); 46 | } 47 | 48 | $(function () { 49 | "use strict"; 50 | 51 | // When the page is closed, make sure to close any processes that were running. 52 | window.onbeforeunload = window.onunload = terminate; 53 | 54 | // Install actions on the buttons. 55 | d3.select("#launch") 56 | .on("click", launch); 57 | 58 | // If a vtk web process has been launched, then shut it down. 59 | d3.select("#terminate") 60 | .classed("disabled", true) 61 | .on("click", terminate); 62 | }); 63 | -------------------------------------------------------------------------------- /tangelo/tangelo/pkgdata/plugin/vtkweb/web/examples/cone/vtkweb_cone.py: -------------------------------------------------------------------------------- 1 | import vtk 2 | 3 | 4 | def initialize(self, VTKWebApp, args): 5 | if not VTKWebApp.view: 6 | # VTK specific code 7 | renderer = vtk.vtkRenderer() 8 | renderWindow = vtk.vtkRenderWindow() 9 | renderWindow.AddRenderer(renderer) 10 | 11 | renderWindowInteractor = vtk.vtkRenderWindowInteractor() 12 | renderWindowInteractor.SetRenderWindow(renderWindow) 13 | renderWindowInteractor.GetInteractorStyle().SetCurrentStyleToTrackballCamera() 14 | 15 | cone = vtk.vtkConeSource() 16 | mapper = vtk.vtkPolyDataMapper() 17 | actor = vtk.vtkActor() 18 | 19 | mapper.SetInputConnection(cone.GetOutputPort()) 20 | actor.SetMapper(mapper) 21 | 22 | renderer.AddActor(actor) 23 | renderer.ResetCamera() 24 | renderWindow.Render() 25 | 26 | # VTK Web application specific 27 | VTKWebApp.view = renderWindow 28 | self.Application.GetObjectIdMap().SetActiveObject("VIEW", renderWindow) 29 | -------------------------------------------------------------------------------- /tangelo/tangelo/pkgdata/tangelo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kitware/tangelo/470034ee9b3d7a01becc1ce5fddc7adc1d5263ef/tangelo/tangelo/pkgdata/tangelo.ico -------------------------------------------------------------------------------- /tangelo/tangelo/pkgdata/web/img/Tangelo_Mark_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kitware/tangelo/470034ee9b3d7a01becc1ce5fddc7adc1d5263ef/tangelo/tangelo/pkgdata/web/img/Tangelo_Mark_256.png -------------------------------------------------------------------------------- /tangelo/tangelo/pkgdata/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | Tangelo 3 | 4 | 5 | 6 | 29 | 30 |
31 | 32 | 33 | 34 | 35 | 36 | Tangelo 37 | 38 |
39 | 40 |
41 |
42 | 43 |

Welcome to 44 | Tangelo, 45 | your gateway to easy, rich web applications! Check out the examples 46 | below to get started, or read the 47 | documentation 48 | to learn how to harness the power of the Tangelo! 49 |

50 | 51 |
52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 69 | 80 | 85 | 86 |
MapsVisualizationStreaming
61 | 68 | 70 | 79 | 81 | 84 |
87 |
88 | 89 | 124 | -------------------------------------------------------------------------------- /tangelo/tangelo/websocket.py: -------------------------------------------------------------------------------- 1 | import cherrypy 2 | from ws4py.server.cherrypyserver import WebSocketPlugin 3 | 4 | 5 | class WebSocketLowPriorityPlugin(WebSocketPlugin): 6 | def __init__(self, *pargs, **kwargs): 7 | WebSocketPlugin.__init__(self, *pargs, **kwargs) 8 | 9 | # This version of start() differs only in that it has an assigned priority. 10 | # The default priority is 50, which is what the actual WebSocketPlugin's 11 | # start method gets, which means it runs before the privilege drop gets a 12 | # chance to (priority 77, slightly lower than the 75 of the engine start 13 | # itself). For some reason if this runs before the priv drop, things get 14 | # screwed up. 15 | def start(self): 16 | WebSocketPlugin.start(self) 17 | start.priority = 80 18 | 19 | 20 | def mount(name, handler_cls, protocols=None): 21 | class WebSocketHandler(object): 22 | @cherrypy.expose 23 | def index(self): 24 | pass 25 | 26 | @cherrypy.expose 27 | def ws(self): 28 | pass 29 | 30 | if protocols is None: 31 | protocols = [] 32 | elif not isinstance(protocols, list): 33 | protocols = [protocols] 34 | 35 | cherrypy.tree.mount(WebSocketHandler(), 36 | "/ws/%s" % (name), 37 | config={"/ws": {"tools.websocket.on": True, 38 | "tools.websocket.handler_cls": handler_cls, 39 | "tools.websocket.protocols": protocols}}) 40 | 41 | 42 | def unmount(name): 43 | try: 44 | del cherrypy.tree.apps["/ws/%s" % (name)] 45 | except KeyError: 46 | raise KeyError("no such websocket path: %s" % (name)) 47 | -------------------------------------------------------------------------------- /tests/200-ok.py: -------------------------------------------------------------------------------- 1 | import nose 2 | import requests 3 | 4 | import fixture 5 | 6 | 7 | @nose.with_setup(fixture.start_tangelo, fixture.stop_tangelo) 8 | def test_200_ok(): 9 | response = requests.get(fixture.url("/")) 10 | assert response.status_code == 200 11 | -------------------------------------------------------------------------------- /tests/404-not-found.py: -------------------------------------------------------------------------------- 1 | import nose 2 | import requests 3 | 4 | import fixture 5 | 6 | 7 | @nose.with_setup(fixture.start_tangelo, fixture.stop_tangelo) 8 | def test_404_not_found(): 9 | response = requests.get(fixture.url("/does-not-exist.html")) 10 | assert response.status_code == 404 11 | -------------------------------------------------------------------------------- /tests/analyze-url.py: -------------------------------------------------------------------------------- 1 | import os 2 | import nose 3 | import requests 4 | 5 | import fixture 6 | from tangelo.server import Content 7 | from tangelo.server import Directive 8 | 9 | cwd = os.getcwd() 10 | 11 | 12 | @nose.with_setup(fixture.start_tangelo, fixture.stop_tangelo) 13 | def test_closed_source(): 14 | analysis = requests.get(fixture.url("analyze-url/analyze-url", test=1)).json() 15 | 16 | print analysis 17 | 18 | assert analysis["directive"] is None 19 | assert analysis["content"] is not None 20 | assert analysis["content"]["type"] == Content.File 21 | assert analysis["content"]["path"] is None 22 | assert analysis["content"]["pargs"] is None 23 | 24 | 25 | @nose.with_setup(fixture.start_tangelo, fixture.stop_tangelo) 26 | def test_open_source(): 27 | analysis = requests.get(fixture.url("analyze-url/analyze-url", test=2)).json() 28 | 29 | assert analysis["directive"] is None 30 | assert analysis["content"] is not None 31 | assert analysis["content"]["type"] == Content.File 32 | assert analysis["content"]["path"] == fixture.relative_path("tests/web/analyze-url/open.py") 33 | assert analysis["content"]["pargs"] is None 34 | 35 | 36 | @nose.with_setup(fixture.start_tangelo, fixture.stop_tangelo) 37 | def test_closed_config(): 38 | analysis = requests.get(fixture.url("analyze-url/analyze-url", test=3)).json() 39 | 40 | assert analysis["directive"] is None 41 | assert analysis["content"] is not None 42 | assert analysis["content"]["type"] == Content.File 43 | assert analysis["content"]["path"] is None 44 | assert analysis["content"]["pargs"] is None 45 | 46 | 47 | @nose.with_setup(fixture.start_tangelo, fixture.stop_tangelo) 48 | def test_open_nonconfig(): 49 | analysis = requests.get(fixture.url("analyze-url/analyze-url", test=4)).json() 50 | 51 | assert analysis["directive"] is None 52 | assert analysis["content"] is not None 53 | assert analysis["content"]["type"] == Content.File 54 | assert analysis["content"]["path"] == fixture.relative_path("tests/web/analyze-url/standalone.yaml") 55 | assert analysis["content"]["pargs"] is None 56 | 57 | 58 | @nose.with_setup(fixture.start_tangelo, fixture.stop_tangelo) 59 | def test_directory(): 60 | analysis = requests.get(fixture.url("analyze-url/analyze-url", test=6)).json() 61 | 62 | assert analysis["directive"] is None 63 | assert analysis["content"] is not None 64 | assert analysis["content"]["type"] == Content.Directory 65 | assert analysis["content"]["path"] == fixture.relative_path("tests/web/analyze-url/") 66 | assert analysis["content"]["pargs"] is None 67 | 68 | 69 | @nose.with_setup(fixture.start_tangelo, fixture.stop_tangelo) 70 | def test_redirect_directory(): 71 | analysis = requests.get(fixture.url("analyze-url/analyze-url", test=7)).json() 72 | 73 | assert analysis["directive"] is not None 74 | assert analysis["directive"]["type"] == Directive.HTTPRedirect 75 | assert analysis["directive"]["argument"] == "/analyze-url/" 76 | assert analysis["content"] is None 77 | 78 | 79 | @nose.with_setup(fixture.start_tangelo, fixture.stop_tangelo) 80 | def test_redirect_index(): 81 | analysis = requests.get(fixture.url("analyze-url/analyze-url", test=8)).json() 82 | 83 | assert analysis["directive"] is not None 84 | assert analysis["directive"]["type"] == Directive.InternalRedirect 85 | assert analysis["directive"]["argument"] == "/analyze-url/has-index/index.html" 86 | assert analysis["content"] is None 87 | 88 | 89 | @nose.with_setup(fixture.start_tangelo, fixture.stop_tangelo) 90 | def test_list_plugins(): 91 | analysis = requests.get(fixture.url("analyze-url/analyze-url", test=9)).json() 92 | 93 | assert analysis["directive"] is not None 94 | assert analysis["directive"]["type"] == Directive.ListPlugins 95 | assert analysis["directive"]["argument"] is None 96 | assert analysis["content"] is None 97 | 98 | 99 | @nose.with_setup(fixture.start_tangelo, fixture.stop_tangelo) 100 | def test_service(): 101 | analysis = requests.get(fixture.url("analyze-url/analyze-url", test=10)).json() 102 | 103 | assert analysis["directive"] is None 104 | assert analysis["content"] is not None 105 | assert analysis["content"]["type"] == Content.Service 106 | assert analysis["content"]["path"] == fixture.relative_path("tests/web/analyze-url/analyze-url.py") 107 | assert analysis["content"]["pargs"] == ["1", "2", "3"] 108 | 109 | 110 | @nose.with_setup(fixture.start_tangelo, fixture.stop_tangelo) 111 | def test_not_found(): 112 | analysis = requests.get(fixture.url("analyze-url/analyze-url", test=11)).json() 113 | 114 | assert analysis["directive"] is None 115 | assert analysis["content"] is not None 116 | assert analysis["content"]["type"] == Content.NotFound 117 | assert analysis["content"]["path"] == "/analyze-url/doesnt-exist.html" 118 | assert analysis["content"]["pargs"] is None 119 | -------------------------------------------------------------------------------- /tests/bundled-plugins.yaml: -------------------------------------------------------------------------------- 1 | plugins: 2 | - name: config 3 | - name: data 4 | - name: docs 5 | - name: mapping 6 | - name: mongo 7 | - name: stream 8 | - name: tangelo 9 | - name: ui 10 | - name: vis 11 | -------------------------------------------------------------------------------- /tests/commandline-version.py: -------------------------------------------------------------------------------- 1 | import fixture 2 | 3 | 4 | def test_commandline_version(): 5 | (code, stdout, stderr) = fixture.run_tangelo("--version") 6 | expected = "0.10.0-dev" 7 | 8 | print "Expected: %s" % (expected) 9 | print "Received: %s" % ("\n".join(stdout)) 10 | 11 | assert code == 0 12 | assert stderr == [] 13 | assert len(stdout) == 1 and stdout[0] == expected 14 | -------------------------------------------------------------------------------- /tests/config/bad-config.yaml: -------------------------------------------------------------------------------- 1 | - This 2 | 3 | is: a badly 4 | 5 | specified 6 | 7 | - yaml 8 | - file 9 | -------------------------------------------------------------------------------- /tests/config/list-config.yaml: -------------------------------------------------------------------------------- 1 | - This 2 | - is a valid 3 | - YAML file, 4 | - but it is not a 5 | - valid Tangelo config file 6 | - because it contains a list at the top level 7 | - instead of 8 | - an associative array. 9 | -------------------------------------------------------------------------------- /tests/echo-service.py: -------------------------------------------------------------------------------- 1 | import nose 2 | import requests 3 | 4 | import fixture 5 | 6 | 7 | @nose.with_setup(fixture.start_tangelo, fixture.stop_tangelo) 8 | def test_echo_service(): 9 | response = requests.get(fixture.url("echo", "oct", "30", color="green", answer="42")) 10 | expected = "\n".join(["[oct, 30]", "color -> green", "answer -> 42"]) 11 | 12 | assert response.content == expected 13 | -------------------------------------------------------------------------------- /tests/error-report-code.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | import fixture 4 | 5 | 6 | def bad_service(): 7 | return requests.get(fixture.url("bad")) 8 | 9 | 10 | def test_error_code(): 11 | (_, _, stderr, result) = fixture.run_tangelo("--host", fixture.host, "--port", fixture.port, "--root", "tests/web", 12 | timeout=5, terminate=True, action=bad_service) 13 | 14 | logcode = filter(lambda x: "SERVICE" in x, stderr)[0][-10:-4] 15 | reportcode = result.json()["message"].split()[2] 16 | 17 | assert logcode == reportcode 18 | -------------------------------------------------------------------------------- /tests/get_free_port.py: -------------------------------------------------------------------------------- 1 | import tangelo.util 2 | 3 | 4 | def test_get_free_port(): 5 | for _ in xrange(1000): 6 | free_port = tangelo.util.get_free_port() 7 | print "Got free port: %d" % (free_port) 8 | 9 | assert free_port > 1024 10 | -------------------------------------------------------------------------------- /tests/plugin-import.py: -------------------------------------------------------------------------------- 1 | import fixture 2 | import json 3 | 4 | 5 | moduletest = {"name": "moduletest", 6 | "path": "tests/plugins/moduletest"} 7 | pluginorder = {"name": "pluginorder", 8 | "path": "tests/plugins/pluginorder"} 9 | 10 | 11 | def test_plugin_module(): 12 | config = {"plugins": [{ 13 | "name": "moduletest", 14 | "path": "tests/plugins/moduletest" 15 | }]} 16 | (_, _, stderr) = fixture.run_tangelo("--config", json.dumps(config), timeout=3, terminate=True) 17 | stderr = "\n".join(stderr) 18 | 19 | assert "Plugin has value server.TestConstant True" in stderr 20 | 21 | 22 | def test_plugin_single_file(): 23 | config = {"plugins": [{ 24 | "name": "pythonfile", 25 | "path": "tests/plugins/pythonfile" 26 | }]} 27 | (_, _, stderr) = fixture.run_tangelo("--config", json.dumps(config), timeout=3, terminate=True) 28 | stderr = "\n".join(stderr) 29 | 30 | assert "Python file plugin" in stderr 31 | 32 | 33 | def test_plugin_order_good(): 34 | config = {"plugins": [moduletest, 35 | pluginorder]} 36 | 37 | (_, _, stderr) = fixture.run_tangelo("--config", json.dumps(config), timeout=3, terminate=True) 38 | stderr = "\n".join(stderr) 39 | 40 | assert "Plugin can reference tangelo.plugin.moduletest True" in stderr 41 | 42 | 43 | def test_plugin_order_bad(): 44 | config = {"plugins": [pluginorder, 45 | moduletest]} 46 | 47 | (_, _, stderr) = fixture.run_tangelo("--config", json.dumps(config), timeout=3, terminate=True) 48 | stderr = "\n".join(stderr) 49 | 50 | assert "Plugin pluginorder failed to load" in stderr 51 | -------------------------------------------------------------------------------- /tests/plugins/moduletest/python/__init__.py: -------------------------------------------------------------------------------- 1 | import server 2 | import tangelo 3 | tangelo.log("Plugin has value server.TestConstant %s" % str(server.TestConstant)) 4 | -------------------------------------------------------------------------------- /tests/plugins/moduletest/python/server.py: -------------------------------------------------------------------------------- 1 | TestConstant = True 2 | -------------------------------------------------------------------------------- /tests/plugins/pluginorder/python/__init__.py: -------------------------------------------------------------------------------- 1 | import tangelo 2 | 3 | modtest = tangelo.plugin.moduletest.server.TestConstant 4 | tangelo.log("Plugin can reference tangelo.plugin.moduletest %s" % str(modtest)) 5 | -------------------------------------------------------------------------------- /tests/plugins/pythonfile/python.py: -------------------------------------------------------------------------------- 1 | import tangelo 2 | 3 | tangelo.log("Python file plugin") 4 | tangelo.log("This doesn't use a Python module directory.") 5 | -------------------------------------------------------------------------------- /tests/redirection.py: -------------------------------------------------------------------------------- 1 | import nose 2 | import requests 3 | 4 | import fixture 5 | 6 | 7 | @nose.with_setup(fixture.start_tangelo, fixture.stop_tangelo) 8 | def test_redirect(): 9 | r = requests.get(fixture.url("redirect/redirect"), allow_redirects=False) 10 | assert r.ok 11 | assert r.status_code == 303 12 | assert r.text.startswith("This resource can be found at") 13 | 14 | r = requests.get(fixture.url("redirect/redirect")) 15 | assert r.ok 16 | assert r.status_code == 200 17 | assert r.text == "hello, world\n" 18 | 19 | r = requests.get(fixture.url("redirect/internal_redirect"), allow_redirects=False) 20 | assert r.ok 21 | assert r.status_code == 200 22 | assert r.text == "hello, world\n" 23 | 24 | r = requests.get(fixture.url("redirect/internal_redirect")) 25 | assert r.ok 26 | assert r.status_code == 200 27 | assert r.text == "hello, world\n" 28 | -------------------------------------------------------------------------------- /tests/rest.py: -------------------------------------------------------------------------------- 1 | import nose 2 | import requests 3 | 4 | import fixture 5 | 6 | 7 | @nose.with_setup(fixture.start_tangelo, fixture.stop_tangelo) 8 | def test_restful_service(): 9 | pos = ["one", "two", "three"] 10 | query = {"foo": "bar", "that": "telling"} 11 | 12 | url = fixture.url("restful", *pos, **query) 13 | expected = "%s: one two three {'foo': u'bar', 'that': u'telling'}" 14 | 15 | get = requests.get(url).text 16 | post = requests.post(url).text 17 | put = requests.put(url).text 18 | delete = requests.delete(url) 19 | propfind = requests.request("PROPFIND", url).text 20 | dukat = requests.request("DUKAT", url).text 21 | undefined = requests.request("UNDEFINED", url) 22 | 23 | # GET, POST, and PUT are standard rest verbs. 24 | assert get == expected % ("GET") 25 | assert post == expected % ("POST") 26 | assert put == expected % ("PUT") 27 | 28 | # PROPFIND is a less common rest verb. 29 | assert propfind == expected % ("PROPFIND") 30 | 31 | # DUKAT is not a standard rest verb, but the service will still respond to 32 | # it because it is defined properly. 33 | assert dukat == expected % ("DUKAT") 34 | 35 | # Though DELETE *is* a standard rest verb, the service module does not 36 | # expose it properly, resulting in a 405 Method Not Allowed error. 37 | assert delete.status_code == 405 38 | 39 | # UNDEFINED is not a standard rest verb, and it is not defined in the 40 | # service module - it should also give a 405 error. 41 | print undefined 42 | assert undefined.status_code == 405 43 | -------------------------------------------------------------------------------- /tests/server-identity.py: -------------------------------------------------------------------------------- 1 | import nose 2 | import requests 3 | 4 | import fixture 5 | 6 | 7 | @nose.with_setup(fixture.start_tangelo, fixture.stop_tangelo) 8 | def test_server_identity(): 9 | response = requests.get(fixture.url("/")) 10 | assert response.headers["server"] == "" 11 | -------------------------------------------------------------------------------- /tests/service-config.py: -------------------------------------------------------------------------------- 1 | import nose 2 | import requests 3 | 4 | import fixture 5 | 6 | 7 | @nose.with_setup(fixture.start_tangelo, fixture.stop_tangelo) 8 | def test_service_config(): 9 | result = requests.get(fixture.url("configured")).content 10 | 11 | assert result == "abracadabra" 12 | 13 | 14 | @nose.with_setup(fixture.start_tangelo, fixture.stop_tangelo) 15 | def test_config_protected(): 16 | result = requests.get(fixture.url("configured.yaml")) 17 | 18 | assert result.status_code == 403 19 | 20 | 21 | @nose.with_setup(fixture.start_tangelo, fixture.stop_tangelo) 22 | def test_source_protected(): 23 | result = requests.get(fixture.url("configured.py")) 24 | 25 | assert result.status_code == 403 26 | -------------------------------------------------------------------------------- /tests/service-cwd.py: -------------------------------------------------------------------------------- 1 | import nose 2 | import requests 3 | 4 | import fixture 5 | 6 | 7 | @nose.with_setup(fixture.start_tangelo, fixture.stop_tangelo) 8 | def test_service_cwd(): 9 | response = requests.get(fixture.url("/cwd")) 10 | expected = fixture.relative_path("tests/web") 11 | 12 | assert response.content == expected 13 | -------------------------------------------------------------------------------- /tests/service-import.py: -------------------------------------------------------------------------------- 1 | import nose 2 | import requests 3 | 4 | import fixture 5 | 6 | 7 | @nose.with_setup(fixture.start_tangelo, fixture.stop_tangelo) 8 | def test_service_import_local(): 9 | response = requests.get(fixture.url("import", "oct", "30", color="green", answer="42")) 10 | expected = "\n".join(["[oct, 30]", "color -> green", "answer -> 42"]) 11 | 12 | assert response.content == expected 13 | 14 | 15 | @nose.with_setup(fixture.start_tangelo, fixture.stop_tangelo) 16 | def test_service_import_path(): 17 | response = requests.get(fixture.url("sub/importpath", "oct", "30", color="green", answer="42")) 18 | expected = "\n".join(["[oct, 30]", "color -> green", "answer -> 42"]) 19 | 20 | assert response.content == expected 21 | -------------------------------------------------------------------------------- /tests/static_file.txt: -------------------------------------------------------------------------------- 1 | Infinite Diversity in Infinite Combinations 2 | -------------------------------------------------------------------------------- /tests/staticfile.py: -------------------------------------------------------------------------------- 1 | import nose 2 | import requests 3 | 4 | import fixture 5 | 6 | 7 | @nose.with_setup(fixture.start_tangelo, fixture.stop_tangelo) 8 | def test_static_file(): 9 | r = requests.get(fixture.url("static_file")) 10 | print r.text 11 | assert r.ok 12 | assert r.status_code == 200 13 | assert r.text == "Infinite Diversity in Infinite Combinations\n" 14 | assert "text/plain" in r.headers["Content-Type"] 15 | r = requests.get(fixture.url( 16 | "static_file?mime_type=application/octet-stream")) 17 | assert r.ok 18 | assert r.status_code == 200 19 | assert r.text == "Infinite Diversity in Infinite Combinations\n" 20 | assert "application/octet-stream" in r.headers["Content-Type"] 21 | -------------------------------------------------------------------------------- /tests/stream.py: -------------------------------------------------------------------------------- 1 | import nose 2 | import requests 3 | import string 4 | 5 | import fixture 6 | 7 | 8 | @nose.with_setup(fixture.start_tangelo, fixture.stop_tangelo) 9 | def test_prime_service(): 10 | primes = [int(requests.get(fixture.url("primes", n=nn)).content) for nn in range(5)] 11 | 12 | assert primes == [2, 3, 5, 7, 11] 13 | 14 | 15 | @nose.with_setup(fixture.start_tangelo, fixture.stop_tangelo) 16 | def test_prime_stream(): 17 | stream_key = requests.post(fixture.plugin_url("stream", "stream", "start", "primes")).json() 18 | 19 | assert "key" in stream_key and all([letter in string.hexdigits for letter in stream_key["key"]]) 20 | 21 | key = stream_key["key"] 22 | primes = [] 23 | 24 | for i in range(5): 25 | response = requests.post(fixture.plugin_url("stream", "stream", "next", key)) 26 | assert response.status_code == 200 27 | 28 | primes.append(response.json()) 29 | 30 | assert primes == [2, 3, 5, 7, 11] 31 | 32 | 33 | @nose.with_setup(fixture.start_tangelo, fixture.stop_tangelo) 34 | def test_finite_stream(): 35 | stream_key = requests.post(fixture.plugin_url("stream", "stream", "start", "finite")).json() 36 | 37 | assert "key" in stream_key 38 | 39 | key = stream_key["key"] 40 | 41 | hello = requests.post(fixture.plugin_url("stream", "stream", "next", key)) 42 | world = requests.post(fixture.plugin_url("stream", "stream", "next", key)) 43 | finished = requests.post(fixture.plugin_url("stream", "stream", "next", key)) 44 | not_found = requests.post(fixture.plugin_url("stream", "stream", "next", key)) 45 | 46 | # The 200 in these responses means that there was data in the stream. 47 | assert hello.status_code == 200 48 | assert hello.content == "hello" 49 | 50 | assert world.status_code == 200 51 | assert world.content == "world" 52 | 53 | # The 204 in this response means the stream is out of data. 54 | assert finished.status_code == 204 55 | 56 | # Now that the stream has finished, querying it again should result in a 57 | # 404. 58 | assert not_found.status_code == 404 59 | 60 | 61 | @nose.with_setup(fixture.start_tangelo, fixture.stop_tangelo) 62 | def test_multiple_streams(): 63 | key1 = requests.post(fixture.plugin_url("stream", "stream", "start", "finite")).json()["key"] 64 | key2 = requests.post(fixture.plugin_url("stream", "stream", "start", "finite")).json()["key"] 65 | key3 = requests.post(fixture.plugin_url("stream", "stream", "start", "finite")).json()["key"] 66 | 67 | server_keys = requests.get(fixture.plugin_url("stream", "stream")).json() 68 | server_keys.sort() 69 | 70 | my_keys = [key1, key2, key3] 71 | my_keys.sort() 72 | 73 | print server_keys 74 | print my_keys 75 | 76 | assert server_keys == my_keys 77 | 78 | 79 | @nose.with_setup(fixture.start_tangelo, fixture.stop_tangelo) 80 | def test_delete_stream(): 81 | key1 = requests.post(fixture.plugin_url("stream", "stream", "start", "finite")).json()["key"] 82 | key2 = requests.post(fixture.plugin_url("stream", "stream", "start", "finite")).json()["key"] 83 | key3 = requests.post(fixture.plugin_url("stream", "stream", "start", "finite")).json()["key"] 84 | 85 | key2d = requests.delete(fixture.plugin_url("stream", "stream", key2)).json() 86 | 87 | assert "key" in key2d 88 | assert key2d["key"] == key2 89 | 90 | server_keys = requests.get(fixture.plugin_url("stream", "stream")).json() 91 | server_keys.sort() 92 | 93 | my_keys = [key1, key3] 94 | my_keys.sort() 95 | 96 | print server_keys 97 | print my_keys 98 | 99 | assert server_keys == my_keys 100 | -------------------------------------------------------------------------------- /tests/tangelo-config-settings.py: -------------------------------------------------------------------------------- 1 | import fixture 2 | import json 3 | import nose 4 | import requests 5 | 6 | 7 | def start_tangelo(): 8 | """Start tangelo with a some settings.""" 9 | config = {"server_settings": {"server.thread_pool": 1000}} 10 | return fixture.start_tangelo("-c", json.dumps(config)) 11 | 12 | 13 | @nose.with_setup(start_tangelo, fixture.stop_tangelo) 14 | def test_config_settings(): 15 | response = requests.get(fixture.url('settings')) 16 | print response 17 | assert 'pool="1000"' in response 18 | 19 | 20 | @nose.with_setup(start_tangelo, fixture.stop_tangelo) 21 | def test_config_settings_change(): 22 | response = requests.get(fixture.url('settings', pool='500')) 23 | print response 24 | assert 'pool="500"' in response 25 | 26 | 27 | @nose.with_setup(fixture.start_tangelo, fixture.stop_tangelo) 28 | def test_config_no_settings(): 29 | response = requests.get(fixture.url('settings')) 30 | print response 31 | assert 'pool="None"' in response 32 | 33 | 34 | def test_config_wrong_type_settings(): 35 | config = {"server_settings": "Angosian"} 36 | (_, _, stderr) = fixture.run_tangelo( 37 | "-c", json.dumps(config), terminate=True) 38 | stderr = "\n".join(stderr) 39 | 40 | print stderr 41 | 42 | assert "option server_settings must be of type dict" in stderr 43 | -------------------------------------------------------------------------------- /tests/tangelo-config.py: -------------------------------------------------------------------------------- 1 | import fixture 2 | import json 3 | 4 | 5 | def test_bad_config(): 6 | (code, stdout, stderr) = fixture.run_tangelo("-c", "tests/config/bad-config.yaml") 7 | 8 | signal = "ERROR while parsing" 9 | 10 | print "Expected: '%s' in third line of log" % (signal) 11 | print "Received: %s" % (stderr[2] if len(stderr) > 2 else "") 12 | 13 | assert len(stderr) > 1 14 | assert signal in stderr[2] 15 | 16 | 17 | def test_non_dict_config(): 18 | (code, stdout, stderr) = fixture.run_tangelo("-c", "tests/config/list-config.yaml") 19 | 20 | signal = "does not contain associative array" 21 | 22 | print "Expected: '%s' in third line of log" % (signal) 23 | print "Received: %s" % (stderr[2] if len(stderr) > 1 else "") 24 | 25 | assert len(stderr) > 1 26 | assert signal in stderr[2] 27 | 28 | 29 | def test_inline_config(): 30 | config = {"plugins": [{"name": "ui"}]} 31 | (_, _, stderr) = fixture.run_tangelo("-c", json.dumps(config), terminate=True) 32 | stderr = "\n".join(stderr) 33 | 34 | print stderr 35 | 36 | assert "Server is running" in stderr 37 | assert "Plugin ui loaded" in stderr 38 | -------------------------------------------------------------------------------- /tests/tangelo-verbose.py: -------------------------------------------------------------------------------- 1 | import fixture 2 | 3 | 4 | def test_standard_verbosity(): 5 | (_, _, stderr) = fixture.run_tangelo("--port", "30047", timeout=3, terminate=True) 6 | stderr = "\n".join(stderr) 7 | 8 | assert "Server is running" in stderr 9 | assert "Hostname" in stderr 10 | 11 | 12 | def test_lower_verbosity(): 13 | (_, _, stderr) = fixture.run_tangelo("-q", "--port", "30047", timeout=3, terminate=True) 14 | 15 | assert len(stderr) == 0 16 | -------------------------------------------------------------------------------- /tests/tangelo-version.py: -------------------------------------------------------------------------------- 1 | import nose 2 | import requests 3 | 4 | import fixture 5 | 6 | 7 | @nose.with_setup(fixture.start_tangelo, fixture.stop_tangelo) 8 | def test_version(): 9 | response = requests.get(fixture.plugin_url("tangelo", "version")) 10 | expected = "0.10.0-dev" 11 | 12 | assert response.content == expected 13 | -------------------------------------------------------------------------------- /tests/tangelo-watch.py: -------------------------------------------------------------------------------- 1 | import fixture 2 | import nose 3 | import requests 4 | import os 5 | import pprint 6 | import time 7 | 8 | 9 | def get_times(response): 10 | """ 11 | Parse a response from a watch script to get the reported times. 12 | 13 | :param response: the response from a requests.get call. 14 | :returns: a dictionary of parsed times. 15 | """ 16 | times = {} 17 | for part in response.content.split("Watch ")[1:]: 18 | name = part.split(" [")[0] 19 | timestamp = part.split(" [")[1].split("]")[0] 20 | times[name] = timestamp 21 | pprint.pprint(times) 22 | return times 23 | 24 | 25 | def start_tangelo(): 26 | """Start tangelo with the watch plugin.""" 27 | return fixture.start_tangelo("--watch") 28 | 29 | 30 | def touch_file(path): 31 | """ 32 | Use os.utime to touch a file, but add a delay to make sure things have a 33 | chance to change. 34 | 35 | :param path: path to touch. 36 | """ 37 | time.sleep(2) 38 | os.utime(path, None) 39 | 40 | 41 | @nose.with_setup(start_tangelo, fixture.stop_tangelo) 42 | def test_watch_plugin(): 43 | times = [] 44 | # Check the original time 45 | response = requests.get(fixture.url("watch_a")) 46 | assert "Watch A" in response.content 47 | times.append(get_times(response)) 48 | 49 | # Calling this again shouldn't change any import time. 50 | response = requests.get(fixture.url("watch_a")) 51 | times.append(get_times(response)) 52 | assert times[-2] == times[-1] 53 | 54 | # Touch script A and check that we now get a new time for A, but not for 55 | # the sub scripts. 56 | touch_file("tests/web/watch_a.py") 57 | response = requests.get(fixture.url("watch_a")) 58 | times.append(get_times(response)) 59 | assert times[-2]["A"] != times[-1]["A"] 60 | assert times[-2]["B"] == times[-1]["B"] 61 | assert times[-2]["C"] == times[-1]["C"] 62 | assert times[-2]["D"] == times[-1]["D"] 63 | 64 | # Touch script B and check that script A updates with that, too. 65 | touch_file("tests/web/watch_b.py") 66 | response = requests.get(fixture.url("watch_a")) 67 | times.append(get_times(response)) 68 | assert times[-2]["A"] != times[-1]["A"] 69 | assert times[-2]["B"] != times[-1]["B"] 70 | assert times[-2]["C"] == times[-1]["C"] 71 | assert times[-2]["D"] == times[-1]["D"] 72 | 73 | # And again with script D which is several layers in 74 | touch_file("tests/web/watch_d.py") 75 | response = requests.get(fixture.url("watch_a")) 76 | times.append(get_times(response)) 77 | assert times[-2]["A"] != times[-1]["A"] 78 | assert times[-2]["B"] != times[-1]["B"] 79 | assert times[-2]["C"] != times[-1]["C"] 80 | assert times[-2]["D"] != times[-1]["D"] 81 | 82 | # Touching script C and then loading E should show a new C time 83 | touch_file("tests/web/watch_c.py") 84 | response = requests.get(fixture.url("watch_e")) 85 | times.append(get_times(response)) 86 | assert times[-2]["C"] != times[-1]["C"] 87 | assert times[-2]["D"] == times[-1]["D"] 88 | 89 | # Touch script B. Calling E should not show any difference in times. 90 | touch_file("tests/web/watch_b.py") 91 | response = requests.get(fixture.url("watch_e")) 92 | times.append(get_times(response)) 93 | assert times[-2] == times[-1] 94 | 95 | # All done 96 | -------------------------------------------------------------------------------- /tests/web/analyze-url/analyze-url.py: -------------------------------------------------------------------------------- 1 | from tangelo.server import analyze_url 2 | 3 | 4 | def run(test): 5 | if test == "1": 6 | return analysis_dict("analyze-url.py") 7 | elif test == "2": 8 | return analysis_dict("open.py") 9 | elif test == "3": 10 | return analysis_dict("open.yaml") 11 | elif test == "4": 12 | return analysis_dict("standalone.yaml") 13 | elif test == "6": 14 | return analysis_dict("/analyze-url", "/") 15 | elif test == "7": 16 | return analysis_dict("/analyze-url", "") 17 | elif test == "8": 18 | return analysis_dict("has-index/") 19 | elif test == "9": 20 | return analysis_dict("/plugin", "") 21 | elif test == "10": 22 | return analysis_dict("analyze-url/1/2/3") 23 | elif test == "11": 24 | return analysis_dict("doesnt-exist.html") 25 | else: 26 | raise ValueError("invalid test name: %s" % (test)) 27 | 28 | 29 | def analysis_dict(base, url=None): 30 | if url is None: 31 | url = base 32 | base = "/analyze-url/" 33 | 34 | return eval(str(analyze_url("%s%s" % (base, url)))) 35 | -------------------------------------------------------------------------------- /tests/web/analyze-url/has-index/index.html: -------------------------------------------------------------------------------- 1 | 2 | Redirect to index.html 3 | 4 |

5 | A request to the directory containing this file should perform an internal 6 | redirect to this file. 7 |

8 | -------------------------------------------------------------------------------- /tests/web/analyze-url/open.py: -------------------------------------------------------------------------------- 1 | def run(): 2 | return "I am open to the public" 3 | -------------------------------------------------------------------------------- /tests/web/analyze-url/open.yaml: -------------------------------------------------------------------------------- 1 | show-py: True 2 | -------------------------------------------------------------------------------- /tests/web/analyze-url/standalone.yaml: -------------------------------------------------------------------------------- 1 | - "This YAML file " 2 | - "should be readable " 3 | - "by anyone." 4 | -------------------------------------------------------------------------------- /tests/web/bad.py: -------------------------------------------------------------------------------- 1 | import bad 2 | 3 | 4 | def run(): 5 | return bad.doesntexist() 6 | -------------------------------------------------------------------------------- /tests/web/configured.py: -------------------------------------------------------------------------------- 1 | import tangelo 2 | 3 | 4 | def run(): 5 | cfg = tangelo.config() 6 | return cfg["secret"] 7 | -------------------------------------------------------------------------------- /tests/web/configured.yaml: -------------------------------------------------------------------------------- 1 | secret: abracadabra 2 | -------------------------------------------------------------------------------- /tests/web/cwd.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | 4 | def run(): 5 | return os.getcwd() 6 | -------------------------------------------------------------------------------- /tests/web/echo.py: -------------------------------------------------------------------------------- 1 | # This service simply echoes back the URL-encoded arguments passed to it. 2 | def run(*pargs, **kwargs): 3 | response = "" 4 | 5 | # Dump the positional arguments. 6 | if len(pargs) > 0: 7 | response += "[" + ", ".join(pargs) + "]" 8 | 9 | # Dump the keyword arguments. 10 | for k in kwargs: 11 | response += "\n%s -> %s" % (k, kwargs[k]) 12 | 13 | # Send the response back. 14 | if response != "": 15 | return response 16 | else: 17 | return "(No arguments passed)" 18 | -------------------------------------------------------------------------------- /tests/web/finite.py: -------------------------------------------------------------------------------- 1 | def stream(): 2 | yield "hello" 3 | yield "world" 4 | -------------------------------------------------------------------------------- /tests/web/import.py: -------------------------------------------------------------------------------- 1 | # This service imports a local script and runs that 2 | import echo 3 | 4 | 5 | def run(*args, **kwargs): 6 | return echo.run(*args, **kwargs) 7 | -------------------------------------------------------------------------------- /tests/web/primes.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | import math 3 | 4 | import tangelo 5 | 6 | 7 | def is_prime(v): 8 | for i in range(2, int(math.ceil(math.sqrt(v)) + 1)): 9 | if v % i == 0: 10 | return False 11 | return True 12 | 13 | 14 | def stream(): 15 | yield 2 16 | yield 3 17 | 18 | for i in itertools.count(start=5, step=2): 19 | if is_prime(i): 20 | yield i 21 | 22 | 23 | @tangelo.types(n=int) 24 | def run(n): 25 | return list(itertools.islice(stream(), n, n + 1))[0] 26 | -------------------------------------------------------------------------------- /tests/web/redirect/content.txt: -------------------------------------------------------------------------------- 1 | hello, world 2 | -------------------------------------------------------------------------------- /tests/web/redirect/internal_redirect.py: -------------------------------------------------------------------------------- 1 | import tangelo 2 | 3 | 4 | def run(): 5 | return tangelo.internal_redirect("content.txt") 6 | -------------------------------------------------------------------------------- /tests/web/redirect/redirect.py: -------------------------------------------------------------------------------- 1 | import tangelo 2 | 3 | 4 | def run(): 5 | return tangelo.redirect("content.txt") 6 | -------------------------------------------------------------------------------- /tests/web/restful.py: -------------------------------------------------------------------------------- 1 | import tangelo 2 | 3 | # This example demonstrates how to create a RESTful service for Tangelo. You 4 | # can try this out by starting Tangelo then using the curl program as follows: 5 | # 6 | # $ curl -X PUT -d foo=bar localhost:8080/service/restful/foo/baz/quark 7 | # 8 | # This will demonstrate the service's response to a PUT request, but you can 9 | # change that verb out for any of the others. Because the "delete" method has 10 | # not been exposed via the tangelo.restful decorator, attempting the DELETE 11 | # action will result in HTTP error 405. 12 | # 13 | # Also note that you can create non-standard verbs, simply by writing the 14 | # function and exposing it via the decorator. 15 | 16 | 17 | @tangelo.restful 18 | def get(*pargs, **kwargs): 19 | return "GET: " + " ".join(pargs) + " %s" % (kwargs) 20 | 21 | 22 | @tangelo.restful 23 | def post(*pargs, **kwargs): 24 | return "POST: " + " ".join(pargs) + " %s" % (kwargs) 25 | 26 | 27 | @tangelo.restful 28 | def put(*pargs, **kwargs): 29 | return "PUT: " + " ".join(pargs) + " %s" % (kwargs) 30 | 31 | 32 | # This function has not been decorated as the above three, so it will not be 33 | # part of this service's RESTful API. 34 | def delete(*pargs, **kwargs): 35 | return "DELETE: " + " ".join(pargs) + " %s" % (kwargs) 36 | 37 | 38 | # And here is an example of an unusual HTTP method. 39 | @tangelo.restful 40 | def propfind(*pargs, **kwargs): 41 | return "PROPFIND: " + " ".join(pargs) + " %s" % (kwargs) 42 | 43 | 44 | # And one that isn't even a valid HTTP method. 45 | @tangelo.restful 46 | def dukat(*pargs, **kwargs): 47 | return "DUKAT: " + " ".join(pargs) + " %s" % (kwargs) 48 | -------------------------------------------------------------------------------- /tests/web/settings.py: -------------------------------------------------------------------------------- 1 | import cherrypy 2 | import tangelo 3 | 4 | 5 | # This service reports the value of cherrypy's thread pool setting 6 | def run(**kwargs): 7 | if kwargs.get('pool'): 8 | tangelo.util.set_server_setting('server.thread_pool', int(kwargs['pool'])) 9 | response = 'pool="%r"' % cherrypy.config.get('server.thread_pool') 10 | return response 11 | -------------------------------------------------------------------------------- /tests/web/static_file.py: -------------------------------------------------------------------------------- 1 | import tangelo 2 | 3 | 4 | def run(**kwargs): 5 | return tangelo.file("../static_file.txt", 6 | content_type=kwargs.get("mime_type", "text/plain")) 7 | -------------------------------------------------------------------------------- /tests/web/sub/importpath.py: -------------------------------------------------------------------------------- 1 | # This service imports a non-local script and runs that 2 | import tangelo 3 | 4 | tangelo.paths("..") 5 | import echo 6 | 7 | 8 | def run(*args, **kwargs): 9 | return echo.run(*args, **kwargs) 10 | -------------------------------------------------------------------------------- /tests/web/watch_a.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | import watch_b 4 | 5 | 6 | ImportTime = time.time() 7 | 8 | 9 | # This service reports its name and import time, plus whatever some sub service 10 | # uses 11 | def run(*args, **kwargs): 12 | response = "Watch A [%s]" % str(ImportTime) 13 | result = watch_b.run(*args, **kwargs) 14 | if isinstance(result, basestring): 15 | response += "\n" + result 16 | return response 17 | -------------------------------------------------------------------------------- /tests/web/watch_b.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | import watch_c 4 | 5 | 6 | ImportTime = time.time() 7 | 8 | 9 | # This service reports its name and import time, plus whatever some sub service 10 | # uses 11 | def run(*args, **kwargs): 12 | response = "Watch B [%s]" % str(ImportTime) 13 | result = watch_c.run(*args, **kwargs) 14 | if isinstance(result, basestring): 15 | response += "\n" + result 16 | return response 17 | -------------------------------------------------------------------------------- /tests/web/watch_c.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | import watch_d 4 | 5 | 6 | ImportTime = time.time() 7 | 8 | 9 | # This service reports its name and import time, plus whatever some sub service 10 | # uses 11 | def run(*args, **kwargs): 12 | response = "Watch C [%s]" % str(ImportTime) 13 | result = watch_d.run(*args, **kwargs) 14 | if isinstance(result, basestring): 15 | response += "\n" + result 16 | return response 17 | -------------------------------------------------------------------------------- /tests/web/watch_d.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | 4 | ImportTime = time.time() 5 | 6 | 7 | # This service reports its name and import time, plus whatever some sub service 8 | # uses 9 | def run(*args, **kwargs): 10 | response = "Watch D [%s]" % str(ImportTime) 11 | return response 12 | -------------------------------------------------------------------------------- /tests/web/watch_e.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | import watch_c 4 | 5 | 6 | ImportTime = time.time() 7 | 8 | 9 | # This service reports its name and import time, plus whatever some sub service 10 | # uses 11 | def run(*args, **kwargs): 12 | response = "Watch E [%s]" % str(ImportTime) 13 | result = watch_c.run(*args, **kwargs) 14 | if isinstance(result, basestring): 15 | response += "\n" + result 16 | return response 17 | --------------------------------------------------------------------------------