├── .dir-locals.el ├── .gitignore ├── .travis.yml ├── LICENSE.md ├── Procfile ├── README.md ├── app.js ├── bin └── datapipes ├── doc ├── cli.md ├── dev.md ├── index.md ├── op-csv.md ├── op-cut.md ├── op-delete.md ├── op-grep.md ├── op-head.md ├── op-html.md ├── op-map.md ├── op-none.md ├── op-replace.md ├── op-strip.md └── op-tail.md ├── lib ├── fixedqueue.js ├── index.js ├── operators.js ├── shorthandlist.js ├── stdout.js ├── tools.js ├── transform.js └── util.js ├── package-lock.json ├── package.json ├── public ├── css │ └── style.css ├── favicon.ico ├── img │ ├── bg.jpg │ └── example.png └── js │ ├── jquery.tablesorter.js │ └── table.js ├── routes └── index.js ├── test ├── data │ └── gla.csv ├── system.js ├── transform.js ├── unit.js ├── unit │ ├── cut.js │ ├── none.js │ └── strip.js └── util.js └── views ├── docs.html ├── interactive.html └── layout.html /.dir-locals.el: -------------------------------------------------------------------------------- 1 | ;;; Set project level defaults for coding standards - Emacs Edition. 2 | ((nil . ((tab-width . 2) 3 | (js-indent-level . 2)))) 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .*.swp 2 | .DS_Store 3 | 4 | # Logs 5 | logs 6 | *.log 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | 11 | # Runtime data 12 | pids 13 | *.pid 14 | *.seed 15 | *.pid.lock 16 | 17 | # Directory for instrumented libs generated by jscoverage/JSCover 18 | lib-cov 19 | 20 | # Coverage directory used by tools like istanbul 21 | coverage 22 | 23 | # nyc test coverage 24 | .nyc_output 25 | 26 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 27 | .grunt 28 | 29 | # Bower dependency directory (https://bower.io/) 30 | bower_components 31 | 32 | # node-waf configuration 33 | .lock-wscript 34 | 35 | # Compiled binary addons (https://nodejs.org/api/addons.html) 36 | build/Release 37 | 38 | # Dependency directories 39 | node_modules/ 40 | jspm_packages/ 41 | 42 | # TypeScript v1 declaration files 43 | typings/ 44 | 45 | # Optional npm cache directory 46 | .npm 47 | 48 | # Optional eslint cache 49 | .eslintcache 50 | 51 | # Optional REPL history 52 | .node_repl_history 53 | 54 | # Output of 'npm pack' 55 | *.tgz 56 | 57 | # Yarn Integrity file 58 | .yarn-integrity 59 | 60 | # dotenv environment variables file 61 | .env 62 | 63 | # next.js build output 64 | .next 65 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "9" 4 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | --------------------- 3 | 4 | Copyright (c) 2013 Open Knowledge Foundation 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: node app.js 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## :warning: Deprecation notice 2 | 3 | **The datapipes website is now archived and read-only.** The `gh-pages` branch hosts the content of the static version of the website, which is now available at [datapipes.datopian.com](https://datapipes.datopian.com/). 4 | 5 | ## datapipes 6 | 7 | A node library, command line tool and webapp to provide "pipe-able" Unix-Style 8 | data transformations on row-based data like CSVs. 9 | 10 | DataPipes offers unix-style `cut`, `grep`, `sed` operations on row-based data 11 | like CSVs in a streaming, connectable "pipe-like" manner. 12 | 13 | DataPipes can be used: 14 | 15 | * Online at 16 | * Via a command line interface - see below 17 | * As a Node JS library - see below 18 | 19 | [![Build 20 | Status](https://api.travis-ci.org/okfn/datapipes.svg)](https://travis-ci.org/okfn/datapipes) 21 | 22 | ## Install 23 | 24 | ``` 25 | npm install -g datapipes 26 | ``` 27 | 28 | ## Usage - Command line 29 | 30 | Once installed, `datapipes` will be available on the command line: 31 | 32 | datapipes -h 33 | 34 | See the help for usage instructions, but to give a quick taster: 35 | 36 | # head (first 10 rows) of this file 37 | datapipes https://raw.githubusercontent.com/datasets/browser-stats/c2709fe7/data.csv head 38 | 39 | # search for occurrences of London (ignore case) and show first 10 results 40 | datapipes https://raw.githubusercontent.com/rgrp/dataset-gla/75b56891/data/all.csv "grep -i london" head 41 | 42 | ## Usage - Library 43 | 44 | See the [Developer 45 | Docs](https://github.com/okfn/datapipes/blob/master/doc/dev.md). 46 | 47 | ---- 48 | 49 | ## Developers 50 | 51 | ### Installation 52 | 53 | This is a Node Express application. To install and run do the following. 54 | 55 | 1. Clone this repo 56 | 2. Change into the repository base directory 57 | 3. Run: 58 | 59 | ```bash 60 | $ npm install 61 | ``` 62 | 63 | ### Testing 64 | 65 | Once installed, you can run the tests locally with: 66 | 67 | ```bash 68 | $ npm test 69 | ``` 70 | 71 | ### Running 72 | 73 | To start the app locally, it’s: 74 | 75 | ```bash 76 | $ node app.js 77 | ``` 78 | 79 | You can then access it from 80 | 81 | ### Deployment 82 | 83 | For deployment we use Heroku. 84 | 85 | The primary app is called `datapipes` on Heroku. To add it as a git remote, do: 86 | 87 | ```bash 88 | $ heroku git:remote -a datapipes 89 | ``` 90 | 91 | Then to deploy: 92 | 93 | ```bash 94 | $ git push datapipes 95 | ``` 96 | 97 | ## Inspirations and Related 98 | 99 | * https://github.com/substack/dnode dnode is an asynchronous rpc system for 100 | node.js that lets you call remote functions. You can pass callbacks to remote 101 | functions, and the remote end can call the functions you passed in with 102 | callbacks of its own and so on. It's callbacks all the way down! 103 | 104 | ## Copyright and License 105 | 106 | Copyright 2013-2014 Open Knowledge Foundation and Contributors. 107 | 108 | Licensed under the MIT license: 109 | 110 | Permission is hereby granted, free of charge, to any person obtaining a copy 111 | of this software and associated documentation files (the "Software"), to deal 112 | in the Software without restriction, including without limitation the rights 113 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 114 | copies of the Software, and to permit persons to whom the Software is 115 | furnished to do so, subject to the following conditions: 116 | 117 | The above copyright notice and this permission notice shall be included in 118 | all copies or substantial portions of the Software. 119 | 120 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 121 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 122 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 123 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 124 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 125 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 126 | THE SOFTWARE. 127 | 128 | 129 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'), 2 | express = require('express'), 3 | path = require('path'), 4 | nunjucks = require('nunjucks'), 5 | request = require('request'), 6 | marked = require('marked'), 7 | _ = require('underscore'); 8 | 9 | var util = require('./lib/util'), 10 | TransformOMatic = require('./lib/transform'), 11 | routes = require('./routes/index'); 12 | 13 | //CORS middleware 14 | var CORSSupport = function(req, res, next) { 15 | res.header('Access-Control-Allow-Origin', '*'); 16 | res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE'); 17 | res.header('Access-Control-Allow-Headers', 'Content-Type'); 18 | // intercept OPTIONS method 19 | if ('OPTIONS' == req.method) { 20 | res.send(200); 21 | } 22 | else { 23 | next(); 24 | } 25 | }; 26 | 27 | var chromeSpaceReplace = function(req, res, next) { 28 | var re = /(?:Windows|Macintosh).*?Chrome/; 29 | var agent = req.headers['user-agent'] || ''; 30 | if (re.test(agent)) { 31 | var parts = req.url.split('?'); 32 | var datapipe = parts.shift(); 33 | if (datapipe.indexOf('%20') !== -1) { 34 | // replace %20s with nbsps 35 | datapipe = datapipe.replace(/%20/g, ' '); 36 | parts.unshift(datapipe); 37 | res.redirect(parts.join('?')); 38 | return; 39 | } 40 | } 41 | 42 | next(); 43 | }; 44 | 45 | function errorHandler(err, req, res, next) { 46 | res.status(500); 47 | res.render('error', { error: err }); 48 | } 49 | 50 | function getMarkdownContent(filepath, cb) { 51 | fs.readFile(filepath, 'utf8', function(err, text) { 52 | if (err) { 53 | cb(err, null); 54 | } else { 55 | cb(null, marked(text, {gfm: false})); 56 | } 57 | }); 58 | } 59 | 60 | function datapipe(path, query, res) { 61 | var pipelineSpec = util.parseUrl(path, query); 62 | var transformers = TransformOMatic.pipeline(pipelineSpec, res); 63 | 64 | if (_.last(transformers).contentType) { 65 | res.setHeader("Content-Type", _.last(transformers).contentType()); 66 | } else { 67 | // default to plain text 68 | res.setHeader("Content-Type", "text/plain; charset=utf-8"); 69 | } 70 | 71 | var url = query.url; 72 | var stream = request(url) 73 | .on('error', function(err) { 74 | var errStr = 'Error with upstream URL: ' + url; 75 | console.log(errStr); 76 | res.send(500, errStr); 77 | }); 78 | TransformOMatic.transform(res, transformers, stream); 79 | } 80 | 81 | var app = express(); 82 | app.set('port', process.env.PORT || 5000); 83 | app.set('views', __dirname + '/templates'); 84 | app.use(chromeSpaceReplace); 85 | app.use(CORSSupport); 86 | app.use(errorHandler); 87 | app.use(express.static(path.join(__dirname, 'public'))); 88 | 89 | var env = new nunjucks.Environment(new nunjucks.FileSystemLoader('views')); 90 | env.express(app); 91 | 92 | app.get(/\/interactive(\/.*)?/, routes.wizard); 93 | app.get(/\/wizard(\/.*)?/, routes.wizard); 94 | 95 | app.get('/*', function(req, res) { 96 | var mdFilename; 97 | var path = req.params[0]; 98 | 99 | if (!req.query.url) { 100 | // if there's no url parameter, 101 | // attempt to serve a doc page 102 | var page = path.split('/')[0]; 103 | if (page === '') { 104 | mdFilename = 'doc/index.md'; 105 | } else { 106 | mdFilename = 'doc/op-' + page + '.md'; 107 | } 108 | getMarkdownContent(mdFilename, function(err, content) { 109 | if (err) { 110 | console.log(err); 111 | res.send(404, 'Page not found: ' + req.params[0]); 112 | } else { 113 | res.render('docs.html', { 114 | content: content 115 | }); 116 | } 117 | }); 118 | } else { 119 | datapipe(path, req.query, res); 120 | } 121 | }); 122 | 123 | var server = app.listen(app.get('port'), function() { 124 | console.log("Listening on " + app.get('port')); 125 | }); 126 | 127 | module.exports = server; 128 | -------------------------------------------------------------------------------- /bin/datapipes: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var TransformOMatic = require('../lib/transform'), 3 | stdout = require('../lib/stdout'), 4 | stdin = process.stdin, 5 | fs = require('fs'), 6 | request = require('request'), 7 | path = require('path'), 8 | util = require('../lib/util'); 9 | 10 | var docdir = path.join(path.dirname(__dirname), 'doc'), 11 | filepath = path.join(docdir, 'cli.md'), 12 | usage = fs.readFileSync(filepath, 'utf8'), 13 | opInfo = ''; 14 | 15 | var opList = fs.readdirSync(docdir).filter(function(fn) { 16 | return fn.indexOf('op-') === 0; 17 | }).map(function(fn) { 18 | var opName = fn.replace('op-', '').replace('.md', ''); 19 | return opName; 20 | }); 21 | 22 | opInfo = opList.join('\n'); 23 | 24 | opInfo += '\n You can get more info on each command by doing:\n\n'; 25 | opInfo += ' $0 OPERATION -h\n\n'; 26 | 27 | usage = usage.replace('{{Operations}}', opInfo); 28 | 29 | var yargs = require('optimist') 30 | .options({ 31 | s: {alias: 'share', boolean: true, describe: 'Generate a URL to share this.'}, 32 | h: {alias: 'help', boolean: false, describe: 'Show help (generic or specific operation)'} 33 | }) 34 | .usage(usage) 35 | .demand(1, 'Please provide a data source and at least one transform') 36 | ; 37 | var argv = yargs.argv; 38 | 39 | if (argv.h) { 40 | if (argv._.length > 0) { 41 | var opName = argv._[0]; 42 | if (opList.indexOf(opName) === -1) { 43 | console.log(opName + ' operation is unknown'); 44 | } else { 45 | var fp = path.join(docdir, 'op-' + opName + '.md'), 46 | info = fs.readFileSync(fp, 'utf8'); 47 | 48 | info = 'Operation: ' + opName + '\n' + info.split('\n').slice(1).join('\n'); 49 | console.log(info); 50 | } 51 | } else { 52 | console.log(yargs.help()); 53 | } 54 | // exit 55 | return; 56 | } 57 | 58 | var stream, 59 | pathOrUrl = argv._[0]; 60 | 61 | if (pathOrUrl.indexOf('http') === 0) { 62 | stream = request(pathOrUrl); 63 | } else if (pathOrUrl === '_') { 64 | stream = stdin; 65 | } else { 66 | stream = fs.createReadStream(pathOrUrl); 67 | } 68 | 69 | // cli mode 70 | var transformStr = argv._.slice(1) 71 | .map(function(item) { 72 | return item.trim(' '); 73 | }) 74 | .join('/'); 75 | 76 | if (argv.share) { 77 | var transformUrl = 'https://datapipes.okfnlabs.org/'; 78 | transformUrl += encodeURI(transformStr); 79 | transformUrl += '?url='; 80 | transformUrl += argv.url; 81 | var stars = Array(transformUrl.length+1).join('*'); 82 | 83 | console.log('URL to share:'); 84 | console.log(stars); 85 | console.log(transformUrl); 86 | console.log(stars); 87 | 88 | return; 89 | } 90 | 91 | 92 | var pipelineSpec = util.parseUrl(transformStr); 93 | var transformers = TransformOMatic.pipeline(pipelineSpec); 94 | TransformOMatic.transform(stdout(), transformers, stream); 95 | -------------------------------------------------------------------------------- /doc/cli.md: -------------------------------------------------------------------------------- 1 | Perform streaming data transformations on local and online csv files. 2 | 3 | Usage: $0 [-s] DATA [PIPELINE OF OPERATIONS ...] 4 | 5 | DATA the file path or URL to the data you want to pass through the 6 | data pipeline. If you want to use stdin use '_' (underscore) 7 | 8 | PIPELINE is the series of operations which will be applied to the input data. 9 | The PIPELINE can be specified in 2 ways on the command line. 10 | 11 | A. in the form like that used online: a single string with 12 | operations separated by '/' e.g. 13 | 14 | "/delete 1/grep abc/head" 15 | 16 | B. as individual operations separated by spaces (ie. classic 17 | positional arguments) 18 | 19 | "delete 1" "grep abc" head 20 | 21 | Available operations are listed below. 22 | 23 | Operations 24 | ========== 25 | 26 | {{Operations}} 27 | 28 | Examples 29 | ======== 30 | 31 | $0 data.csv head 32 | $0 data.csv "delete 2" head 33 | $0 data.csv "/delete 2/head/" 34 | 35 | -------------------------------------------------------------------------------- /doc/dev.md: -------------------------------------------------------------------------------- 1 | # Using as a Library 2 | 3 | ``` 4 | var dp = require('datapipes'); 5 | 6 | // load data from inUrl, write to outFile after applying the sequence of transformations 7 | dp.transform(inUrl, outFile, [ 8 | { 9 | operator: 'head', 10 | options: { 11 | number: 10 // number of rows 12 | } 13 | }, 14 | { 15 | operator: 'grep', 16 | options: { 17 | regex: 'london', 18 | ignorecase: true 19 | } 20 | }, 21 | { 22 | operator: 'delete', 23 | options: { 24 | range: '3,5:10' 25 | } 26 | } 27 | ]); 28 | ``` 29 | 30 | ## do it by hand 31 | 32 | ``` 33 | // create a head operator 34 | // this is a stream transform - see node docs 35 | var headOp = dp.operators.head(args, options); 36 | 37 | // create a CSV 38 | var csv = csv().from(...) 39 | 40 | var outFile = fs.createWriteStream('tmp.txt'); 41 | 42 | csv.pipe(headOp).pipe(outFile); 43 | ``` 44 | 45 | ## Make Your Own Operators 46 | 47 | We have a helpful `mapToTranform` 48 | 49 | Suppose you have a map function 50 | 51 | ``` 52 | function helloMap(obj, idx) { 53 | row[0] = 'hello' 54 | return row; 55 | } 56 | 57 | operators['hello'] = mapToTransform(helloMap); 58 | ``` 59 | 60 | ## How It Works 61 | 62 | Operators are Transform streams - i.e. a readable and writable stream 63 | i.e. a readable stream pipes data into it, and a writable stream is piped 64 | from it. The data piped in is a series of JSON objects of the form: 65 | 66 | { 67 | row: ['row', 'data', 'goes', 'here'], 68 | index: 7 69 | } 70 | 71 | The data pushed out should be of the same form. 72 | 73 | Questions: 74 | 75 | * How do I skip a row (just don't push it on ...) 76 | * How do I tell an upstream part of the pipeline to halt 77 | 78 | Most of the operators here inherit the node Transform class. As such, they 79 | define a _transform() method, and optionally a _flush() method. More 80 | details here: 81 | 82 | http://nodejs.org/api/stream.html#stream_class_stream_transform_1 83 | 84 | -------------------------------------------------------------------------------- /doc/index.md: -------------------------------------------------------------------------------- 1 | # Shareable Simple Data Transformations 2 | 3 | Data Pipes is an online service for doing **simple data transformations** on tabular 4 | data – deleting rows and columns, find and replace, filtering, viewing as HTML. 5 | 6 | Even better you can **connect these transformations** together Unix pipes style 7 | to make more complex transformations (for example, first delete a column, then 8 | do a find and replace). 9 | 10 | You can do all of this in your browser **without having to install anything** 11 | and to **share your data and pipeline** all you need to do is copy and paste a 12 | URL. 13 | 14 | ### Quick start 15 | 16 | * [View a CSV][html] – turn a CSV into a nice online HTML table in seconds 17 | * [Pipeline Wizard][wizard] – create your own Data Pipeline interactively 18 | * [Find out more](#doc) – including full docs of the API 19 | 20 | [wizard]: /wizard/ 21 | 22 | 23 | 24 | ## Example 25 | 26 | To illustrate here's an example which shows the power of DataPipes. It shows 27 | DataPipes being used to clean up and display a raw spending data CSV file from 28 | the Greater London Authority. 29 | 30 | [https://datapipes.okfnlabs.org/csv/head -n 50/cut 0/delete 1:7/grep -i London/html?url=https://raw.githubusercontent.com/okfn/datapipes/master/test/data/gla.csv][ex] 31 | 32 | [ex]: /csv/head%20-n%2050/cut%200/delete%201:7/grep%20-i%20London/html?url=https://raw.githubusercontent.com/okfn/datapipes/master/test/data/gla.csv 33 | 34 | This does the following: 35 | 36 | * parses the incoming url as CSV 37 | * slices out the first 50 rows (using [head][]) 38 | * deletes the first column (using [cut][]) 39 | * deletes rows 1-5 (using [delete][]) 40 | * then selects those rows with London (case-insensitive) in them (using [grep][]) 41 | * finally transforms the output to an HTML table (using [html][]) 42 | 43 | Here's what the output looks like: 44 | 45 | [][ex] 46 | 47 |

API

48 | 49 | The basic API is of the form: 50 | 51 | /csv/{transform} {args}/?url={source-url} 52 | 53 | For example, here is a head operation which shows first n rows or a file (default case with no arguments will show first 10 lines): 54 | 55 | /csv/head/?url={source-url} 56 | 57 | With arguments (showing first 20 rows): 58 | 59 | /csv/head -n 20/?url={source-url} 60 | 61 | ### Piping 62 | 63 | You can also do **piping**, that is pass output of one transformation as input to another: 64 | 65 | /csv/{trans1} {args}/{trans2} {args}/.../?url={source-url} 66 | 67 | ### Input Formats 68 | 69 | At present we only support CSV but we are considering support for JSON, plain text and RSS. 70 | 71 | *If you are interested in [JSON support then vote here][json-issue])* 72 | 73 | [json-issue]: https://github.com/okfn/datapipes/issues/16 74 | 75 | ### Query string substitution 76 | 77 | Some characters can’t be used in a URL path because of [restrictions][ietf]. If this is a limitation (for instance if you need to use backslashes in your `grep` regex) variables can be defined in the query string and substituted in. E.g.: 78 | 79 | /csv/grep $dt/html/?dt=\d{2}-\d{2}-\d{4}&url={source-url} 80 | 81 | [ietf]: https://tools.ietf.org/html/rfc3986 82 | 83 | ### CORS and JS web apps 84 | 85 | CORS is supported so you can use this from pure JS web apps. 86 | 87 | ## Transform Operations 88 | 89 | The basic operations are inspired by unix-style commands such `head`, `cut`, `grep`, `sed` but really anything a map function can do could be supported. ([Suggest new operations here][suggest]). 90 | 91 | [suggest]: https://github.com/okfn/datapipes/issues 92 | 93 | * [none][] (aka `raw`) = no transform but file parsed (useful with CORS) 94 | * [csv][] = parse / render csv 95 | * [head][] = take only first X rows 96 | * [tail][] = take only last X rows 97 | * [delete][] = delete rows 98 | * [strip][] = delete all blank rows 99 | * [grep][] = filter rows based on pattern matching 100 | * [cut][] = select / delete columns 101 | * [replace][] = find and replace (not yet implemented) 102 | * [html][] = render as viewable HTML table 103 | 104 | [none]: /none/ 105 | [csv]: /csv/ 106 | [head]: /head/ 107 | [tail]: /tail/ 108 | [delete]: /delete/ 109 | [strip]: /strip/ 110 | [grep]: /grep/ 111 | [cut]: /cut/ 112 | [replace]: /replace/ 113 | [html]: /html/ 114 | 115 |

Contributing

116 | 117 | Under the hood Data Pipes is a simple open-source node.js webapp living [here on github][source]. 118 | 119 | It's super easy to contribute and here are some of the [current issues][issues]. 120 | 121 | [source]: https://github.com/okfn/datapipes 122 | [issues]: https://github.com/okfn/datapipes/issues 123 | 124 | -------------------------------------------------------------------------------- /doc/op-csv.md: -------------------------------------------------------------------------------- 1 | ## csv 2 | 3 | Parse or render csv 4 | 5 | ### Usage 6 | 7 | csv [-tHS] [-d DELIMITER] [-p ESCAPECHAR] [-q QUOTECHAR] 8 | 9 | -t, --tabs 10 | Indicate the data being parsed or rendered is 11 | tab-delimited. 12 | 13 | -H, --no-header-row 14 | On parse, indicate that the data does not contain a 15 | header row. 16 | On render, this switch is not valid and has no effect. 17 | 18 | -S, --skipinitialspace 19 | Ignore whitespace immediately following the delimiter. 20 | 21 | -d DELIMITER, --delimiter DELIMITER 22 | Delimiting character for the data. Defaults to comma. 23 | 24 | -p ESCAPECHAR, --escapechar ESCAPECHAR 25 | Character used to escape the quote character. Defaults 26 | to backslash. 27 | 28 | -q QUOTECHAR, --quotechar QUOTECHAR 29 | Character used to quote strings. 30 | 31 | ### Examples 32 | 33 | Turn comma-delimited data (csv) to tab-delimited (tsv). 34 | 35 | /csv/csv -t/?url=http://static.london.gov.uk/gla/expenditure/docs/2012-13-P12-250.csv 36 | -------------------------------------------------------------------------------- /doc/op-cut.md: -------------------------------------------------------------------------------- 1 | ## cut 2 | 3 | Remove specified columns from data. 4 | 5 | ### Usage 6 | 7 | cut [--complement] [RANGE] 8 | 9 | --complement 10 | Keep the specified columns, and delete the rest. 11 | 12 | RANGE 13 | Comma separated column indices (0 based). Ranges can 14 | also be specified with a hyphen. 15 | 16 | ### Examples 17 | 18 | Delete the columns 0 and 3 of a CSV file: 19 | 20 | /csv/cut 0,3/?url=http://static.london.gov.uk/gla/expenditure/docs/2012-13-P12-250.csv 21 | 22 | Delete the columns 1,3 and 5 to 10 of a CSV file: 23 | 24 | /csv/cut 1,3,5:10/?url=http://static.london.gov.uk/gla/expenditure/docs/2012-13-P12-250.csv 25 | -------------------------------------------------------------------------------- /doc/op-delete.md: -------------------------------------------------------------------------------- 1 | ## delete 2 | 3 | Remove specified rows from data. 4 | 5 | ### Usage 6 | 7 | delete [RANGE] 8 | 9 | RANGE 10 | Comma separated column indices (0 based). Ranges can 11 | also be specified with a colon. 12 | 13 | range = comma separated range of row indices (0 based) e.g. 14 | 15 | ### Examples 16 | 17 | Delete the rows 1-5 and 10-15 (inclusive) of a CSV file: 18 | 19 | /csv/delete 1:5,10:15/?url=http://static.london.gov.uk/gla/expenditure/docs/2012-13-P12-250.csv 20 | -------------------------------------------------------------------------------- /doc/op-grep.md: -------------------------------------------------------------------------------- 1 | ## grep 2 | 3 | Filter data to only those rows where certain columns match a pattern. 4 | 5 | ### Usage 6 | 7 | grep [-iv] [-e pattern] [-c columns] [PATTERN] 8 | 9 | -c COLUMNS, --columns COLUMNS 10 | comma-separated list of columns to search. 11 | 12 | -e PATTERN, --regexp PATTERN 13 | The regular expression to search for. 14 | 15 | -i, --ignore-case 16 | Perform case-insensitive pattern matching. 17 | 18 | -v, --invert-match 19 | Return the rows that do __not__ match the regular 20 | expression. 21 | 22 | ### Examples 23 | 24 | Return only those rows containing LONDON: 25 | 26 | /csv/grep LONDON?url=http://static.london.gov.uk/gla/expenditure/docs/2012-13-P12-250.csv 27 | 28 | Return only those rows that do __not__ mention LONDON (piped through html): 29 | 30 | /csv/grep -v LONDON/html/?url=http://static.london.gov.uk/gla/expenditure/docs/2012-13-P12-250.csv 31 | -------------------------------------------------------------------------------- /doc/op-head.md: -------------------------------------------------------------------------------- 1 | ## head 2 | 3 | Truncate dataset to its first rows. 4 | 5 | ### Usage 6 | 7 | head [[-n] COUNT] 8 | 9 | COUNT 10 | Number of rows to truncate to. If this option is 11 | omitted, it defaults to 10. 12 | 13 | Note we allow you to prefix `COUNT` with `-n` to ensure compatability with standard unix `head`. 14 | 15 | ### Examples 16 | 17 | Return the first 10 rows. 18 | 19 | [/csv/head/?url=https://raw.githubusercontent.com/datasets/bond-yields-uk-10y/9e921283/data/annual.csv](/csv/head/?url=https://raw.githubusercontent.com/datasets/bond-yields-uk-10y/9e921283/data/annual.csv) 20 | 21 | Return the first 20 rows. 22 | 23 | [/csv/head 20/?url=https://raw.githubusercontent.com/datasets/bond-yields-uk-10y/9e921283/data/annual.csv](/csv/head%2020/?url=https://raw.githubusercontent.com/datasets/bond-yields-uk-10y/9e921283/data/annual.csv) 24 | 25 | -------------------------------------------------------------------------------- /doc/op-html.md: -------------------------------------------------------------------------------- 1 | ## html 2 | 3 | Convert the data to an elegant HTML table (with line numbers!). 4 | 5 |
6 | 7 | 8 |
9 |
10 | 11 | ### Usage 12 | 13 | html 14 | 15 | You can also highlight lines by their line numbers: 16 | 17 | html/?url=...#L10 18 | 19 | ### Examples 20 | 21 | S&P 500 companies: 22 | 23 | [/csv/html/?url=https://raw.githubusercontent.com/datasets/s-and-p-companies-financials/c9f83a9c/data/constituents-financials.csv](/csv/html/?url=https://raw.githubusercontent.com/datasets/s-and-p-companies-financials/c9f83a9c/data/constituents-financials.csv) 24 | 25 | Highlight a line: 26 | 27 | [/csv/html/?url=https://raw.githubusercontent.com/datasets/s-and-p-companies-financials/c9f83a9c/data/constituents-financials.csv#L110](/csv/html/?url=https://raw.githubusercontent.com/datasets/s-and-p-companies-financials/c9f83a9c/data/constituents-financials.csv#L110) 28 | 29 | -------------------------------------------------------------------------------- /doc/op-map.md: -------------------------------------------------------------------------------- 1 | ## map 2 | 3 | Apply a user-defined operator 4 | 5 | ### Usage 6 | 7 | map [URL] 8 | 9 | URL 10 | Location of the code of the operator. 11 | 12 | ### Operator definition 13 | 14 | Operators consist of two functions: 15 | 16 | * `transform(input)`, which accepts the current line of data, and returns the processed line. 17 | * `flush()`, which is called after all the data has been passed through. 18 | 19 | This [uppercase operator](https://gist.github.com/andylolz/7794290) serves as an example. 20 | 21 | ### Examples 22 | 23 | [/csv/map $map/html?map=https://gist.github.com/andylolz/7794290/raw/8e88a5daac9a6496a8397dad99e14f18ed5ab378/uppercase.js&url=https://raw.githubusercontent.com/okfn/datapipes/master/test/data/gla.csv](/csv/map%20$map/html?map=https://gist.github.com/andylolz/7794290/raw/8e88a5daac9a6496a8397dad99e14f18ed5ab378/uppercase.js&url=https://raw.githubusercontent.com/okfn/datapipes/master/test/data/gla.csv) 24 | -------------------------------------------------------------------------------- /doc/op-none.md: -------------------------------------------------------------------------------- 1 | ## none (aka raw) 2 | 3 | No transform of the data. Still useful as: 4 | 5 | * CORS support (you can access a random CSV file from JS) 6 | * Simple plain text style view (quick and dirty view of the file) 7 | 8 | ### Usage 9 | 10 | none 11 | 12 | ### Examples 13 | 14 | [/none/?url=https://raw.githubusercontent.com/datasets/bond-yields-uk-10y/9e921283/data/annual.csv](/none/?url=https://raw.githubusercontent.com/datasets/bond-yields-uk-10y/9e921283/data/annual.csv) 15 | -------------------------------------------------------------------------------- /doc/op-replace.md: -------------------------------------------------------------------------------- 1 | ## replace 2 | 3 | Find and replace. 4 | 5 | ### Usage 6 | 7 | replace [-r] [-c columns] [FIND] [REPLACE] 8 | 9 | -r, --regex 10 | Find argument is a regular expression. 11 | 12 | -c COLUMNS, --columns COLUMNS 13 | comma-separated list of columns to search. 14 | 15 | FIND 16 | Text to search for. 17 | 18 | REPLACE 19 | Text to replace the found text with. Defaults 20 | to the empty string. 21 | 22 | ### Examples 23 | 24 | Turn multiple consecutive spaces into a single space. 25 | 26 | [/csv/head/replace -r $f $r?f=\s\s*&r=%20&url=https://raw.githubusercontent.com/okfn/datapipes/master/test/data/gla.csv](/csv/head/replace%20-r%20$f%20$r?f=\s\s*&r=%20&url=https://raw.githubusercontent.com/okfn/datapipes/master/test/data/gla.csv) 27 | -------------------------------------------------------------------------------- /doc/op-strip.md: -------------------------------------------------------------------------------- 1 | ## strip 2 | 3 | Discard empty rows. 4 | 5 | ### Usage 6 | 7 | strip 8 | 9 | ### Examples 10 | 11 | [/csv/head/strip?url=https://raw.githubusercontent.com/okfn/datapipes/master/test/data/gla.csv](/csv/head/strip?url=https://raw.githubusercontent.com/okfn/datapipes/master/test/data/gla.csv) 12 | -------------------------------------------------------------------------------- /doc/op-tail.md: -------------------------------------------------------------------------------- 1 | ## tail 2 | 3 | Truncate dataset to its last rows. 4 | 5 | ### Usage 6 | 7 | tail [-n COUNT] 8 | 9 | -n COUNT 10 | Number of rows to truncate to. If this option is 11 | omitted, it defaults to 10. 12 | 13 | A leading + sign means this number is relative to the 14 | first row. Otherwise it is relative to the last row. 15 | 16 | ### Examples 17 | 18 | Return the last 10 rows. 19 | 20 | [/csv/tail/?url=https://raw.githubusercontent.com/datasets/bond-yields-uk-10y/9e921283/data/annual.csv](/csv/tail/?url=https://raw.githubusercontent.com/datasets/bond-yields-uk-10y/9e921283/data/annual.csv) 21 | 22 | Return all rows after the first 5. 23 | 24 | [/csv/tail -n +5/?url=https://raw.githubusercontent.com/datasets/bond-yields-uk-10y/9e921283/data/annual.csv](/csv/tail -n +5/?url=https://raw.githubusercontent.com/datasets/bond-yields-uk-10y/9e921283/data/annual.csv) 25 | -------------------------------------------------------------------------------- /lib/fixedqueue.js: -------------------------------------------------------------------------------- 1 | function FixedQueue(maxsize) { 2 | if (!(this instanceof FixedQueue)) return new FixedQueue(size); 3 | this._maxsize = maxsize; 4 | this._queue = []; 5 | } 6 | 7 | FixedQueue.prototype.push = function(item) { 8 | this._queue.push(item); 9 | if (this._queue.length > this._maxsize) { 10 | return this._queue.shift(); 11 | } 12 | return undefined; 13 | }; 14 | 15 | FixedQueue.prototype.shift = function() { 16 | return this._queue.shift(); 17 | }; 18 | 19 | FixedQueue.prototype.get = function() { 20 | return this._queue; 21 | }; 22 | 23 | module.exports = FixedQueue; 24 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | var ops = require('./operators.js'), 2 | transform = require('./transform'); 3 | 4 | module.exports = { 5 | operators: ops, 6 | pipeline: transform.pipeline, 7 | transform: transform.transform 8 | }; 9 | -------------------------------------------------------------------------------- /lib/operators.js: -------------------------------------------------------------------------------- 1 | var _ = require('underscore'); 2 | var csv = require('csv'); 3 | var inherits = require('util').inherits; 4 | var PassThrough = require('stream').PassThrough; 5 | var request = require('request'); 6 | var Sandbox = require('sandbox'); 7 | var Transform = require('stream').Transform; 8 | var FixedQueue = require('./fixedqueue'); 9 | var optimist = require('./tools').optimist; 10 | var ShorthandList = require('./shorthandlist'); 11 | 12 | var headerrow = false; 13 | 14 | // For a given pipeline, each operator is constructed by calling: 15 | // 16 | // ```new operators[operator name](args)``` 17 | // 18 | // ...where operator name is the name of the operator, and args is a string 19 | // array of the input arguments to the operator. 20 | // 21 | // The constructed operator is then treated as a readable and writable stream 22 | // i.e. a readable stream pipes data into it, and a writable stream is piped 23 | // from it. The data piped in is a series of stringified JSON objects of the 24 | // form: 25 | // 26 | // {row: ['row', 'data', 'goes', 'here'], index: 7} 27 | // 28 | // The data pushed out should be of the same form. 29 | // 30 | // Most of the operators here inherit the node Transform class. As such, they 31 | // define a _transform() method, and optionally a _flush() method. More 32 | // details here: 33 | // 34 | // http://nodejs.org/api/stream.html#stream_class_stream_transform_1 35 | 36 | var operators = { 37 | incsv: function(args) { 38 | var argv = optimist() 39 | .options({ 40 | d: {alias: 'delimiter', default: ',', string: true}, 41 | p: {alias: 'escapechar', default: '\\', string: true}, 42 | q: {alias: 'quotechar', default: '\"', string: true}, 43 | t: {alias: 'tabs', boolean: true}, 44 | H: {alias: 'no-header-row', boolean: true}, 45 | S: {alias: 'skipinitialspace', boolean: true}, 46 | }) 47 | .string(['delimiter', 'escapechar', 'quotechar']) 48 | .parse(args) 49 | ; 50 | 51 | // by default, assume there is a header row 52 | headerrow = true; 53 | if (argv.H || argv['header-row'] === false) { 54 | headerrow = false; 55 | } 56 | 57 | if (argv.t) { 58 | // tabs switch overrides delimiter opt 59 | argv.d = '\t'; 60 | } 61 | return csv() 62 | .from.options({ 63 | delimiter: argv.d, 64 | escape: argv.p, 65 | quote: argv.q, 66 | ltrim: argv.S, 67 | }) 68 | .transform(function(row, index) { 69 | return JSON.stringify({'row': row, 'index': index}); 70 | }); 71 | }, 72 | 73 | // none transformation 74 | none: function(args) { 75 | PassThrough.call(this); 76 | }, 77 | 78 | // return our HEAD transformation 79 | head: function(args) { 80 | Transform.call(this, {objectMode: true}); 81 | 82 | var argv = optimist() 83 | .options('n', { 84 | default: 10, 85 | }) 86 | .parse(args) 87 | ; 88 | 89 | this._number = argv.n; 90 | if (argv._[0]) { 91 | try { 92 | this._number = parseInt(argv._[0]); 93 | } catch(e) {} 94 | } 95 | this._lines = 0; 96 | 97 | this._pushheaderrow = headerrow; 98 | }, 99 | 100 | // return our tail transformation 101 | tail: function(args) { 102 | Transform.call(this, {objectMode: true}); 103 | 104 | var argv = optimist() 105 | .options('n', { 106 | default: '10', 107 | string: true 108 | }) 109 | .parse(args) 110 | ; 111 | 112 | var number = argv.n; 113 | 114 | if (number.charAt(0) == '+') { 115 | // relative to the beginning of the stream 116 | this._rel = 'beginning'; 117 | this._lines = 0; 118 | this._number = number; 119 | } else { 120 | // relative to the end of the stream 121 | this._rel = 'end'; 122 | if (number.charAt(0) == '-') { 123 | number = -number; 124 | } 125 | this._fixedqueue = new FixedQueue(number); 126 | } 127 | 128 | this._pushheaderrow = headerrow; 129 | }, 130 | 131 | // return a transformation that will cut columns. 132 | // Accepts a comma separated list of column positions to remove. 133 | // This is 0-indexed. 134 | cut: function(args) { 135 | Transform.call(this, {objectMode: true}); 136 | 137 | var argv = optimist() 138 | .boolean('complement') 139 | .parse(args) 140 | ; 141 | 142 | if (argv._.length < 1) { 143 | throw 'Error: cut requires at least 1 argument.'; 144 | } 145 | 146 | var columns = argv._[0].toString(); 147 | 148 | this._complement = argv.complement; 149 | 150 | this._cols = ShorthandList(columns); 151 | }, 152 | 153 | // transformation that will grep for a pattern 154 | grep: function(args) { 155 | Transform.call(this, {objectMode: true}); 156 | 157 | var argv = optimist() 158 | .options({ 159 | c: {alias: 'columns', string: true}, 160 | i: {alias: 'case-insensitive', boolean: true}, 161 | e: {alias: 'regexp', string: true}, 162 | v: {alias: 'invert-match', boolean: true}, 163 | }) 164 | .string(['columns', 'regexp']) 165 | .parse(args) 166 | ; 167 | 168 | if (argv.c !== undefined) { 169 | this._cols = ShorthandList(argv.c); 170 | } 171 | 172 | var flags = (argv.i) ? 'i' : ''; 173 | var regex = argv.e || argv._[0]; 174 | this._pattern = new RegExp(regex, flags); 175 | 176 | this._invert = argv.v; 177 | 178 | this._pushheaderrow = headerrow; 179 | }, 180 | 181 | // return a transformation that will find and replace all 182 | // occurrences of a string 183 | replace: function(args) { 184 | Transform.call(this, {objectMode: true}); 185 | 186 | var argv = optimist() 187 | .options({ 188 | c: {alias: 'columns', string: true}, 189 | r: {alias: 'regexp', boolean: true}, 190 | }) 191 | .string('columns') 192 | .parse(args) 193 | ; 194 | 195 | if (argv._.length < 1) { 196 | throw 'Error: replace requires at least 1 argument.'; 197 | } 198 | 199 | if (argv.c !== undefined) { 200 | this._cols = ShorthandList(argv.c); 201 | } 202 | 203 | this._find = (argv.r) ? new RegExp(argv._[0], 'g') : argv._[0]; 204 | this._replace = (argv._.length > 1) ? argv._[1] : ''; 205 | 206 | this._pushheaderrow = headerrow; 207 | }, 208 | 209 | // return a transformation that will delete empty rows 210 | strip: function(args) { 211 | Transform.call(this, {objectMode: true}); 212 | 213 | this._pushheaderrow = headerrow; 214 | }, 215 | 216 | // transformation that will delete rows 217 | delete: function(args) { 218 | Transform.call(this, {objectMode: true}); 219 | 220 | var argv = optimist() 221 | .parse(args) 222 | ; 223 | 224 | if (argv._.length < 1) { 225 | throw 'Error: delete requires at least 1 argument.'; 226 | } 227 | 228 | var shorthand = argv._[0].toString(); 229 | this._shorthandlist = ShorthandList(shorthand); 230 | this._index = 0; 231 | 232 | this._pushheaderrow = headerrow; 233 | }, 234 | 235 | map: function(args) { 236 | Transform.call(this, {objectMode: true}); 237 | 238 | var argv = optimist() 239 | .parse(args) 240 | ; 241 | 242 | if (argv._.length < 1) { 243 | throw 'Error: map requires at least 1 argument.'; 244 | } 245 | 246 | this._map_url = argv._[0]; 247 | this._sandbox = new Sandbox(); 248 | 249 | this._pushheaderrow = headerrow; 250 | }, 251 | 252 | outcsv: function(args) { 253 | Transform.call(this, {objectMode: true}); 254 | 255 | var argv = optimist() 256 | .options({ 257 | d: {alias: 'delimiter', default: ',', string: true}, 258 | p: {alias: 'escapechar', default: '\\', string: true}, 259 | q: {alias: 'quotechar', default: '\"', string: true}, 260 | t: {alias: 'tabs', boolean: true}, 261 | S: {alias: 'skipinitialspace', boolean: true}, 262 | }) 263 | .string(['delimiter', 'escapechar', 'quotechar']) 264 | .parse(args) 265 | ; 266 | if (argv.t) { 267 | // tabs switch overrides delimiter opt 268 | argv.d = '\t'; 269 | } 270 | this._options = { 271 | delimiter: argv.d, 272 | escape: argv.p, 273 | quote: argv.q, 274 | }; 275 | this._trimInitialSpace = argv.skipinitialspace; 276 | }, 277 | 278 | // return our HTML transformation 279 | outhtml: function(args) { 280 | Transform.call(this, {objectMode: true}); 281 | 282 | this._initialHtmlSent = false; 283 | }, 284 | }; 285 | 286 | inherits(operators.none, PassThrough); 287 | 288 | inherits(operators.head, Transform); 289 | 290 | operators.head.prototype._transform = function(chunk, encoding, done) { 291 | if (this._pushheaderrow) { 292 | this.push(chunk); 293 | this._pushheaderrow = false; 294 | return done(); 295 | } 296 | 297 | if (this._lines < this._number) { 298 | this._lines += 1; 299 | this.push(chunk); 300 | } else { 301 | this.push(null); 302 | } 303 | 304 | done(); 305 | }; 306 | 307 | inherits(operators.tail, Transform); 308 | 309 | operators.tail.prototype._transform = function(chunk, encoding, done) { 310 | if (this._pushheaderrow) { 311 | this.push(chunk); 312 | this._pushheaderrow = false; 313 | return done(); 314 | } 315 | 316 | if (this._rel == 'end') { 317 | this._fixedqueue.push(chunk); 318 | } else { 319 | if (this._lines < this._number) { 320 | this._lines += 1; 321 | } else { 322 | this.push(chunk); 323 | } 324 | } 325 | done(); 326 | }; 327 | 328 | operators.tail.prototype._flush = function(done) { 329 | if (this._rel == 'end') { 330 | var chunk = this._fixedqueue.shift(); 331 | while (chunk !== undefined) { 332 | this.push(chunk); 333 | chunk = this._fixedqueue.shift(); 334 | } 335 | } 336 | done(); 337 | }; 338 | 339 | inherits(operators.cut, Transform); 340 | 341 | operators.cut.prototype._transform = function(chunk, encoding, done) { 342 | var json = JSON.parse(chunk); 343 | 344 | if (this._expandedCols === undefined) { 345 | this._expandedCols = this._cols.expand(json.row.length); 346 | if (this._complement) { 347 | this._expandedCols = _.difference(_.range(json.row.length), this._expandedCols); 348 | } 349 | } 350 | 351 | _.each(this._expandedCols, function(position) { 352 | delete json.row[position]; 353 | }); 354 | 355 | json.row = _.without(json.row, undefined); 356 | 357 | this.push(JSON.stringify(json)); 358 | 359 | done(); 360 | }; 361 | 362 | inherits(operators.grep, Transform); 363 | 364 | operators.grep.prototype._transform = function(chunk, encoding, done) { 365 | if (this._pushheaderrow) { 366 | this.push(chunk); 367 | this._pushheaderrow = false; 368 | return done(); 369 | } 370 | 371 | var json = JSON.parse(chunk); 372 | 373 | var self = this; 374 | 375 | var expandedCols = (this._cols !== undefined) ? this._cols.expand(json.row.length) : _.range(json.row.length); 376 | var match = _.any(expandedCols, function(idx) { 377 | return self._pattern.test(json.row[idx]); 378 | }); 379 | 380 | if (this._invert) match = !match; 381 | 382 | if (match) this.push(chunk); 383 | 384 | done(); 385 | }; 386 | 387 | inherits(operators.replace, Transform); 388 | 389 | operators.replace.prototype._transform = function(chunk, encoding, done) { 390 | if (this._pushheaderrow) { 391 | this.push(chunk); 392 | this._pushheaderrow = false; 393 | return done(); 394 | } 395 | 396 | var json = JSON.parse(chunk); 397 | 398 | var self = this; 399 | 400 | 401 | var expandedCols; 402 | if (this._cols !== undefined) { 403 | expandedCols = this._cols.expand(json.row.length); 404 | } else { 405 | expandedCols = _.range(json.row.length); 406 | } 407 | _.each(expandedCols, function(idx) { 408 | json.row[idx] = json.row[idx].replace(self._find, self._replace); 409 | }); 410 | 411 | this.push(JSON.stringify(json)); 412 | 413 | done(); 414 | }; 415 | 416 | inherits(operators.strip, Transform); 417 | 418 | operators.strip.prototype._transform = function(chunk, encoding, done) { 419 | if (this._pushheaderrow) { 420 | this.push(chunk); 421 | this._pushheaderrow = false; 422 | return done(); 423 | } 424 | 425 | var json = JSON.parse(chunk); 426 | 427 | var keep = _.some(json.row, function(val) { 428 | return val !== ''; 429 | }); 430 | 431 | if (keep) this.push(chunk); 432 | 433 | done(); 434 | }; 435 | 436 | inherits(operators.delete, Transform); 437 | 438 | operators.delete.prototype._transform = function(chunk, encoding, done) { 439 | if (this._pushheaderrow) { 440 | this.push(chunk); 441 | this._pushheaderrow = false; 442 | return done(); 443 | } 444 | 445 | if(this._shorthandlist === undefined || !this._shorthandlist.includes(this._index)) { 446 | this.push(chunk); 447 | } 448 | 449 | this._index += 1; 450 | done(); 451 | }; 452 | 453 | inherits(operators.map, Transform); 454 | 455 | operators.map.prototype._transform = function(chunk, encoding, done) { 456 | if (this._pushheaderrow) { 457 | this.push(chunk); 458 | this._pushheaderrow = false; 459 | return done(); 460 | } 461 | 462 | function sandbox_run() { 463 | self._sandbox.run(self._map_fn + "transform('" + chunk + "');", function(output) { 464 | var result = output.result; 465 | if (result == 'null') { 466 | return done(); 467 | } 468 | 469 | result = result.slice(1, -1); 470 | try { 471 | JSON.parse(result); 472 | } catch (e) { 473 | self.emit('error', new Error('Error performing map transform.')); 474 | return; 475 | } 476 | self.push(result); 477 | done(); 478 | }); 479 | } 480 | 481 | var self = this; 482 | if (this._map_fn === undefined) { 483 | request(this._map_url, function(error, response) { 484 | if (error !== null) { 485 | self.emit('error', new Error('Error opening map URL.')); 486 | return; 487 | } 488 | self._map_fn = response.body; 489 | sandbox_run(); 490 | }); 491 | } else { 492 | sandbox_run(); 493 | } 494 | }; 495 | 496 | operators.map.prototype._flush = function(done) { 497 | function sandbox_run() { 498 | self._sandbox.run(self._map_fn + "flush();", function(output) { 499 | var result = output.result; 500 | if (result == 'null') { 501 | return done(); 502 | } 503 | 504 | result = result.slice(1, -1); 505 | try { 506 | JSON.parse(result); 507 | } catch (e) { 508 | self.emit('error', new Error('Error performing map flush.')); 509 | return; 510 | } 511 | self.push(result); 512 | done(); 513 | }); 514 | } 515 | 516 | var self = this; 517 | if (this._map_fn === undefined) { 518 | request(this._map_url, function(error, response) { 519 | self._map_fn = response.body; 520 | sandbox_run(); 521 | }); 522 | } else { 523 | sandbox_run(); 524 | } 525 | }; 526 | 527 | inherits(operators.outcsv, Transform); 528 | 529 | operators.outcsv.prototype._transform = function(chunk, encoding, done) { 530 | var self = this; 531 | var row = JSON.parse(chunk).row; 532 | 533 | if (this._trimInitialSpace) { 534 | row = _.each(function(item) { 535 | return item.trimLeft(item); 536 | }); 537 | } 538 | 539 | csv() 540 | .from.array([row]) 541 | .to.string(function(data){ 542 | self.push(data + '\n'); 543 | done(); 544 | }, self._options) 545 | ; 546 | }; 547 | 548 | operators.outcsv.prototype.contentType = function() { 549 | return "text/plain; charset=utf-8"; 550 | }; 551 | 552 | inherits(operators.outhtml, Transform); 553 | 554 | operators.outhtml.prototype._transform = function(chunk, encoding, done) { 555 | var json = JSON.parse(chunk); 556 | var self = this; 557 | 558 | if (!this._initialHtmlSent) { 559 | this.push(''); 560 | this.push(''); 561 | this.push(''); 562 | this.push(''); 563 | this.push(''); 564 | this.push(''); 565 | this.push(''); 566 | if (headerrow) { 567 | this.push(''); 568 | json.row.forEach(function(item) { 569 | self.push(''.replace(/%s/g, item)); 570 | }); 571 | this.push(''); 572 | 573 | this._initialHtmlSent = true; 574 | 575 | return done(); 576 | } else { 577 | this.push(''); 578 | this._initialHtmlSent = true; 579 | } 580 | } 581 | 582 | var index = json.index; 583 | if (headerrow) { 584 | // the header row was passed straight through 585 | // (without incrementing any counters) so we have 586 | // to correct for that here. 587 | index--; 588 | } 589 | 590 | this.push(''.replace(/%s/g, index)); 591 | json.row.forEach(function(item) { 592 | self.push(''); 593 | }); 594 | this.push(''); 595 | 596 | done(); 597 | }; 598 | 599 | operators.outhtml.prototype._flush = function(done) { 600 | this.push(''); 601 | this.push('
%s
%s
' + item + '
'); 602 | this.push(''); 603 | this.push(''); 604 | this.push(''); 605 | this.push(''); 606 | 607 | done(); 608 | }; 609 | 610 | operators.outhtml.prototype.contentType = function() { 611 | return "text/html; charset=utf-8"; 612 | }; 613 | 614 | module.exports = operators; 615 | -------------------------------------------------------------------------------- /lib/shorthandlist.js: -------------------------------------------------------------------------------- 1 | var _ = require('underscore'); 2 | 3 | // class for keeping track of our 4 | // shorthand row and column indexing. 5 | 6 | // constructor mainly just parses all the strings to ints 7 | function ShorthandList(shorthand) { 8 | if (!(this instanceof ShorthandList)) { 9 | return new ShorthandList(shorthand); 10 | } 11 | 12 | var shorthandArr = _.without(shorthand.split(','), ''); 13 | var self = this; 14 | 15 | // generate our shorthand array 16 | this._shorthand = _.map(shorthandArr, function(part) { 17 | if (part.indexOf(':') == -1) { 18 | return parseInt(part); 19 | } else { 20 | var range = part.split(':'); 21 | return _.map(range, function(val, idx) { 22 | var num = parseInt(val); 23 | if (isNaN(num)) { 24 | return undefined; 25 | } 26 | return num; 27 | }); 28 | } 29 | }); 30 | } 31 | 32 | // expand out the shorthand list to an array 33 | ShorthandList.prototype.expand = function(len) { 34 | if (this._expanded !== undefined) return this._expanded; 35 | 36 | var self = this; 37 | this._expanded = _.chain(this._shorthand) 38 | .map(function(part){ 39 | if (!_.isArray(part)) { 40 | return (part < 0) ? [len+part] : [part]; 41 | } else { 42 | part = _.map(part, function(num) { 43 | return (num < 0) ? len+num : num; 44 | }); 45 | if (part[0] === undefined) { 46 | return _.range(part[1] + 1); 47 | } 48 | if (part[1] === undefined) { 49 | return _.range(part[0], len); 50 | } 51 | return _.range(part[0], part[1] + 1); 52 | } 53 | }) 54 | .flatten() 55 | .uniq() 56 | .value(); 57 | 58 | return this._expanded; 59 | }; 60 | 61 | // returns true if the shorthand list includes 62 | // the given index 63 | ShorthandList.prototype.includes = function(idx) { 64 | return _.any(this._shorthand, function(part) { 65 | if (!_.isArray(part)) { 66 | if (idx == part) { 67 | return true; 68 | } 69 | } else { 70 | if (part[0] === undefined) { 71 | if (idx <= part[1]) { 72 | return true; 73 | } 74 | } else if (part[1] === undefined) { 75 | if (idx >= part[0]) { 76 | return true; 77 | } 78 | } else { 79 | if (idx >= part[0] && idx <= part[1]) { 80 | return true; 81 | } 82 | } 83 | } 84 | return false; 85 | }); 86 | }; 87 | 88 | module.exports = ShorthandList; 89 | -------------------------------------------------------------------------------- /lib/stdout.js: -------------------------------------------------------------------------------- 1 | var inherits = require('util').inherits; 2 | var Writable = require('stream').Writable; 3 | 4 | function stdout() { 5 | if (!(this instanceof stdout)) return new stdout(); 6 | Writable.call(this, {objectMode: true}); 7 | } 8 | 9 | inherits(stdout, Writable); 10 | 11 | stdout.prototype._write = function(chunk, encoding, done) { 12 | process.stdout.write(chunk); 13 | done(); 14 | }; 15 | 16 | stdout.prototype.send = function(statuscode, msg) { 17 | process.stderr.write(msg + '\n'); 18 | }; 19 | 20 | module.exports = stdout; 21 | -------------------------------------------------------------------------------- /lib/tools.js: -------------------------------------------------------------------------------- 1 | var _ = require('underscore'); 2 | 3 | var tools = { 4 | optimist: function (){ 5 | delete require.cache[require.resolve('optimist')]; 6 | return require('optimist'); 7 | }, 8 | }; 9 | 10 | module.exports = tools; 11 | -------------------------------------------------------------------------------- /lib/transform.js: -------------------------------------------------------------------------------- 1 | var request = require('request'), 2 | _ = require('underscore'), 3 | es = require('event-stream'); 4 | 5 | var util = require('./util'); 6 | var ops = require('./operators'); 7 | 8 | // Takes a string and turns it into an array of transformations 9 | exports.pipeline = function(pipelineSpec) { 10 | var transformers = _.map(pipelineSpec, function(pipe){ 11 | if (_.has(ops, pipe.operator)) { 12 | var out = new ops[pipe.operator](pipe.options.split(' ')); 13 | // stub on so es.pipeline works 14 | // out.on = function(name, func) {}; 15 | return out; 16 | } else { 17 | var errStr = 'No such operation: ' + pipe.operator; 18 | throw errStr; 19 | } 20 | }); 21 | return transformers; 22 | }; 23 | 24 | exports.pipelineToStream = function(pipelineSpec) { 25 | var transformers = exports.pipeline(pipelineSpec); 26 | return es.pipeline.apply(es.pipeline, transformers); 27 | }; 28 | 29 | // Conduct our transformation 30 | exports.transform = function(response, transformers, stream) { 31 | _.each(transformers, function(next) { 32 | if (next) { 33 | next.on('error', function(err) { 34 | var errStr = err.toString(); 35 | console.log(errStr); 36 | response.write(errStr); 37 | response.end(); 38 | }); 39 | stream = stream.pipe(next); 40 | } 41 | }); 42 | stream.pipe(response); 43 | }; 44 | -------------------------------------------------------------------------------- /lib/util.js: -------------------------------------------------------------------------------- 1 | exports.parseUrl = function(path, query) { 2 | // remove leading&trailing spaces&slashes 3 | var transformStr = path.replace(/(\/|\s)+$/g, ''); 4 | transformStr = transformStr.replace(/^(\/|\s)+/g, ''); 5 | // replace nbsps with spaces 6 | transformStr = transformStr.replace(/ /g, ' '); 7 | 8 | var transform = transformStr.split('/'); 9 | 10 | // 'none' operator is a special case 11 | if (transform.length == 1 && ['', 'none'].indexOf(transform[0]) != -1) { 12 | return [{ 13 | operator: 'none', 14 | options: '' 15 | }]; 16 | } 17 | 18 | transform = transform.map(function(opString) { 19 | var ops = opString.trim().split(' '), 20 | op = ops.shift(); 21 | // deal with vars ... 22 | ops = ops.map(function(arg) { 23 | if (query && arg.length > 0 && arg.charAt(0) == '$') { 24 | return query[arg.substr(1)]; 25 | } 26 | return arg; 27 | }); 28 | return { 29 | operator: op, 30 | options: ops.join(' ') 31 | }; 32 | }); 33 | 34 | // TODO: validate operations 35 | // if (supportedFormats.indexOf(op[0]) != -1) { 36 | 37 | // first operator __must__ be a parser 38 | if (transform[0].operator === 'csv') { 39 | transform[0].operator = 'incsv'; 40 | } else { 41 | transform.unshift({ 42 | operator: 'incsv', 43 | options: '' 44 | }); 45 | } 46 | 47 | var supportedFormats = ['csv', 'html']; 48 | var defaultOutputFormat = 'csv'; 49 | 50 | // last operator __must__ be a renderer 51 | var lastOp = transform[transform.length-1]; 52 | if (supportedFormats.indexOf(lastOp.operator) != -1) { 53 | lastOp.operator = 'out' + lastOp.operator; 54 | } else { 55 | // use the default output format 56 | transform.push({ 57 | operator: 'out' + defaultOutputFormat, 58 | options: '' 59 | }); 60 | } 61 | 62 | return transform; 63 | }; 64 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "datapipes", 3 | "version": "0.3.3", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@ungap/promise-all-settled": { 8 | "version": "1.1.2", 9 | "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", 10 | "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==" 11 | }, 12 | "a-sync-waterfall": { 13 | "version": "1.0.1", 14 | "resolved": "https://registry.npmjs.org/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz", 15 | "integrity": "sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA==" 16 | }, 17 | "accepts": { 18 | "version": "1.3.5", 19 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", 20 | "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", 21 | "requires": { 22 | "mime-types": "~2.1.18", 23 | "negotiator": "0.6.1" 24 | } 25 | }, 26 | "ajv": { 27 | "version": "6.12.6", 28 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", 29 | "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", 30 | "requires": { 31 | "fast-deep-equal": "^3.1.1", 32 | "fast-json-stable-stringify": "^2.0.0", 33 | "json-schema-traverse": "^0.4.1", 34 | "uri-js": "^4.2.2" 35 | } 36 | }, 37 | "ansi-colors": { 38 | "version": "4.1.1", 39 | "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", 40 | "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==" 41 | }, 42 | "ansi-regex": { 43 | "version": "5.0.1", 44 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 45 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" 46 | }, 47 | "ansi-styles": { 48 | "version": "4.3.0", 49 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 50 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 51 | "requires": { 52 | "color-convert": "^2.0.1" 53 | } 54 | }, 55 | "anymatch": { 56 | "version": "3.1.2", 57 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", 58 | "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", 59 | "requires": { 60 | "normalize-path": "^3.0.0", 61 | "picomatch": "^2.0.4" 62 | } 63 | }, 64 | "argparse": { 65 | "version": "2.0.1", 66 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", 67 | "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" 68 | }, 69 | "array-flatten": { 70 | "version": "1.1.1", 71 | "resolved": "http://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 72 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 73 | }, 74 | "asap": { 75 | "version": "2.0.6", 76 | "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", 77 | "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" 78 | }, 79 | "asn1": { 80 | "version": "0.2.6", 81 | "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", 82 | "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", 83 | "requires": { 84 | "safer-buffer": "~2.1.0" 85 | } 86 | }, 87 | "assert-plus": { 88 | "version": "1.0.0", 89 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 90 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 91 | }, 92 | "asynckit": { 93 | "version": "0.4.0", 94 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 95 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" 96 | }, 97 | "aws-sign2": { 98 | "version": "0.7.0", 99 | "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", 100 | "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" 101 | }, 102 | "aws4": { 103 | "version": "1.11.0", 104 | "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", 105 | "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" 106 | }, 107 | "balanced-match": { 108 | "version": "1.0.2", 109 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 110 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" 111 | }, 112 | "bcrypt-pbkdf": { 113 | "version": "1.0.2", 114 | "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", 115 | "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", 116 | "requires": { 117 | "tweetnacl": "^0.14.3" 118 | } 119 | }, 120 | "binary-extensions": { 121 | "version": "2.2.0", 122 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", 123 | "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==" 124 | }, 125 | "body-parser": { 126 | "version": "1.18.2", 127 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", 128 | "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", 129 | "requires": { 130 | "bytes": "3.0.0", 131 | "content-type": "~1.0.4", 132 | "debug": "2.6.9", 133 | "depd": "~1.1.1", 134 | "http-errors": "~1.6.2", 135 | "iconv-lite": "0.4.19", 136 | "on-finished": "~2.3.0", 137 | "qs": "6.5.1", 138 | "raw-body": "2.3.2", 139 | "type-is": "~1.6.15" 140 | } 141 | }, 142 | "brace-expansion": { 143 | "version": "1.1.11", 144 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 145 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 146 | "requires": { 147 | "balanced-match": "^1.0.0", 148 | "concat-map": "0.0.1" 149 | } 150 | }, 151 | "braces": { 152 | "version": "3.0.2", 153 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", 154 | "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", 155 | "requires": { 156 | "fill-range": "^7.0.1" 157 | } 158 | }, 159 | "browser-stdout": { 160 | "version": "1.3.1", 161 | "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", 162 | "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==" 163 | }, 164 | "buffer-from": { 165 | "version": "1.1.2", 166 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", 167 | "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" 168 | }, 169 | "bytes": { 170 | "version": "3.0.0", 171 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", 172 | "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" 173 | }, 174 | "call-bind": { 175 | "version": "1.0.2", 176 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", 177 | "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", 178 | "requires": { 179 | "function-bind": "^1.1.1", 180 | "get-intrinsic": "^1.0.2" 181 | } 182 | }, 183 | "camelcase": { 184 | "version": "6.3.0", 185 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", 186 | "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==" 187 | }, 188 | "caseless": { 189 | "version": "0.12.0", 190 | "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", 191 | "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" 192 | }, 193 | "chalk": { 194 | "version": "4.1.2", 195 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 196 | "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 197 | "requires": { 198 | "ansi-styles": "^4.1.0", 199 | "supports-color": "^7.1.0" 200 | }, 201 | "dependencies": { 202 | "supports-color": { 203 | "version": "7.2.0", 204 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 205 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 206 | "requires": { 207 | "has-flag": "^4.0.0" 208 | } 209 | } 210 | } 211 | }, 212 | "chokidar": { 213 | "version": "3.5.2", 214 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", 215 | "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", 216 | "requires": { 217 | "anymatch": "~3.1.2", 218 | "braces": "~3.0.2", 219 | "fsevents": "~2.3.2", 220 | "glob-parent": "~5.1.2", 221 | "is-binary-path": "~2.1.0", 222 | "is-glob": "~4.0.1", 223 | "normalize-path": "~3.0.0", 224 | "readdirp": "~3.6.0" 225 | } 226 | }, 227 | "cliui": { 228 | "version": "7.0.4", 229 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", 230 | "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", 231 | "requires": { 232 | "string-width": "^4.2.0", 233 | "strip-ansi": "^6.0.0", 234 | "wrap-ansi": "^7.0.0" 235 | } 236 | }, 237 | "color-convert": { 238 | "version": "2.0.1", 239 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 240 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 241 | "requires": { 242 | "color-name": "~1.1.4" 243 | } 244 | }, 245 | "color-name": { 246 | "version": "1.1.4", 247 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 248 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" 249 | }, 250 | "combined-stream": { 251 | "version": "1.0.8", 252 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 253 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 254 | "requires": { 255 | "delayed-stream": "~1.0.0" 256 | } 257 | }, 258 | "commander": { 259 | "version": "5.1.0", 260 | "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", 261 | "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==" 262 | }, 263 | "component-emitter": { 264 | "version": "1.3.0", 265 | "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", 266 | "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" 267 | }, 268 | "concat-map": { 269 | "version": "0.0.1", 270 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 271 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" 272 | }, 273 | "concat-stream": { 274 | "version": "2.0.0", 275 | "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", 276 | "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", 277 | "requires": { 278 | "buffer-from": "^1.0.0", 279 | "inherits": "^2.0.3", 280 | "readable-stream": "^3.0.2", 281 | "typedarray": "^0.0.6" 282 | } 283 | }, 284 | "content-disposition": { 285 | "version": "0.5.2", 286 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", 287 | "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" 288 | }, 289 | "content-type": { 290 | "version": "1.0.4", 291 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 292 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 293 | }, 294 | "cookie": { 295 | "version": "0.3.1", 296 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", 297 | "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" 298 | }, 299 | "cookie-signature": { 300 | "version": "1.0.6", 301 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 302 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 303 | }, 304 | "cookiejar": { 305 | "version": "2.1.3", 306 | "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.3.tgz", 307 | "integrity": "sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==" 308 | }, 309 | "core-util-is": { 310 | "version": "1.0.2", 311 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 312 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 313 | }, 314 | "csv": { 315 | "version": "0.3.7", 316 | "resolved": "http://registry.npmjs.org/csv/-/csv-0.3.7.tgz", 317 | "integrity": "sha1-pPijY/AHLNFVGB5rtJvVjlSXsxw=" 318 | }, 319 | "dashdash": { 320 | "version": "1.14.1", 321 | "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", 322 | "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", 323 | "requires": { 324 | "assert-plus": "^1.0.0" 325 | } 326 | }, 327 | "debug": { 328 | "version": "2.6.9", 329 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 330 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 331 | "requires": { 332 | "ms": "2.0.0" 333 | } 334 | }, 335 | "decamelize": { 336 | "version": "4.0.0", 337 | "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", 338 | "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==" 339 | }, 340 | "delayed-stream": { 341 | "version": "1.0.0", 342 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 343 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" 344 | }, 345 | "depd": { 346 | "version": "1.1.2", 347 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 348 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 349 | }, 350 | "destroy": { 351 | "version": "1.0.4", 352 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 353 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 354 | }, 355 | "dezalgo": { 356 | "version": "1.0.3", 357 | "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz", 358 | "integrity": "sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY=", 359 | "requires": { 360 | "asap": "^2.0.0", 361 | "wrappy": "1" 362 | } 363 | }, 364 | "diff": { 365 | "version": "5.0.0", 366 | "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", 367 | "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==" 368 | }, 369 | "duplexer": { 370 | "version": "0.1.1", 371 | "resolved": "http://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", 372 | "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=" 373 | }, 374 | "ecc-jsbn": { 375 | "version": "0.1.2", 376 | "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", 377 | "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", 378 | "requires": { 379 | "jsbn": "~0.1.0", 380 | "safer-buffer": "^2.1.0" 381 | } 382 | }, 383 | "ee-first": { 384 | "version": "1.1.1", 385 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 386 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 387 | }, 388 | "emoji-regex": { 389 | "version": "8.0.0", 390 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 391 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" 392 | }, 393 | "encodeurl": { 394 | "version": "1.0.2", 395 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 396 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 397 | }, 398 | "escalade": { 399 | "version": "3.1.1", 400 | "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", 401 | "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" 402 | }, 403 | "escape-html": { 404 | "version": "1.0.3", 405 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 406 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 407 | }, 408 | "escape-string-regexp": { 409 | "version": "4.0.0", 410 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", 411 | "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" 412 | }, 413 | "etag": { 414 | "version": "1.8.1", 415 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 416 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 417 | }, 418 | "event-stream": { 419 | "version": "3.3.4", 420 | "resolved": "http://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", 421 | "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", 422 | "requires": { 423 | "duplexer": "~0.1.1", 424 | "from": "~0", 425 | "map-stream": "~0.1.0", 426 | "pause-stream": "0.0.11", 427 | "split": "0.3", 428 | "stream-combiner": "~0.0.4", 429 | "through": "~2.3.1" 430 | } 431 | }, 432 | "express": { 433 | "version": "4.16.2", 434 | "resolved": "https://registry.npmjs.org/express/-/express-4.16.2.tgz", 435 | "integrity": "sha1-41xt/i1kt9ygpc1PIXgb4ymeB2w=", 436 | "requires": { 437 | "accepts": "~1.3.4", 438 | "array-flatten": "1.1.1", 439 | "body-parser": "1.18.2", 440 | "content-disposition": "0.5.2", 441 | "content-type": "~1.0.4", 442 | "cookie": "0.3.1", 443 | "cookie-signature": "1.0.6", 444 | "debug": "2.6.9", 445 | "depd": "~1.1.1", 446 | "encodeurl": "~1.0.1", 447 | "escape-html": "~1.0.3", 448 | "etag": "~1.8.1", 449 | "finalhandler": "1.1.0", 450 | "fresh": "0.5.2", 451 | "merge-descriptors": "1.0.1", 452 | "methods": "~1.1.2", 453 | "on-finished": "~2.3.0", 454 | "parseurl": "~1.3.2", 455 | "path-to-regexp": "0.1.7", 456 | "proxy-addr": "~2.0.2", 457 | "qs": "6.5.1", 458 | "range-parser": "~1.2.0", 459 | "safe-buffer": "5.1.1", 460 | "send": "0.16.1", 461 | "serve-static": "1.13.1", 462 | "setprototypeof": "1.1.0", 463 | "statuses": "~1.3.1", 464 | "type-is": "~1.6.15", 465 | "utils-merge": "1.0.1", 466 | "vary": "~1.1.2" 467 | } 468 | }, 469 | "extend": { 470 | "version": "3.0.2", 471 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", 472 | "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" 473 | }, 474 | "extsprintf": { 475 | "version": "1.3.0", 476 | "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", 477 | "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" 478 | }, 479 | "fast-deep-equal": { 480 | "version": "3.1.3", 481 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", 482 | "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" 483 | }, 484 | "fast-json-stable-stringify": { 485 | "version": "2.1.0", 486 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", 487 | "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" 488 | }, 489 | "fast-safe-stringify": { 490 | "version": "2.1.1", 491 | "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", 492 | "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" 493 | }, 494 | "fill-range": { 495 | "version": "7.0.1", 496 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", 497 | "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", 498 | "requires": { 499 | "to-regex-range": "^5.0.1" 500 | } 501 | }, 502 | "finalhandler": { 503 | "version": "1.1.0", 504 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.0.tgz", 505 | "integrity": "sha1-zgtoVbRYU+eRsvzGgARtiCU91/U=", 506 | "requires": { 507 | "debug": "2.6.9", 508 | "encodeurl": "~1.0.1", 509 | "escape-html": "~1.0.3", 510 | "on-finished": "~2.3.0", 511 | "parseurl": "~1.3.2", 512 | "statuses": "~1.3.1", 513 | "unpipe": "~1.0.0" 514 | } 515 | }, 516 | "find-up": { 517 | "version": "5.0.0", 518 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", 519 | "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", 520 | "requires": { 521 | "locate-path": "^6.0.0", 522 | "path-exists": "^4.0.0" 523 | } 524 | }, 525 | "flat": { 526 | "version": "5.0.2", 527 | "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", 528 | "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==" 529 | }, 530 | "forever-agent": { 531 | "version": "0.6.1", 532 | "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", 533 | "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" 534 | }, 535 | "form-data": { 536 | "version": "2.3.3", 537 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", 538 | "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", 539 | "requires": { 540 | "asynckit": "^0.4.0", 541 | "combined-stream": "^1.0.6", 542 | "mime-types": "^2.1.12" 543 | } 544 | }, 545 | "formidable": { 546 | "version": "2.0.1", 547 | "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.0.1.tgz", 548 | "integrity": "sha512-rjTMNbp2BpfQShhFbR3Ruk3qk2y9jKpvMW78nJgx8QKtxjDVrwbZG+wvDOmVbifHyOUOQJXxqEy6r0faRrPzTQ==", 549 | "requires": { 550 | "dezalgo": "1.0.3", 551 | "hexoid": "1.0.0", 552 | "once": "1.4.0", 553 | "qs": "6.9.3" 554 | }, 555 | "dependencies": { 556 | "qs": { 557 | "version": "6.9.3", 558 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.3.tgz", 559 | "integrity": "sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw==" 560 | } 561 | } 562 | }, 563 | "forwarded": { 564 | "version": "0.1.2", 565 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", 566 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" 567 | }, 568 | "fresh": { 569 | "version": "0.5.2", 570 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 571 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 572 | }, 573 | "from": { 574 | "version": "0.1.7", 575 | "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", 576 | "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=" 577 | }, 578 | "fs.realpath": { 579 | "version": "1.0.0", 580 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 581 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" 582 | }, 583 | "fsevents": { 584 | "version": "2.3.2", 585 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", 586 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", 587 | "optional": true 588 | }, 589 | "function-bind": { 590 | "version": "1.1.1", 591 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 592 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" 593 | }, 594 | "get-caller-file": { 595 | "version": "2.0.5", 596 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", 597 | "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" 598 | }, 599 | "get-intrinsic": { 600 | "version": "1.1.1", 601 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", 602 | "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", 603 | "requires": { 604 | "function-bind": "^1.1.1", 605 | "has": "^1.0.3", 606 | "has-symbols": "^1.0.1" 607 | } 608 | }, 609 | "getpass": { 610 | "version": "0.1.7", 611 | "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", 612 | "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", 613 | "requires": { 614 | "assert-plus": "^1.0.0" 615 | } 616 | }, 617 | "glob": { 618 | "version": "7.1.7", 619 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", 620 | "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", 621 | "requires": { 622 | "fs.realpath": "^1.0.0", 623 | "inflight": "^1.0.4", 624 | "inherits": "2", 625 | "minimatch": "^3.0.4", 626 | "once": "^1.3.0", 627 | "path-is-absolute": "^1.0.0" 628 | } 629 | }, 630 | "glob-parent": { 631 | "version": "5.1.2", 632 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 633 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 634 | "requires": { 635 | "is-glob": "^4.0.1" 636 | } 637 | }, 638 | "growl": { 639 | "version": "1.10.5", 640 | "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", 641 | "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==" 642 | }, 643 | "har-schema": { 644 | "version": "2.0.0", 645 | "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", 646 | "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" 647 | }, 648 | "har-validator": { 649 | "version": "5.1.5", 650 | "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", 651 | "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", 652 | "requires": { 653 | "ajv": "^6.12.3", 654 | "har-schema": "^2.0.0" 655 | } 656 | }, 657 | "has": { 658 | "version": "1.0.3", 659 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 660 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 661 | "requires": { 662 | "function-bind": "^1.1.1" 663 | } 664 | }, 665 | "has-flag": { 666 | "version": "4.0.0", 667 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 668 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" 669 | }, 670 | "has-symbols": { 671 | "version": "1.0.2", 672 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", 673 | "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==" 674 | }, 675 | "he": { 676 | "version": "1.2.0", 677 | "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", 678 | "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" 679 | }, 680 | "hexoid": { 681 | "version": "1.0.0", 682 | "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", 683 | "integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==" 684 | }, 685 | "http-errors": { 686 | "version": "1.6.3", 687 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", 688 | "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", 689 | "requires": { 690 | "depd": "~1.1.2", 691 | "inherits": "2.0.3", 692 | "setprototypeof": "1.1.0", 693 | "statuses": ">= 1.4.0 < 2" 694 | }, 695 | "dependencies": { 696 | "statuses": { 697 | "version": "1.5.0", 698 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 699 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" 700 | } 701 | } 702 | }, 703 | "http-signature": { 704 | "version": "1.2.0", 705 | "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", 706 | "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", 707 | "requires": { 708 | "assert-plus": "^1.0.0", 709 | "jsprim": "^1.2.2", 710 | "sshpk": "^1.7.0" 711 | } 712 | }, 713 | "iconv-lite": { 714 | "version": "0.4.19", 715 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", 716 | "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" 717 | }, 718 | "inflight": { 719 | "version": "1.0.6", 720 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 721 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 722 | "requires": { 723 | "once": "^1.3.0", 724 | "wrappy": "1" 725 | } 726 | }, 727 | "inherits": { 728 | "version": "2.0.3", 729 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 730 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 731 | }, 732 | "ipaddr.js": { 733 | "version": "1.8.0", 734 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz", 735 | "integrity": "sha1-6qM9bd16zo9/b+DJygRA5wZzix4=" 736 | }, 737 | "is-binary-path": { 738 | "version": "2.1.0", 739 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", 740 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", 741 | "requires": { 742 | "binary-extensions": "^2.0.0" 743 | } 744 | }, 745 | "is-extglob": { 746 | "version": "2.1.1", 747 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 748 | "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" 749 | }, 750 | "is-fullwidth-code-point": { 751 | "version": "3.0.0", 752 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 753 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" 754 | }, 755 | "is-glob": { 756 | "version": "4.0.3", 757 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 758 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 759 | "requires": { 760 | "is-extglob": "^2.1.1" 761 | } 762 | }, 763 | "is-number": { 764 | "version": "7.0.0", 765 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 766 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" 767 | }, 768 | "is-plain-obj": { 769 | "version": "2.1.0", 770 | "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", 771 | "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==" 772 | }, 773 | "is-typedarray": { 774 | "version": "1.0.0", 775 | "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", 776 | "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" 777 | }, 778 | "is-unicode-supported": { 779 | "version": "0.1.0", 780 | "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", 781 | "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==" 782 | }, 783 | "isexe": { 784 | "version": "2.0.0", 785 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 786 | "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" 787 | }, 788 | "isstream": { 789 | "version": "0.1.2", 790 | "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", 791 | "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" 792 | }, 793 | "js-yaml": { 794 | "version": "4.1.0", 795 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", 796 | "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", 797 | "requires": { 798 | "argparse": "^2.0.1" 799 | } 800 | }, 801 | "jsbn": { 802 | "version": "0.1.1", 803 | "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", 804 | "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" 805 | }, 806 | "json-schema": { 807 | "version": "0.4.0", 808 | "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", 809 | "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" 810 | }, 811 | "json-schema-traverse": { 812 | "version": "0.4.1", 813 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 814 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" 815 | }, 816 | "json-stringify-safe": { 817 | "version": "5.0.1", 818 | "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", 819 | "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" 820 | }, 821 | "jsprim": { 822 | "version": "1.4.2", 823 | "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", 824 | "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", 825 | "requires": { 826 | "assert-plus": "1.0.0", 827 | "extsprintf": "1.3.0", 828 | "json-schema": "0.4.0", 829 | "verror": "1.10.0" 830 | } 831 | }, 832 | "locate-path": { 833 | "version": "6.0.0", 834 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", 835 | "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", 836 | "requires": { 837 | "p-locate": "^5.0.0" 838 | } 839 | }, 840 | "log-symbols": { 841 | "version": "4.1.0", 842 | "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", 843 | "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", 844 | "requires": { 845 | "chalk": "^4.1.0", 846 | "is-unicode-supported": "^0.1.0" 847 | } 848 | }, 849 | "lru-cache": { 850 | "version": "6.0.0", 851 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", 852 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", 853 | "requires": { 854 | "yallist": "^4.0.0" 855 | } 856 | }, 857 | "map-stream": { 858 | "version": "0.1.0", 859 | "resolved": "http://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", 860 | "integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=" 861 | }, 862 | "marked": { 863 | "version": "4.0.10", 864 | "resolved": "https://registry.npmjs.org/marked/-/marked-4.0.10.tgz", 865 | "integrity": "sha512-+QvuFj0nGgO970fySghXGmuw+Fd0gD2x3+MqCWLIPf5oxdv1Ka6b2q+z9RP01P/IaKPMEramy+7cNy/Lw8c3hw==" 866 | }, 867 | "media-typer": { 868 | "version": "0.3.0", 869 | "resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 870 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 871 | }, 872 | "merge-descriptors": { 873 | "version": "1.0.1", 874 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 875 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 876 | }, 877 | "methods": { 878 | "version": "1.1.2", 879 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 880 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 881 | }, 882 | "mime": { 883 | "version": "1.4.1", 884 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", 885 | "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" 886 | }, 887 | "mime-db": { 888 | "version": "1.37.0", 889 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", 890 | "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==" 891 | }, 892 | "mime-types": { 893 | "version": "2.1.21", 894 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", 895 | "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", 896 | "requires": { 897 | "mime-db": "~1.37.0" 898 | } 899 | }, 900 | "minimatch": { 901 | "version": "3.0.4", 902 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 903 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 904 | "requires": { 905 | "brace-expansion": "^1.1.7" 906 | } 907 | }, 908 | "minimist": { 909 | "version": "0.0.10", 910 | "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", 911 | "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=" 912 | }, 913 | "mocha": { 914 | "version": "9.1.4", 915 | "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.1.4.tgz", 916 | "integrity": "sha512-+q2aV5VlJZuLgCWoBvGI5zEwPF9eEI0kr/sAA9Jm4xMND7RfIEyF8JE7C0JIg8WXRG+P1sdIAb5ccoHPlXLzcw==", 917 | "requires": { 918 | "@ungap/promise-all-settled": "1.1.2", 919 | "ansi-colors": "4.1.1", 920 | "browser-stdout": "1.3.1", 921 | "chokidar": "3.5.2", 922 | "debug": "4.3.2", 923 | "diff": "5.0.0", 924 | "escape-string-regexp": "4.0.0", 925 | "find-up": "5.0.0", 926 | "glob": "7.1.7", 927 | "growl": "1.10.5", 928 | "he": "1.2.0", 929 | "js-yaml": "4.1.0", 930 | "log-symbols": "4.1.0", 931 | "minimatch": "3.0.4", 932 | "ms": "2.1.3", 933 | "nanoid": "3.1.25", 934 | "serialize-javascript": "6.0.0", 935 | "strip-json-comments": "3.1.1", 936 | "supports-color": "8.1.1", 937 | "which": "2.0.2", 938 | "workerpool": "6.1.5", 939 | "yargs": "16.2.0", 940 | "yargs-parser": "20.2.4", 941 | "yargs-unparser": "2.0.0" 942 | }, 943 | "dependencies": { 944 | "debug": { 945 | "version": "4.3.2", 946 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", 947 | "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", 948 | "requires": { 949 | "ms": "2.1.2" 950 | }, 951 | "dependencies": { 952 | "ms": { 953 | "version": "2.1.2", 954 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 955 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 956 | } 957 | } 958 | }, 959 | "ms": { 960 | "version": "2.1.3", 961 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 962 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 963 | } 964 | } 965 | }, 966 | "ms": { 967 | "version": "2.0.0", 968 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 969 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 970 | }, 971 | "nanoid": { 972 | "version": "3.1.25", 973 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.25.tgz", 974 | "integrity": "sha512-rdwtIXaXCLFAQbnfqDRnI6jaRHp9fTcYBjtFKE8eezcZ7LuLjhUaQGNeMXf1HmRoCH32CLz6XwX0TtxEOS/A3Q==" 975 | }, 976 | "negotiator": { 977 | "version": "0.6.1", 978 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", 979 | "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" 980 | }, 981 | "normalize-path": { 982 | "version": "3.0.0", 983 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", 984 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" 985 | }, 986 | "nunjucks": { 987 | "version": "3.2.3", 988 | "resolved": "https://registry.npmjs.org/nunjucks/-/nunjucks-3.2.3.tgz", 989 | "integrity": "sha512-psb6xjLj47+fE76JdZwskvwG4MYsQKXUtMsPh6U0YMvmyjRtKRFcxnlXGWglNybtNTNVmGdp94K62/+NjF5FDQ==", 990 | "requires": { 991 | "a-sync-waterfall": "^1.0.0", 992 | "asap": "^2.0.3", 993 | "commander": "^5.1.0" 994 | } 995 | }, 996 | "oauth-sign": { 997 | "version": "0.9.0", 998 | "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", 999 | "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" 1000 | }, 1001 | "object-inspect": { 1002 | "version": "1.12.0", 1003 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", 1004 | "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==" 1005 | }, 1006 | "on-finished": { 1007 | "version": "2.3.0", 1008 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 1009 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 1010 | "requires": { 1011 | "ee-first": "1.1.1" 1012 | } 1013 | }, 1014 | "once": { 1015 | "version": "1.4.0", 1016 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1017 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 1018 | "requires": { 1019 | "wrappy": "1" 1020 | } 1021 | }, 1022 | "optimist": { 1023 | "version": "0.6.1", 1024 | "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", 1025 | "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", 1026 | "requires": { 1027 | "minimist": "~0.0.1", 1028 | "wordwrap": "~0.0.2" 1029 | } 1030 | }, 1031 | "p-limit": { 1032 | "version": "3.1.0", 1033 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", 1034 | "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", 1035 | "requires": { 1036 | "yocto-queue": "^0.1.0" 1037 | } 1038 | }, 1039 | "p-locate": { 1040 | "version": "5.0.0", 1041 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", 1042 | "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", 1043 | "requires": { 1044 | "p-limit": "^3.0.2" 1045 | } 1046 | }, 1047 | "parseurl": { 1048 | "version": "1.3.2", 1049 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", 1050 | "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" 1051 | }, 1052 | "path-exists": { 1053 | "version": "4.0.0", 1054 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", 1055 | "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" 1056 | }, 1057 | "path-is-absolute": { 1058 | "version": "1.0.1", 1059 | "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 1060 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" 1061 | }, 1062 | "path-to-regexp": { 1063 | "version": "0.1.7", 1064 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 1065 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 1066 | }, 1067 | "pause-stream": { 1068 | "version": "0.0.11", 1069 | "resolved": "http://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", 1070 | "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", 1071 | "requires": { 1072 | "through": "~2.3" 1073 | } 1074 | }, 1075 | "performance-now": { 1076 | "version": "2.1.0", 1077 | "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", 1078 | "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" 1079 | }, 1080 | "picomatch": { 1081 | "version": "2.3.1", 1082 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 1083 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" 1084 | }, 1085 | "proxy-addr": { 1086 | "version": "2.0.4", 1087 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz", 1088 | "integrity": "sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA==", 1089 | "requires": { 1090 | "forwarded": "~0.1.2", 1091 | "ipaddr.js": "1.8.0" 1092 | } 1093 | }, 1094 | "psl": { 1095 | "version": "1.8.0", 1096 | "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", 1097 | "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" 1098 | }, 1099 | "punycode": { 1100 | "version": "2.1.1", 1101 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 1102 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" 1103 | }, 1104 | "qs": { 1105 | "version": "6.5.1", 1106 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", 1107 | "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" 1108 | }, 1109 | "randombytes": { 1110 | "version": "2.1.0", 1111 | "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", 1112 | "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", 1113 | "requires": { 1114 | "safe-buffer": "^5.1.0" 1115 | } 1116 | }, 1117 | "range-parser": { 1118 | "version": "1.2.0", 1119 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", 1120 | "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" 1121 | }, 1122 | "raw-body": { 1123 | "version": "2.3.2", 1124 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", 1125 | "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", 1126 | "requires": { 1127 | "bytes": "3.0.0", 1128 | "http-errors": "1.6.2", 1129 | "iconv-lite": "0.4.19", 1130 | "unpipe": "1.0.0" 1131 | }, 1132 | "dependencies": { 1133 | "depd": { 1134 | "version": "1.1.1", 1135 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", 1136 | "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" 1137 | }, 1138 | "http-errors": { 1139 | "version": "1.6.2", 1140 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", 1141 | "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", 1142 | "requires": { 1143 | "depd": "1.1.1", 1144 | "inherits": "2.0.3", 1145 | "setprototypeof": "1.0.3", 1146 | "statuses": ">= 1.3.1 < 2" 1147 | } 1148 | }, 1149 | "setprototypeof": { 1150 | "version": "1.0.3", 1151 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", 1152 | "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" 1153 | } 1154 | } 1155 | }, 1156 | "readable-stream": { 1157 | "version": "3.6.0", 1158 | "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", 1159 | "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", 1160 | "requires": { 1161 | "inherits": "^2.0.3", 1162 | "string_decoder": "^1.1.1", 1163 | "util-deprecate": "^1.0.1" 1164 | } 1165 | }, 1166 | "readdirp": { 1167 | "version": "3.6.0", 1168 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", 1169 | "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", 1170 | "requires": { 1171 | "picomatch": "^2.2.1" 1172 | } 1173 | }, 1174 | "request": { 1175 | "version": "2.88.2", 1176 | "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", 1177 | "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", 1178 | "requires": { 1179 | "aws-sign2": "~0.7.0", 1180 | "aws4": "^1.8.0", 1181 | "caseless": "~0.12.0", 1182 | "combined-stream": "~1.0.6", 1183 | "extend": "~3.0.2", 1184 | "forever-agent": "~0.6.1", 1185 | "form-data": "~2.3.2", 1186 | "har-validator": "~5.1.3", 1187 | "http-signature": "~1.2.0", 1188 | "is-typedarray": "~1.0.0", 1189 | "isstream": "~0.1.2", 1190 | "json-stringify-safe": "~5.0.1", 1191 | "mime-types": "~2.1.19", 1192 | "oauth-sign": "~0.9.0", 1193 | "performance-now": "^2.1.0", 1194 | "qs": "~6.5.2", 1195 | "safe-buffer": "^5.1.2", 1196 | "tough-cookie": "~2.5.0", 1197 | "tunnel-agent": "^0.6.0", 1198 | "uuid": "^3.3.2" 1199 | }, 1200 | "dependencies": { 1201 | "qs": { 1202 | "version": "6.5.3", 1203 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", 1204 | "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==" 1205 | }, 1206 | "safe-buffer": { 1207 | "version": "5.2.1", 1208 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 1209 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 1210 | } 1211 | } 1212 | }, 1213 | "require-directory": { 1214 | "version": "2.1.1", 1215 | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", 1216 | "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" 1217 | }, 1218 | "safe-buffer": { 1219 | "version": "5.1.1", 1220 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", 1221 | "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" 1222 | }, 1223 | "safer-buffer": { 1224 | "version": "2.1.2", 1225 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 1226 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 1227 | }, 1228 | "sandbox": { 1229 | "version": "0.8.6", 1230 | "resolved": "https://registry.npmjs.org/sandbox/-/sandbox-0.8.6.tgz", 1231 | "integrity": "sha1-0WnxhRNgTjJ8m/HMOYgbC+xO17Y=" 1232 | }, 1233 | "semver": { 1234 | "version": "7.3.5", 1235 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", 1236 | "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", 1237 | "requires": { 1238 | "lru-cache": "^6.0.0" 1239 | } 1240 | }, 1241 | "send": { 1242 | "version": "0.16.1", 1243 | "resolved": "https://registry.npmjs.org/send/-/send-0.16.1.tgz", 1244 | "integrity": "sha512-ElCLJdJIKPk6ux/Hocwhk7NFHpI3pVm/IZOYWqUmoxcgeyM+MpxHHKhb8QmlJDX1pU6WrgaHBkVNm73Sv7uc2A==", 1245 | "requires": { 1246 | "debug": "2.6.9", 1247 | "depd": "~1.1.1", 1248 | "destroy": "~1.0.4", 1249 | "encodeurl": "~1.0.1", 1250 | "escape-html": "~1.0.3", 1251 | "etag": "~1.8.1", 1252 | "fresh": "0.5.2", 1253 | "http-errors": "~1.6.2", 1254 | "mime": "1.4.1", 1255 | "ms": "2.0.0", 1256 | "on-finished": "~2.3.0", 1257 | "range-parser": "~1.2.0", 1258 | "statuses": "~1.3.1" 1259 | } 1260 | }, 1261 | "serialize-javascript": { 1262 | "version": "6.0.0", 1263 | "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", 1264 | "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", 1265 | "requires": { 1266 | "randombytes": "^2.1.0" 1267 | } 1268 | }, 1269 | "serve-static": { 1270 | "version": "1.13.1", 1271 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.1.tgz", 1272 | "integrity": "sha512-hSMUZrsPa/I09VYFJwa627JJkNs0NrfL1Uzuup+GqHfToR2KcsXFymXSV90hoyw3M+msjFuQly+YzIH/q0MGlQ==", 1273 | "requires": { 1274 | "encodeurl": "~1.0.1", 1275 | "escape-html": "~1.0.3", 1276 | "parseurl": "~1.3.2", 1277 | "send": "0.16.1" 1278 | } 1279 | }, 1280 | "setprototypeof": { 1281 | "version": "1.1.0", 1282 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", 1283 | "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" 1284 | }, 1285 | "side-channel": { 1286 | "version": "1.0.4", 1287 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", 1288 | "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", 1289 | "requires": { 1290 | "call-bind": "^1.0.0", 1291 | "get-intrinsic": "^1.0.2", 1292 | "object-inspect": "^1.9.0" 1293 | } 1294 | }, 1295 | "split": { 1296 | "version": "0.3.3", 1297 | "resolved": "http://registry.npmjs.org/split/-/split-0.3.3.tgz", 1298 | "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=", 1299 | "requires": { 1300 | "through": "2" 1301 | } 1302 | }, 1303 | "sshpk": { 1304 | "version": "1.17.0", 1305 | "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", 1306 | "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", 1307 | "requires": { 1308 | "asn1": "~0.2.3", 1309 | "assert-plus": "^1.0.0", 1310 | "bcrypt-pbkdf": "^1.0.0", 1311 | "dashdash": "^1.12.0", 1312 | "ecc-jsbn": "~0.1.1", 1313 | "getpass": "^0.1.1", 1314 | "jsbn": "~0.1.0", 1315 | "safer-buffer": "^2.0.2", 1316 | "tweetnacl": "~0.14.0" 1317 | } 1318 | }, 1319 | "statuses": { 1320 | "version": "1.3.1", 1321 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", 1322 | "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" 1323 | }, 1324 | "stream-combiner": { 1325 | "version": "0.0.4", 1326 | "resolved": "http://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", 1327 | "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=", 1328 | "requires": { 1329 | "duplexer": "~0.1.1" 1330 | } 1331 | }, 1332 | "string-width": { 1333 | "version": "4.2.3", 1334 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 1335 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 1336 | "requires": { 1337 | "emoji-regex": "^8.0.0", 1338 | "is-fullwidth-code-point": "^3.0.0", 1339 | "strip-ansi": "^6.0.1" 1340 | } 1341 | }, 1342 | "string_decoder": { 1343 | "version": "1.3.0", 1344 | "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", 1345 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 1346 | "requires": { 1347 | "safe-buffer": "~5.2.0" 1348 | }, 1349 | "dependencies": { 1350 | "safe-buffer": { 1351 | "version": "5.2.1", 1352 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 1353 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 1354 | } 1355 | } 1356 | }, 1357 | "strip-ansi": { 1358 | "version": "6.0.1", 1359 | "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 1360 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 1361 | "requires": { 1362 | "ansi-regex": "^5.0.1" 1363 | } 1364 | }, 1365 | "strip-json-comments": { 1366 | "version": "3.1.1", 1367 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", 1368 | "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==" 1369 | }, 1370 | "superagent": { 1371 | "version": "7.0.2", 1372 | "resolved": "https://registry.npmjs.org/superagent/-/superagent-7.0.2.tgz", 1373 | "integrity": "sha512-2Kx35bZxLLJMBKtuXezxvD0aZQ7l923VwoCn7EtUx+aFxdG7co7PeRIddfrNtvvMuGaLZXA0mKzX+yWRhjrJ7A==", 1374 | "requires": { 1375 | "component-emitter": "^1.3.0", 1376 | "cookiejar": "^2.1.3", 1377 | "debug": "^4.3.3", 1378 | "fast-safe-stringify": "^2.1.1", 1379 | "form-data": "^4.0.0", 1380 | "formidable": "^2.0.1", 1381 | "methods": "^1.1.2", 1382 | "mime": "^2.5.0", 1383 | "qs": "^6.10.1", 1384 | "readable-stream": "^3.6.0", 1385 | "semver": "^7.3.5" 1386 | }, 1387 | "dependencies": { 1388 | "debug": { 1389 | "version": "4.3.3", 1390 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", 1391 | "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", 1392 | "requires": { 1393 | "ms": "2.1.2" 1394 | } 1395 | }, 1396 | "form-data": { 1397 | "version": "4.0.0", 1398 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", 1399 | "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", 1400 | "requires": { 1401 | "asynckit": "^0.4.0", 1402 | "combined-stream": "^1.0.8", 1403 | "mime-types": "^2.1.12" 1404 | } 1405 | }, 1406 | "mime": { 1407 | "version": "2.6.0", 1408 | "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", 1409 | "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==" 1410 | }, 1411 | "ms": { 1412 | "version": "2.1.2", 1413 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 1414 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 1415 | }, 1416 | "qs": { 1417 | "version": "6.10.3", 1418 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", 1419 | "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", 1420 | "requires": { 1421 | "side-channel": "^1.0.4" 1422 | } 1423 | } 1424 | } 1425 | }, 1426 | "supertest": { 1427 | "version": "6.2.1", 1428 | "resolved": "https://registry.npmjs.org/supertest/-/supertest-6.2.1.tgz", 1429 | "integrity": "sha512-2kBKhfZgnPLmjpzB0n7A2ZnEAWTaLXq4bn3EEVY9w8rUpLyIlSusqKKvWA1Cav7hxXBnXGpxBsSeOHj5wQGe1Q==", 1430 | "requires": { 1431 | "methods": "^1.1.2", 1432 | "superagent": "^7.0.2" 1433 | } 1434 | }, 1435 | "supports-color": { 1436 | "version": "8.1.1", 1437 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", 1438 | "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", 1439 | "requires": { 1440 | "has-flag": "^4.0.0" 1441 | } 1442 | }, 1443 | "through": { 1444 | "version": "2.3.8", 1445 | "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz", 1446 | "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" 1447 | }, 1448 | "to-regex-range": { 1449 | "version": "5.0.1", 1450 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 1451 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 1452 | "requires": { 1453 | "is-number": "^7.0.0" 1454 | } 1455 | }, 1456 | "tough-cookie": { 1457 | "version": "2.5.0", 1458 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", 1459 | "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", 1460 | "requires": { 1461 | "psl": "^1.1.28", 1462 | "punycode": "^2.1.1" 1463 | } 1464 | }, 1465 | "tunnel-agent": { 1466 | "version": "0.6.0", 1467 | "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", 1468 | "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", 1469 | "requires": { 1470 | "safe-buffer": "^5.0.1" 1471 | } 1472 | }, 1473 | "tweetnacl": { 1474 | "version": "0.14.5", 1475 | "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", 1476 | "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" 1477 | }, 1478 | "type-is": { 1479 | "version": "1.6.16", 1480 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", 1481 | "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", 1482 | "requires": { 1483 | "media-typer": "0.3.0", 1484 | "mime-types": "~2.1.18" 1485 | } 1486 | }, 1487 | "typedarray": { 1488 | "version": "0.0.6", 1489 | "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", 1490 | "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" 1491 | }, 1492 | "underscore": { 1493 | "version": "1.13.2", 1494 | "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.2.tgz", 1495 | "integrity": "sha512-ekY1NhRzq0B08g4bGuX4wd2jZx5GnKz6mKSqFL4nqBlfyMGiG10gDFhDTMEfYmDL6Jy0FUIZp7wiRB+0BP7J2g==" 1496 | }, 1497 | "unpipe": { 1498 | "version": "1.0.0", 1499 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 1500 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 1501 | }, 1502 | "uri-js": { 1503 | "version": "4.4.1", 1504 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", 1505 | "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", 1506 | "requires": { 1507 | "punycode": "^2.1.0" 1508 | } 1509 | }, 1510 | "util-deprecate": { 1511 | "version": "1.0.2", 1512 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 1513 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 1514 | }, 1515 | "utils-merge": { 1516 | "version": "1.0.1", 1517 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 1518 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 1519 | }, 1520 | "uuid": { 1521 | "version": "3.4.0", 1522 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", 1523 | "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" 1524 | }, 1525 | "vary": { 1526 | "version": "1.1.2", 1527 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 1528 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 1529 | }, 1530 | "verror": { 1531 | "version": "1.10.0", 1532 | "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", 1533 | "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", 1534 | "requires": { 1535 | "assert-plus": "^1.0.0", 1536 | "core-util-is": "1.0.2", 1537 | "extsprintf": "^1.2.0" 1538 | } 1539 | }, 1540 | "which": { 1541 | "version": "2.0.2", 1542 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 1543 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 1544 | "requires": { 1545 | "isexe": "^2.0.0" 1546 | } 1547 | }, 1548 | "wordwrap": { 1549 | "version": "0.0.3", 1550 | "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", 1551 | "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" 1552 | }, 1553 | "workerpool": { 1554 | "version": "6.1.5", 1555 | "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.5.tgz", 1556 | "integrity": "sha512-XdKkCK0Zqc6w3iTxLckiuJ81tiD/o5rBE/m+nXpRCB+/Sq4DqkfXZ/x0jW02DG1tGsfUGXbTJyZDP+eu67haSw==" 1557 | }, 1558 | "wrap-ansi": { 1559 | "version": "7.0.0", 1560 | "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", 1561 | "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", 1562 | "requires": { 1563 | "ansi-styles": "^4.0.0", 1564 | "string-width": "^4.1.0", 1565 | "strip-ansi": "^6.0.0" 1566 | } 1567 | }, 1568 | "wrappy": { 1569 | "version": "1.0.2", 1570 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1571 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 1572 | }, 1573 | "y18n": { 1574 | "version": "5.0.8", 1575 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", 1576 | "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" 1577 | }, 1578 | "yallist": { 1579 | "version": "4.0.0", 1580 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 1581 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" 1582 | }, 1583 | "yargs": { 1584 | "version": "16.2.0", 1585 | "resolved": "http://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", 1586 | "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", 1587 | "requires": { 1588 | "cliui": "^7.0.2", 1589 | "escalade": "^3.1.1", 1590 | "get-caller-file": "^2.0.5", 1591 | "require-directory": "^2.1.1", 1592 | "string-width": "^4.2.0", 1593 | "y18n": "^5.0.5", 1594 | "yargs-parser": "^20.2.2" 1595 | } 1596 | }, 1597 | "yargs-parser": { 1598 | "version": "20.2.4", 1599 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", 1600 | "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==" 1601 | }, 1602 | "yargs-unparser": { 1603 | "version": "2.0.0", 1604 | "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", 1605 | "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", 1606 | "requires": { 1607 | "camelcase": "^6.0.0", 1608 | "decamelize": "^4.0.0", 1609 | "flat": "^5.0.2", 1610 | "is-plain-obj": "^2.1.0" 1611 | } 1612 | }, 1613 | "yocto-queue": { 1614 | "version": "0.1.0", 1615 | "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", 1616 | "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" 1617 | } 1618 | } 1619 | } 1620 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "datapipes", 3 | "version": "0.3.3", 4 | "author": "Open Knowledge Foundation ", 5 | "license": "MIT", 6 | "dependencies": { 7 | "csv": "^0.3.7", 8 | "event-stream": "3.3.4", 9 | "express": "<4.16.3", 10 | "marked": "", 11 | "nunjucks": "", 12 | "optimist": "", 13 | "request": "", 14 | "sandbox": "", 15 | "underscore": "" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/okfn/datapipes" 20 | }, 21 | "devDependencies": { 22 | "supertest": "", 23 | "mocha": "", 24 | "concat-stream": "" 25 | }, 26 | "engines": { 27 | "node": "9.11.1", 28 | "npm": "5.6.0" 29 | }, 30 | "scripts": { 31 | "test": "mocha test" 32 | }, 33 | "bin": { 34 | "datapipes": "./bin/datapipes" 35 | }, 36 | "main": "lib/index" 37 | } 38 | -------------------------------------------------------------------------------- /public/css/style.css: -------------------------------------------------------------------------------- 1 | /** 2 | CSS derived (with modifications) from Pink Touch 2 Licensed under GNU General Public License: 3 | 4 | http://theme.wordpress.com/themes/pink-touch-2/ 5 | 6 | This CSS licensed as far as necessary under GNU GPL2 and otherwise public domain. 7 | */ 8 | 9 | @import url(https://fonts.googleapis.com/css?family=Arvo:400,700); 10 | @import url(https://fonts.googleapis.com/css?family=Inconsolata); 11 | 12 | /* =Reset 13 | ----------------------------------------------- */ 14 | 15 | html { 16 | color: #000; 17 | height: 100%; 18 | } 19 | body, 20 | div, 21 | dl, 22 | dt, 23 | dd, 24 | ul, 25 | ol, 26 | li, 27 | h1, 28 | h2, 29 | h3, 30 | h4, 31 | h5, 32 | h6, 33 | pre, 34 | code, 35 | form, 36 | fieldset, 37 | legend, 38 | input, 39 | textarea, 40 | p, 41 | blockquote, 42 | th, 43 | td { 44 | margin: 0; 45 | padding: 0; 46 | } 47 | table { 48 | border-collapse: collapse; 49 | border-spacing: 0; 50 | } 51 | fieldset, 52 | img { 53 | border: 0; 54 | } 55 | address, 56 | caption, 57 | cite, 58 | code, 59 | dfn, 60 | em, 61 | strong, 62 | th, 63 | var { 64 | font-style: normal; 65 | font-weight: normal; 66 | } 67 | caption, 68 | th { 69 | text-align: left; 70 | } 71 | h1, 72 | h2, 73 | h3, 74 | h4, 75 | h5, 76 | h6 { 77 | font-size: 100%; 78 | font-weight: normal; 79 | } 80 | blockquote, 81 | q { 82 | quotes: none; 83 | } 84 | blockquote:before, 85 | blockquote:after, 86 | q:before, 87 | q:after { 88 | content: ''; 89 | content: none; 90 | } 91 | abbr, 92 | acronym { 93 | border: 0; 94 | font-variant: normal; 95 | } 96 | sup { 97 | vertical-align: text-top; 98 | } 99 | sub { 100 | vertical-align: text-bottom; 101 | } 102 | input, 103 | textarea, 104 | select { 105 | font-family: inherit; 106 | font-size: inherit; 107 | font-weight: inherit; 108 | outline: 0; 109 | } 110 | legend { 111 | color: #000; 112 | } 113 | 114 | 115 | /* =Global Setting 116 | -------------------------------------------------------------- */ 117 | 118 | body { 119 | background-color: #e3e3e3; 120 | background-attachment: fixed; 121 | background-image: url('../img/bg.jpg'); 122 | background-repeat: repeat; 123 | font-family: Cambria, Georgia, Times, serif; 124 | font-size: 1em; 125 | line-height: 1.5em; 126 | text-align: center; 127 | } 128 | h1, 129 | h2, 130 | h3, 131 | h4, 132 | h5, 133 | h6 { 134 | clear: both; 135 | color: #000; 136 | font-family: Arvo, Cambria, Georgia, Times, serif; 137 | font-weight: normal; 138 | line-height: 1.3em; 139 | text-shadow: 0 1px 0 #fff; 140 | } 141 | hr { 142 | background-color: #ccc; 143 | border: 0; 144 | height: 1px; 145 | margin-bottom: 1.375em; 146 | } 147 | p, 148 | dd, 149 | table { 150 | margin-bottom: 1.375em; 151 | } 152 | ul, 153 | ol, 154 | pre, 155 | blockquote { 156 | margin-bottom: 0.9375em; 157 | } 158 | ul, ol { 159 | margin: 0 0 1.375em 2em; 160 | } 161 | ul { 162 | list-style: square; 163 | } 164 | ol { 165 | list-style-type: decimal; 166 | } 167 | ol ol { 168 | list-style: upper-alpha; 169 | } 170 | ol ol ol { 171 | list-style: lower-roman; 172 | } 173 | ol ol ol ol { 174 | list-style: lower-alpha; 175 | } 176 | ul ul, 177 | ol ol, 178 | ul ol, 179 | ol ul { 180 | margin-bottom: 0; 181 | } 182 | dt { 183 | font-weight: bold; 184 | } 185 | dd { 186 | margin-bottom: 1.375em; 187 | } 188 | strong { 189 | font-weight: bold; 190 | } 191 | cite, 192 | em, 193 | i { 194 | font-style: italic; 195 | } 196 | blockquote em, 197 | blockquote i, 198 | blockquote cite { 199 | font-style: normal; 200 | } 201 | pre { 202 | background: #ddd; 203 | font: 1em monospace; 204 | line-height: 1.5; 205 | margin-bottom: 1.625em; 206 | overflow: auto; 207 | padding: 0.75em 1.625em; 208 | } 209 | code, 210 | kbd { 211 | font: 1em monospace; 212 | } 213 | abbr, 214 | acronym, 215 | dfn { 216 | border-bottom: 1px dotted #a9a9a9; 217 | cursor: help; 218 | } 219 | input, 220 | select, 221 | textarea, 222 | button { 223 | font-family: Cambria, Georgia, Times, serif; 224 | color: #000; 225 | } 226 | button, 227 | label, 228 | a { 229 | cursor: pointer; 230 | } 231 | address { 232 | font-style: normal; 233 | display: block; 234 | margin: 0 0 1.375em; 235 | } 236 | ins { 237 | background: #fff9c0; 238 | text-decoration: none; 239 | } 240 | sup, 241 | sub { 242 | font-size: 10px; 243 | height: 0; 244 | line-height: 1; 245 | position: relative; 246 | vertical-align: baseline; 247 | } 248 | sup { 249 | bottom: 1ex; 250 | } 251 | sub { 252 | top: .5ex; 253 | } 254 | /* Links */ 255 | a { 256 | color: #eb374b; 257 | } 258 | a:hover { 259 | color: #05b2c2; 260 | } 261 | h1 a, 262 | h2 a, 263 | h3 a, 264 | h4 a, 265 | h5 a, 266 | h6 a { 267 | text-decoration: none; 268 | } 269 | /* Clear setting */ 270 | .clear { 271 | clear: both; 272 | display: block; 273 | } 274 | .clearfix:after { 275 | clear: both; 276 | content: "."; 277 | display: block; 278 | height: 0; 279 | visibility: hidden; 280 | } 281 | div:after, 282 | form li:after, 283 | ul:after { 284 | clear: both; 285 | } 286 | /* Alignment */ 287 | .alignleft { 288 | display: inline; 289 | float: left; 290 | } 291 | .alignright { 292 | display: inline; 293 | float: right; 294 | } 295 | .aligncenter { 296 | clear: both; 297 | display: block; 298 | margin-left: auto; 299 | margin-right: auto; 300 | } 301 | 302 | /* =Structure 303 | -------------------------------------------------------------- */ 304 | 305 | .container { 306 | margin: 0 auto; 307 | max-width: 100%; 308 | width: 690px; 309 | text-align: left; 310 | } 311 | div.header { 312 | margin: 0px auto 15px; 313 | padding: 25px 0 35px; 314 | position: relative; 315 | text-align: center; 316 | width: 690px; 317 | } 318 | 319 | /* =Header 320 | -------------------------------------------------------------- */ 321 | 322 | .header h1 { 323 | font-size: 2.45em; 324 | line-height: 1.1em; 325 | } 326 | .header h1 a { 327 | color: #000; 328 | } 329 | .header h1 a:hover { 330 | color: #05b2c2; 331 | } 332 | .description { 333 | margin: 4px 0 0 0; 334 | } 335 | .description p { 336 | color: #817f7f; 337 | font-size: 1.125em; 338 | font-style: italic; 339 | margin: 0 auto; 340 | max-width: 100%; 341 | text-shadow: 0 1px 0 #fff; 342 | width: 690px; 343 | } 344 | .header-image { 345 | height: 230px; 346 | margin: -40px auto 60px; 347 | width: 690px; 348 | } 349 | .searchfield { 350 | background: url(images/sprite.png) 0 -600px no-repeat; 351 | float: right; 352 | height: 1.375em; 353 | padding-left: 0.9375em; 354 | padding-top: 1px; 355 | width: 11.4375em; 356 | } 357 | .searchfield input { 358 | background: transparent; 359 | border: none; 360 | color: #a5a5a5; 361 | font-size: 1em; 362 | width: 9.75em; 363 | } 364 | 365 | 366 | /* =Content 367 | -------------------------------------------------------------- */ 368 | 369 | .content { 370 | -moz-box-shadow: 0 4px 0 rgba(94, 94, 94, 0.1); 371 | -webkit-box-shadow: 0 4px 0 rgba(94, 94, 94, 0.1); 372 | box-shadow: 0 4px 0 rgba(94, 94, 94, 0.1); 373 | background: #f2f2f2; 374 | margin-bottom: 90px; 375 | padding: 25px 90px 22px; 376 | } 377 | /* content */ 378 | .content h1 { 379 | font-size: 2.5em; 380 | margin-bottom: 12px; 381 | } 382 | .content > h1 { 383 | font-size: 1.6em; 384 | line-height:1.2em; 385 | margin: 0 0 1.1em; 386 | } 387 | .content h2 { 388 | font-size: 1.6875em; 389 | margin-bottom: 12px; 390 | } 391 | .content h3 { 392 | font-size: 1.125em; 393 | margin-bottom: 12px; 394 | } 395 | .content h4 { 396 | font-size: 0.8125em; 397 | margin-bottom: 12px; 398 | } 399 | .content h5 { 400 | font-size: 0.6875em; 401 | margin-bottom: 12px; 402 | } 403 | .content h6 { 404 | font-size: 0.5em; 405 | margin-bottom: 12px; 406 | } 407 | .content table { 408 | border-bottom: 1px solid #ddd; 409 | margin: 0 0 1.625em; 410 | width: 100%; 411 | } 412 | .content th { 413 | color: #666; 414 | font-size: 10px; 415 | font-weight: 500; 416 | letter-spacing: 0.1em; 417 | line-height: 2.6em; 418 | text-transform: uppercase; 419 | } 420 | .content td { 421 | border-top: 1px solid #ddd; 422 | padding: 6px 10px 6px 0; 423 | } 424 | .content blockquote, 425 | .content blockquote[class*="align"] { 426 | font-size: 1.5em; 427 | line-height: 1.166666em; 428 | margin: 0; 429 | padding: 0 0 0 20px; 430 | } 431 | .content blockquote * { 432 | font-size: 1em; 433 | margin-bottom: 0.9166666666666666em; 434 | } 435 | 436 | /* =Images 437 | -------------------------------------------------------------- */ 438 | 439 | a img { 440 | border: none; 441 | } 442 | 443 | /* =Footer 444 | -------------------------------------------------------------- */ 445 | 446 | .footer { 447 | background-color: #f2f2f2; 448 | margin-top: -5px; 449 | padding: 25px 0; 450 | position: relative; 451 | border-top: 2px solid #ccc; 452 | text-align: center; 453 | } 454 | 455 | /* =Tables 456 | * 457 | * Courtesy of http://johnsardine.com/example/simple-little-table/ 458 | -------------------------------------------------------------- */ 459 | 460 | 461 | table { 462 | width: 100%; 463 | border-collapse: collapse; 464 | font-family:Arial, Helvetica, sans-serif; 465 | font-size: 12px; 466 | } 467 | /* Zebra striping */ 468 | tr:nth-of-type(odd) { 469 | background: #eee; 470 | } 471 | tr:nth-of-type(even) { 472 | background: #fff; 473 | } 474 | th { 475 | color: #333; 476 | font-weight: bold; 477 | 478 | background-color: #e6e6e6; 479 | background-repeat: no-repeat; 480 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), color-stop(25%, #ffffff), to(#e6e6e6)); 481 | background-image: -webkit-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6); 482 | background-image: -moz-linear-gradient(top, #ffffff, #ffffff 25%, #e6e6e6); 483 | background-image: -ms-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6); 484 | background-image: -o-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6); 485 | background-image: linear-gradient(#ffffff, #ffffff 25%, #e6e6e6); 486 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e6e6e6', GradientType=0); 487 | text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75); 488 | } 489 | td, th { 490 | padding: 6px; 491 | border: 1px solid #ccc; 492 | text-align: left; 493 | } 494 | table tr:hover td { 495 | } 496 | 497 | table td > div { 498 | overflow: hidden; 499 | height: 15px; 500 | } 501 | 502 | table tr:target td { 503 | background-color: rgb(255, 255, 204); 504 | } 505 | 506 | table .counter, table tr:target .counter { 507 | font-family: Inconsolata, Courier, monospace; 508 | font-size: 13px; 509 | color: #aaa; 510 | color: rgba(0,0,0,0.5); 511 | background-color: #fff; 512 | background-image: none; 513 | border: none; 514 | text-align: right; 515 | width: 1ch; 516 | } 517 | 518 | table .counter a { 519 | text-decoration: none; 520 | } 521 | 522 | 523 | /* Sorting styles */ 524 | th:hover { 525 | cursor: pointer; 526 | text-decoration: underline; 527 | } 528 | 529 | .headerSortDown, .headerSortUp { 530 | 531 | background-color: #E9EA9F; 532 | background-repeat: no-repeat; 533 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), color-stop(25%, #ffffff), to(#E9EA9F)); 534 | background-image: -webkit-linear-gradient(#ffffff, #ffffff 25%, #E9EA9F); 535 | background-image: -moz-linear-gradient(top, #ffffff, #ffffff 25%, #E9EA9F); 536 | background-image: -ms-linear-gradient(#ffffff, #ffffff 25%, #E9EA9F); 537 | background-image: -o-linear-gradient(#ffffff, #ffffff 25%, #E9EA9F); 538 | background-image: linear-gradient(#ffffff, #ffffff 25%, #E9EA9F); 539 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#E9EA9F', GradientType=0); 540 | } 541 | 542 | /* Interactive mode */ 543 | body.interactive { 544 | padding-top: 20px; 545 | text-align: inherit; 546 | font-size: 14px; 547 | font-family: "Helvetica Neue",Helvetica,Arial,sans-serif; 548 | } 549 | 550 | .interactive div.link { 551 | text-align: center; 552 | } 553 | 554 | .interactive iframe { 555 | width: 100%; 556 | border: 0; 557 | min-height: 500px; 558 | } 559 | 560 | .interactive pre { 561 | border: none; 562 | background: none; 563 | font-size: 13px; 564 | } 565 | 566 | .interactive .overlay { 567 | position: fixed; 568 | top: 0; 569 | left: 0; 570 | height: 100%; 571 | width: 100%; 572 | background: url(data:image/gif;base64,R0lGODlhEAAQAPQAAP///wAAAPDw8IqKiuDg4EZGRnp6egAAAFhYWCQkJKysrL6+vhQUFJycnAQEBDY2NmhoaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCgAAACwAAAAAEAAQAAAFdyAgAgIJIeWoAkRCCMdBkKtIHIngyMKsErPBYbADpkSCwhDmQCBethRB6Vj4kFCkQPG4IlWDgrNRIwnO4UKBXDufzQvDMaoSDBgFb886MiQadgNABAokfCwzBA8LCg0Egl8jAggGAA1kBIA1BAYzlyILczULC2UhACH5BAkKAAAALAAAAAAQABAAAAV2ICACAmlAZTmOREEIyUEQjLKKxPHADhEvqxlgcGgkGI1DYSVAIAWMx+lwSKkICJ0QsHi9RgKBwnVTiRQQgwF4I4UFDQQEwi6/3YSGWRRmjhEETAJfIgMFCnAKM0KDV4EEEAQLiF18TAYNXDaSe3x6mjidN1s3IQAh+QQJCgAAACwAAAAAEAAQAAAFeCAgAgLZDGU5jgRECEUiCI+yioSDwDJyLKsXoHFQxBSHAoAAFBhqtMJg8DgQBgfrEsJAEAg4YhZIEiwgKtHiMBgtpg3wbUZXGO7kOb1MUKRFMysCChAoggJCIg0GC2aNe4gqQldfL4l/Ag1AXySJgn5LcoE3QXI3IQAh+QQJCgAAACwAAAAAEAAQAAAFdiAgAgLZNGU5joQhCEjxIssqEo8bC9BRjy9Ag7GILQ4QEoE0gBAEBcOpcBA0DoxSK/e8LRIHn+i1cK0IyKdg0VAoljYIg+GgnRrwVS/8IAkICyosBIQpBAMoKy9dImxPhS+GKkFrkX+TigtLlIyKXUF+NjagNiEAIfkECQoAAAAsAAAAABAAEAAABWwgIAICaRhlOY4EIgjH8R7LKhKHGwsMvb4AAy3WODBIBBKCsYA9TjuhDNDKEVSERezQEL0WrhXucRUQGuik7bFlngzqVW9LMl9XWvLdjFaJtDFqZ1cEZUB0dUgvL3dgP4WJZn4jkomWNpSTIyEAIfkECQoAAAAsAAAAABAAEAAABX4gIAICuSxlOY6CIgiD8RrEKgqGOwxwUrMlAoSwIzAGpJpgoSDAGifDY5kopBYDlEpAQBwevxfBtRIUGi8xwWkDNBCIwmC9Vq0aiQQDQuK+VgQPDXV9hCJjBwcFYU5pLwwHXQcMKSmNLQcIAExlbH8JBwttaX0ABAcNbWVbKyEAIfkECQoAAAAsAAAAABAAEAAABXkgIAICSRBlOY7CIghN8zbEKsKoIjdFzZaEgUBHKChMJtRwcWpAWoWnifm6ESAMhO8lQK0EEAV3rFopIBCEcGwDKAqPh4HUrY4ICHH1dSoTFgcHUiZjBhAJB2AHDykpKAwHAwdzf19KkASIPl9cDgcnDkdtNwiMJCshACH5BAkKAAAALAAAAAAQABAAAAV3ICACAkkQZTmOAiosiyAoxCq+KPxCNVsSMRgBsiClWrLTSWFoIQZHl6pleBh6suxKMIhlvzbAwkBWfFWrBQTxNLq2RG2yhSUkDs2b63AYDAoJXAcFRwADeAkJDX0AQCsEfAQMDAIPBz0rCgcxky0JRWE1AmwpKyEAIfkECQoAAAAsAAAAABAAEAAABXkgIAICKZzkqJ4nQZxLqZKv4NqNLKK2/Q4Ek4lFXChsg5ypJjs1II3gEDUSRInEGYAw6B6zM4JhrDAtEosVkLUtHA7RHaHAGJQEjsODcEg0FBAFVgkQJQ1pAwcDDw8KcFtSInwJAowCCA6RIwqZAgkPNgVpWndjdyohACH5BAkKAAAALAAAAAAQABAAAAV5ICACAimc5KieLEuUKvm2xAKLqDCfC2GaO9eL0LABWTiBYmA06W6kHgvCqEJiAIJiu3gcvgUsscHUERm+kaCxyxa+zRPk0SgJEgfIvbAdIAQLCAYlCj4DBw0IBQsMCjIqBAcPAooCBg9pKgsJLwUFOhCZKyQDA3YqIQAh+QQJCgAAACwAAAAAEAAQAAAFdSAgAgIpnOSonmxbqiThCrJKEHFbo8JxDDOZYFFb+A41E4H4OhkOipXwBElYITDAckFEOBgMQ3arkMkUBdxIUGZpEb7kaQBRlASPg0FQQHAbEEMGDSVEAA1QBhAED1E0NgwFAooCDWljaQIQCE5qMHcNhCkjIQAh+QQJCgAAACwAAAAAEAAQAAAFeSAgAgIpnOSoLgxxvqgKLEcCC65KEAByKK8cSpA4DAiHQ/DkKhGKh4ZCtCyZGo6F6iYYPAqFgYy02xkSaLEMV34tELyRYNEsCQyHlvWkGCzsPgMCEAY7Cg04Uk48LAsDhRA8MVQPEF0GAgqYYwSRlycNcWskCkApIyEAOwAAAAAAAAAAAA%3D%3D) center center no-repeat; 573 | display: none; 574 | } 575 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datopian/datapipes/827d84928230f0421a2821f063a483ff551b6d12/public/favicon.ico -------------------------------------------------------------------------------- /public/img/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datopian/datapipes/827d84928230f0421a2821f063a483ff551b6d12/public/img/bg.jpg -------------------------------------------------------------------------------- /public/img/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datopian/datapipes/827d84928230f0421a2821f063a483ff551b6d12/public/img/example.png -------------------------------------------------------------------------------- /public/js/jquery.tablesorter.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * TableSorter 2.0 - Client-side table sorting with ease! 4 | * Version 2.0.5b 5 | * @requires jQuery v1.2.3 6 | * 7 | * Copyright (c) 2007 Christian Bach 8 | * Examples and docs at: http://tablesorter.com 9 | * Dual licensed under the MIT and GPL licenses: 10 | * http://www.opensource.org/licenses/mit-license.php 11 | * http://www.gnu.org/licenses/gpl.html 12 | * 13 | */ 14 | /** 15 | * 16 | * @description Create a sortable table with multi-column sorting capabilitys 17 | * 18 | * @example $('table').tablesorter(); 19 | * @desc Create a simple tablesorter interface. 20 | * 21 | * @example $('table').tablesorter({ sortList:[[0,0],[1,0]] }); 22 | * @desc Create a tablesorter interface and sort on the first and secound column column headers. 23 | * 24 | * @example $('table').tablesorter({ headers: { 0: { sorter: false}, 1: {sorter: false} } }); 25 | * 26 | * @desc Create a tablesorter interface and disableing the first and second column headers. 27 | * 28 | * 29 | * @example $('table').tablesorter({ headers: { 0: {sorter:"integer"}, 1: {sorter:"currency"} } }); 30 | * 31 | * @desc Create a tablesorter interface and set a column parser for the first 32 | * and second column. 33 | * 34 | * 35 | * @param Object 36 | * settings An object literal containing key/value pairs to provide 37 | * optional settings. 38 | * 39 | * 40 | * @option String cssHeader (optional) A string of the class name to be appended 41 | * to sortable tr elements in the thead of the table. Default value: 42 | * "header" 43 | * 44 | * @option String cssAsc (optional) A string of the class name to be appended to 45 | * sortable tr elements in the thead on a ascending sort. Default value: 46 | * "headerSortUp" 47 | * 48 | * @option String cssDesc (optional) A string of the class name to be appended 49 | * to sortable tr elements in the thead on a descending sort. Default 50 | * value: "headerSortDown" 51 | * 52 | * @option String sortInitialOrder (optional) A string of the inital sorting 53 | * order can be asc or desc. Default value: "asc" 54 | * 55 | * @option String sortMultisortKey (optional) A string of the multi-column sort 56 | * key. Default value: "shiftKey" 57 | * 58 | * @option String textExtraction (optional) A string of the text-extraction 59 | * method to use. For complex html structures inside td cell set this 60 | * option to "complex", on large tables the complex option can be slow. 61 | * Default value: "simple" 62 | * 63 | * @option Object headers (optional) An array containing the forces sorting 64 | * rules. This option let's you specify a default sorting rule. Default 65 | * value: null 66 | * 67 | * @option Array sortList (optional) An array containing the forces sorting 68 | * rules. This option let's you specify a default sorting rule. Default 69 | * value: null 70 | * 71 | * @option Array sortForce (optional) An array containing forced sorting rules. 72 | * This option let's you specify a default sorting rule, which is 73 | * prepended to user-selected rules. Default value: null 74 | * 75 | * @option Boolean sortLocaleCompare (optional) Boolean flag indicating whatever 76 | * to use String.localeCampare method or not. Default set to true. 77 | * 78 | * 79 | * @option Array sortAppend (optional) An array containing forced sorting rules. 80 | * This option let's you specify a default sorting rule, which is 81 | * appended to user-selected rules. Default value: null 82 | * 83 | * @option Boolean widthFixed (optional) Boolean flag indicating if tablesorter 84 | * should apply fixed widths to the table columns. This is usefull when 85 | * using the pager companion plugin. This options requires the dimension 86 | * jquery plugin. Default value: false 87 | * 88 | * @option Boolean cancelSelection (optional) Boolean flag indicating if 89 | * tablesorter should cancel selection of the table headers text. 90 | * Default value: true 91 | * 92 | * @option Boolean debug (optional) Boolean flag indicating if tablesorter 93 | * should display debuging information usefull for development. 94 | * 95 | * @type jQuery 96 | * 97 | * @name tablesorter 98 | * 99 | * @cat Plugins/Tablesorter 100 | * 101 | * @author Christian Bach/christian.bach@polyester.se 102 | */ 103 | 104 | (function ($) { 105 | $.extend({ 106 | tablesorter: new 107 | function () { 108 | 109 | var parsers = [], 110 | widgets = []; 111 | 112 | this.defaults = { 113 | cssHeader: "header", 114 | cssAsc: "headerSortUp", 115 | cssDesc: "headerSortDown", 116 | cssChildRow: "expand-child", 117 | sortInitialOrder: "asc", 118 | sortMultiSortKey: "shiftKey", 119 | sortForce: null, 120 | sortAppend: null, 121 | sortLocaleCompare: true, 122 | textExtraction: "simple", 123 | parsers: {}, widgets: [], 124 | widgetZebra: { 125 | css: ["even", "odd"] 126 | }, headers: {}, widthFixed: false, 127 | cancelSelection: true, 128 | sortList: [], 129 | headerList: [], 130 | dateFormat: "us", 131 | decimal: '/\.|\,/g', 132 | onRenderHeader: null, 133 | selectorHeaders: 'thead th', 134 | debug: false 135 | }; 136 | 137 | /* debuging utils */ 138 | 139 | function benchmark(s, d) { 140 | log(s + "," + (new Date().getTime() - d.getTime()) + "ms"); 141 | } 142 | 143 | this.benchmark = benchmark; 144 | 145 | function log(s) { 146 | if (typeof console != "undefined" && typeof console.debug != "undefined") { 147 | console.log(s); 148 | } else { 149 | alert(s); 150 | } 151 | } 152 | 153 | /* parsers utils */ 154 | 155 | function buildParserCache(table, $headers) { 156 | 157 | if (table.config.debug) { 158 | var parsersDebug = ""; 159 | } 160 | 161 | if (table.tBodies.length == 0) return; // In the case of empty tables 162 | var rows = table.tBodies[0].rows; 163 | 164 | if (rows[0]) { 165 | 166 | var list = [], 167 | cells = rows[0].cells, 168 | l = cells.length; 169 | 170 | for (var i = 0; i < l; i++) { 171 | 172 | var p = false; 173 | 174 | if ($.metadata && ($($headers[i]).metadata() && $($headers[i]).metadata().sorter)) { 175 | 176 | p = getParserById($($headers[i]).metadata().sorter); 177 | 178 | } else if ((table.config.headers[i] && table.config.headers[i].sorter)) { 179 | 180 | p = getParserById(table.config.headers[i].sorter); 181 | } 182 | if (!p) { 183 | 184 | p = detectParserForColumn(table, rows, -1, i); 185 | } 186 | 187 | if (table.config.debug) { 188 | parsersDebug += "column:" + i + " parser:" + p.id + "\n"; 189 | } 190 | 191 | list.push(p); 192 | } 193 | } 194 | 195 | if (table.config.debug) { 196 | log(parsersDebug); 197 | } 198 | 199 | return list; 200 | }; 201 | 202 | function detectParserForColumn(table, rows, rowIndex, cellIndex) { 203 | var l = parsers.length, 204 | node = false, 205 | nodeValue = false, 206 | keepLooking = true; 207 | while (nodeValue == '' && keepLooking) { 208 | rowIndex++; 209 | if (rows[rowIndex]) { 210 | node = getNodeFromRowAndCellIndex(rows, rowIndex, cellIndex); 211 | nodeValue = trimAndGetNodeText(table.config, node); 212 | if (table.config.debug) { 213 | log('Checking if value was empty on row:' + rowIndex); 214 | } 215 | } else { 216 | keepLooking = false; 217 | } 218 | } 219 | for (var i = 1; i < l; i++) { 220 | if (parsers[i].is(nodeValue, table, node)) { 221 | return parsers[i]; 222 | } 223 | } 224 | // 0 is always the generic parser (text) 225 | return parsers[0]; 226 | } 227 | 228 | function getNodeFromRowAndCellIndex(rows, rowIndex, cellIndex) { 229 | return rows[rowIndex].cells[cellIndex]; 230 | } 231 | 232 | function trimAndGetNodeText(config, node) { 233 | return $.trim(getElementText(config, node)); 234 | } 235 | 236 | function getParserById(name) { 237 | var l = parsers.length; 238 | for (var i = 0; i < l; i++) { 239 | if (parsers[i].id.toLowerCase() == name.toLowerCase()) { 240 | return parsers[i]; 241 | } 242 | } 243 | return false; 244 | } 245 | 246 | /* utils */ 247 | 248 | function buildCache(table) { 249 | 250 | if (table.config.debug) { 251 | var cacheTime = new Date(); 252 | } 253 | 254 | var totalRows = (table.tBodies[0] && table.tBodies[0].rows.length) || 0, 255 | totalCells = (table.tBodies[0].rows[0] && table.tBodies[0].rows[0].cells.length) || 0, 256 | parsers = table.config.parsers, 257 | cache = { 258 | row: [], 259 | normalized: [] 260 | }; 261 | 262 | for (var i = 0; i < totalRows; ++i) { 263 | 264 | /** Add the table data to main data array */ 265 | var c = $(table.tBodies[0].rows[i]), 266 | cols = []; 267 | 268 | // if this is a child row, add it to the last row's children and 269 | // continue to the next row 270 | if (c.hasClass(table.config.cssChildRow)) { 271 | cache.row[cache.row.length - 1] = cache.row[cache.row.length - 1].add(c); 272 | // go to the next for loop 273 | continue; 274 | } 275 | 276 | cache.row.push(c); 277 | 278 | for (var j = 0; j < totalCells; ++j) { 279 | cols.push(parsers[j].format(getElementText(table.config, c[0].cells[j]), table, c[0].cells[j])); 280 | } 281 | 282 | cols.push(cache.normalized.length); // add position for rowCache 283 | cache.normalized.push(cols); 284 | cols = null; 285 | }; 286 | 287 | if (table.config.debug) { 288 | benchmark("Building cache for " + totalRows + " rows:", cacheTime); 289 | } 290 | 291 | return cache; 292 | }; 293 | 294 | function getElementText(config, node) { 295 | 296 | var text = ""; 297 | 298 | if (!node) return ""; 299 | 300 | if (!config.supportsTextContent) config.supportsTextContent = node.textContent || false; 301 | 302 | if (config.textExtraction == "simple") { 303 | if (config.supportsTextContent) { 304 | text = node.textContent; 305 | } else { 306 | if (node.childNodes[0] && node.childNodes[0].hasChildNodes()) { 307 | text = node.childNodes[0].innerHTML; 308 | } else { 309 | text = node.innerHTML; 310 | } 311 | } 312 | } else { 313 | if (typeof(config.textExtraction) == "function") { 314 | text = config.textExtraction(node); 315 | } else { 316 | text = $(node).text(); 317 | } 318 | } 319 | return text; 320 | } 321 | 322 | function appendToTable(table, cache) { 323 | 324 | if (table.config.debug) { 325 | var appendTime = new Date() 326 | } 327 | 328 | var c = cache, 329 | r = c.row, 330 | n = c.normalized, 331 | totalRows = n.length, 332 | checkCell = (n[0].length - 1), 333 | tableBody = $(table.tBodies[0]), 334 | rows = []; 335 | 336 | 337 | for (var i = 0; i < totalRows; i++) { 338 | var pos = n[i][checkCell]; 339 | 340 | rows.push(r[pos]); 341 | 342 | if (!table.config.appender) { 343 | 344 | //var o = ; 345 | var l = r[pos].length; 346 | for (var j = 0; j < l; j++) { 347 | tableBody[0].appendChild(r[pos][j]); 348 | } 349 | 350 | // 351 | } 352 | } 353 | 354 | 355 | 356 | if (table.config.appender) { 357 | 358 | table.config.appender(table, rows); 359 | } 360 | 361 | rows = null; 362 | 363 | if (table.config.debug) { 364 | benchmark("Rebuilt table:", appendTime); 365 | } 366 | 367 | // apply table widgets 368 | applyWidget(table); 369 | 370 | // trigger sortend 371 | setTimeout(function () { 372 | $(table).trigger("sortEnd"); 373 | }, 0); 374 | 375 | }; 376 | 377 | function buildHeaders(table) { 378 | 379 | if (table.config.debug) { 380 | var time = new Date(); 381 | } 382 | 383 | var meta = ($.metadata) ? true : false; 384 | 385 | var header_index = computeTableHeaderCellIndexes(table); 386 | 387 | $tableHeaders = $(table.config.selectorHeaders, table).each(function (index) { 388 | 389 | this.column = header_index[this.parentNode.rowIndex + "-" + this.cellIndex]; 390 | // this.column = index; 391 | this.order = formatSortingOrder(table.config.sortInitialOrder); 392 | 393 | 394 | this.count = this.order; 395 | 396 | if (checkHeaderMetadata(this) || checkHeaderOptions(table, index)) this.sortDisabled = true; 397 | if (checkHeaderOptionsSortingLocked(table, index)) this.order = this.lockedOrder = checkHeaderOptionsSortingLocked(table, index); 398 | 399 | if (!this.sortDisabled) { 400 | var $th = $(this).addClass(table.config.cssHeader); 401 | if (table.config.onRenderHeader) table.config.onRenderHeader.apply($th); 402 | } 403 | 404 | // add cell to headerList 405 | table.config.headerList[index] = this; 406 | }); 407 | 408 | if (table.config.debug) { 409 | benchmark("Built headers:", time); 410 | log($tableHeaders); 411 | } 412 | 413 | return $tableHeaders; 414 | 415 | }; 416 | 417 | // from: 418 | // http://www.javascripttoolbox.com/lib/table/examples.php 419 | // http://www.javascripttoolbox.com/temp/table_cellindex.html 420 | 421 | 422 | function computeTableHeaderCellIndexes(t) { 423 | var matrix = []; 424 | var lookup = {}; 425 | var thead = t.getElementsByTagName('THEAD')[0]; 426 | var trs = thead.getElementsByTagName('TR'); 427 | 428 | for (var i = 0; i < trs.length; i++) { 429 | var cells = trs[i].cells; 430 | for (var j = 0; j < cells.length; j++) { 431 | var c = cells[j]; 432 | 433 | var rowIndex = c.parentNode.rowIndex; 434 | var cellId = rowIndex + "-" + c.cellIndex; 435 | var rowSpan = c.rowSpan || 1; 436 | var colSpan = c.colSpan || 1 437 | var firstAvailCol; 438 | if (typeof(matrix[rowIndex]) == "undefined") { 439 | matrix[rowIndex] = []; 440 | } 441 | // Find first available column in the first row 442 | for (var k = 0; k < matrix[rowIndex].length + 1; k++) { 443 | if (typeof(matrix[rowIndex][k]) == "undefined") { 444 | firstAvailCol = k; 445 | break; 446 | } 447 | } 448 | lookup[cellId] = firstAvailCol; 449 | for (var k = rowIndex; k < rowIndex + rowSpan; k++) { 450 | if (typeof(matrix[k]) == "undefined") { 451 | matrix[k] = []; 452 | } 453 | var matrixrow = matrix[k]; 454 | for (var l = firstAvailCol; l < firstAvailCol + colSpan; l++) { 455 | matrixrow[l] = "x"; 456 | } 457 | } 458 | } 459 | } 460 | return lookup; 461 | } 462 | 463 | function checkCellColSpan(table, rows, row) { 464 | var arr = [], 465 | r = table.tHead.rows, 466 | c = r[row].cells; 467 | 468 | for (var i = 0; i < c.length; i++) { 469 | var cell = c[i]; 470 | 471 | if (cell.colSpan > 1) { 472 | arr = arr.concat(checkCellColSpan(table, headerArr, row++)); 473 | } else { 474 | if (table.tHead.length == 1 || (cell.rowSpan > 1 || !r[row + 1])) { 475 | arr.push(cell); 476 | } 477 | // headerArr[row] = (i+row); 478 | } 479 | } 480 | return arr; 481 | }; 482 | 483 | function checkHeaderMetadata(cell) { 484 | if (($.metadata) && ($(cell).metadata().sorter === false)) { 485 | return true; 486 | }; 487 | return false; 488 | } 489 | 490 | function checkHeaderOptions(table, i) { 491 | if ((table.config.headers[i]) && (table.config.headers[i].sorter === false)) { 492 | return true; 493 | }; 494 | return false; 495 | } 496 | 497 | function checkHeaderOptionsSortingLocked(table, i) { 498 | if ((table.config.headers[i]) && (table.config.headers[i].lockedOrder)) return table.config.headers[i].lockedOrder; 499 | return false; 500 | } 501 | 502 | function applyWidget(table) { 503 | var c = table.config.widgets; 504 | var l = c.length; 505 | for (var i = 0; i < l; i++) { 506 | 507 | getWidgetById(c[i]).format(table); 508 | } 509 | 510 | } 511 | 512 | function getWidgetById(name) { 513 | var l = widgets.length; 514 | for (var i = 0; i < l; i++) { 515 | if (widgets[i].id.toLowerCase() == name.toLowerCase()) { 516 | return widgets[i]; 517 | } 518 | } 519 | }; 520 | 521 | function formatSortingOrder(v) { 522 | if (typeof(v) != "Number") { 523 | return (v.toLowerCase() == "desc") ? 1 : 0; 524 | } else { 525 | return (v == 1) ? 1 : 0; 526 | } 527 | } 528 | 529 | function isValueInArray(v, a) { 530 | var l = a.length; 531 | for (var i = 0; i < l; i++) { 532 | if (a[i][0] == v) { 533 | return true; 534 | } 535 | } 536 | return false; 537 | } 538 | 539 | function setHeadersCss(table, $headers, list, css) { 540 | // remove all header information 541 | $headers.removeClass(css[0]).removeClass(css[1]); 542 | 543 | var h = []; 544 | $headers.each(function (offset) { 545 | if (!this.sortDisabled) { 546 | h[this.column] = $(this); 547 | } 548 | }); 549 | 550 | var l = list.length; 551 | for (var i = 0; i < l; i++) { 552 | h[list[i][0]].addClass(css[list[i][1]]); 553 | } 554 | } 555 | 556 | function fixColumnWidth(table, $headers) { 557 | var c = table.config; 558 | if (c.widthFixed) { 559 | var colgroup = $(''); 560 | $("tr:first td", table.tBodies[0]).each(function () { 561 | colgroup.append($('').css('width', $(this).width())); 562 | }); 563 | $(table).prepend(colgroup); 564 | }; 565 | } 566 | 567 | function updateHeaderSortCount(table, sortList) { 568 | var c = table.config, 569 | l = sortList.length; 570 | for (var i = 0; i < l; i++) { 571 | var s = sortList[i], 572 | o = c.headerList[s[0]]; 573 | o.count = s[1]; 574 | o.count++; 575 | } 576 | } 577 | 578 | /* sorting methods */ 579 | 580 | function multisort(table, sortList, cache) { 581 | 582 | if (table.config.debug) { 583 | var sortTime = new Date(); 584 | } 585 | 586 | var dynamicExp = "var sortWrapper = function(a,b) {", 587 | l = sortList.length; 588 | 589 | // TODO: inline functions. 590 | for (var i = 0; i < l; i++) { 591 | 592 | var c = sortList[i][0]; 593 | var order = sortList[i][1]; 594 | // var s = (getCachedSortType(table.config.parsers,c) == "text") ? 595 | // ((order == 0) ? "sortText" : "sortTextDesc") : ((order == 0) ? 596 | // "sortNumeric" : "sortNumericDesc"); 597 | // var s = (table.config.parsers[c].type == "text") ? ((order == 0) 598 | // ? makeSortText(c) : makeSortTextDesc(c)) : ((order == 0) ? 599 | // makeSortNumeric(c) : makeSortNumericDesc(c)); 600 | var s = (table.config.parsers[c].type == "text") ? ((order == 0) ? makeSortFunction("text", "asc", c) : makeSortFunction("text", "desc", c)) : ((order == 0) ? makeSortFunction("numeric", "asc", c) : makeSortFunction("numeric", "desc", c)); 601 | var e = "e" + i; 602 | 603 | dynamicExp += "var " + e + " = " + s; // + "(a[" + c + "],b[" + c 604 | // + "]); "; 605 | dynamicExp += "if(" + e + ") { return " + e + "; } "; 606 | dynamicExp += "else { "; 607 | 608 | } 609 | 610 | // if value is the same keep orignal order 611 | var orgOrderCol = cache.normalized[0].length - 1; 612 | dynamicExp += "return a[" + orgOrderCol + "]-b[" + orgOrderCol + "];"; 613 | 614 | for (var i = 0; i < l; i++) { 615 | dynamicExp += "}; "; 616 | } 617 | 618 | dynamicExp += "return 0; "; 619 | dynamicExp += "}; "; 620 | 621 | if (table.config.debug) { 622 | benchmark("Evaling expression:" + dynamicExp, new Date()); 623 | } 624 | 625 | eval(dynamicExp); 626 | 627 | cache.normalized.sort(sortWrapper); 628 | 629 | if (table.config.debug) { 630 | benchmark("Sorting on " + sortList.toString() + " and dir " + order + " time:", sortTime); 631 | } 632 | 633 | return cache; 634 | }; 635 | 636 | function makeSortFunction(type, direction, index) { 637 | var a = "a[" + index + "]", 638 | b = "b[" + index + "]"; 639 | if (type == 'text' && direction == 'asc') { 640 | return "(" + a + " == " + b + " ? 0 : (" + a + " === null ? Number.POSITIVE_INFINITY : (" + b + " === null ? Number.NEGATIVE_INFINITY : (" + a + " < " + b + ") ? -1 : 1 )));"; 641 | } else if (type == 'text' && direction == 'desc') { 642 | return "(" + a + " == " + b + " ? 0 : (" + a + " === null ? Number.POSITIVE_INFINITY : (" + b + " === null ? Number.NEGATIVE_INFINITY : (" + b + " < " + a + ") ? -1 : 1 )));"; 643 | } else if (type == 'numeric' && direction == 'asc') { 644 | return "(" + a + " === null && " + b + " === null) ? 0 :(" + a + " === null ? Number.POSITIVE_INFINITY : (" + b + " === null ? Number.NEGATIVE_INFINITY : " + a + " - " + b + "));"; 645 | } else if (type == 'numeric' && direction == 'desc') { 646 | return "(" + a + " === null && " + b + " === null) ? 0 :(" + a + " === null ? Number.POSITIVE_INFINITY : (" + b + " === null ? Number.NEGATIVE_INFINITY : " + b + " - " + a + "));"; 647 | } 648 | }; 649 | 650 | function makeSortText(i) { 651 | return "((a[" + i + "] < b[" + i + "]) ? -1 : ((a[" + i + "] > b[" + i + "]) ? 1 : 0));"; 652 | }; 653 | 654 | function makeSortTextDesc(i) { 655 | return "((b[" + i + "] < a[" + i + "]) ? -1 : ((b[" + i + "] > a[" + i + "]) ? 1 : 0));"; 656 | }; 657 | 658 | function makeSortNumeric(i) { 659 | return "a[" + i + "]-b[" + i + "];"; 660 | }; 661 | 662 | function makeSortNumericDesc(i) { 663 | return "b[" + i + "]-a[" + i + "];"; 664 | }; 665 | 666 | function sortText(a, b) { 667 | if (table.config.sortLocaleCompare) return a.localeCompare(b); 668 | return ((a < b) ? -1 : ((a > b) ? 1 : 0)); 669 | }; 670 | 671 | function sortTextDesc(a, b) { 672 | if (table.config.sortLocaleCompare) return b.localeCompare(a); 673 | return ((b < a) ? -1 : ((b > a) ? 1 : 0)); 674 | }; 675 | 676 | function sortNumeric(a, b) { 677 | return a - b; 678 | }; 679 | 680 | function sortNumericDesc(a, b) { 681 | return b - a; 682 | }; 683 | 684 | function getCachedSortType(parsers, i) { 685 | return parsers[i].type; 686 | }; /* public methods */ 687 | this.construct = function (settings) { 688 | return this.each(function () { 689 | // if no thead or tbody quit. 690 | if (!this.tHead || !this.tBodies) return; 691 | // declare 692 | var $this, $document, $headers, cache, config, shiftDown = 0, 693 | sortOrder; 694 | // new blank config object 695 | this.config = {}; 696 | // merge and extend. 697 | config = $.extend(this.config, $.tablesorter.defaults, settings); 698 | // store common expression for speed 699 | $this = $(this); 700 | // save the settings where they read 701 | $.data(this, "tablesorter", config); 702 | // build headers 703 | $headers = buildHeaders(this); 704 | // try to auto detect column type, and store in tables config 705 | this.config.parsers = buildParserCache(this, $headers); 706 | // build the cache for the tbody cells 707 | cache = buildCache(this); 708 | // get the css class names, could be done else where. 709 | var sortCSS = [config.cssDesc, config.cssAsc]; 710 | // fixate columns if the users supplies the fixedWidth option 711 | fixColumnWidth(this); 712 | // apply event handling to headers 713 | // this is to big, perhaps break it out? 714 | $headers.click( 715 | 716 | function (e) { 717 | var totalRows = ($this[0].tBodies[0] && $this[0].tBodies[0].rows.length) || 0; 718 | if (!this.sortDisabled && totalRows > 0) { 719 | // Only call sortStart if sorting is 720 | // enabled. 721 | $this.trigger("sortStart"); 722 | // store exp, for speed 723 | var $cell = $(this); 724 | // get current column index 725 | var i = this.column; 726 | // get current column sort order 727 | this.order = this.count++ % 2; 728 | // always sort on the locked order. 729 | if(this.lockedOrder) this.order = this.lockedOrder; 730 | 731 | // user only whants to sort on one 732 | // column 733 | if (!e[config.sortMultiSortKey]) { 734 | // flush the sort list 735 | config.sortList = []; 736 | if (config.sortForce != null) { 737 | var a = config.sortForce; 738 | for (var j = 0; j < a.length; j++) { 739 | if (a[j][0] != i) { 740 | config.sortList.push(a[j]); 741 | } 742 | } 743 | } 744 | // add column to sort list 745 | config.sortList.push([i, this.order]); 746 | // multi column sorting 747 | } else { 748 | // the user has clicked on an all 749 | // ready sortet column. 750 | if (isValueInArray(i, config.sortList)) { 751 | // revers the sorting direction 752 | // for all tables. 753 | for (var j = 0; j < config.sortList.length; j++) { 754 | var s = config.sortList[j], 755 | o = config.headerList[s[0]]; 756 | if (s[0] == i) { 757 | o.count = s[1]; 758 | o.count++; 759 | s[1] = o.count % 2; 760 | } 761 | } 762 | } else { 763 | // add column to sort list array 764 | config.sortList.push([i, this.order]); 765 | } 766 | }; 767 | setTimeout(function () { 768 | // set css for headers 769 | setHeadersCss($this[0], $headers, config.sortList, sortCSS); 770 | appendToTable( 771 | $this[0], multisort( 772 | $this[0], config.sortList, cache) 773 | ); 774 | }, 1); 775 | // stop normal event by returning false 776 | return false; 777 | } 778 | // cancel selection 779 | }).mousedown(function () { 780 | if (config.cancelSelection) { 781 | this.onselectstart = function () { 782 | return false 783 | }; 784 | return false; 785 | } 786 | }); 787 | // apply easy methods that trigger binded events 788 | $this.bind("update", function () { 789 | var me = this; 790 | setTimeout(function () { 791 | // rebuild parsers. 792 | me.config.parsers = buildParserCache( 793 | me, $headers); 794 | // rebuild the cache map 795 | cache = buildCache(me); 796 | }, 1); 797 | }).bind("updateCell", function (e, cell) { 798 | var config = this.config; 799 | // get position from the dom. 800 | var pos = [(cell.parentNode.rowIndex - 1), cell.cellIndex]; 801 | // update cache 802 | cache.normalized[pos[0]][pos[1]] = config.parsers[pos[1]].format( 803 | getElementText(config, cell), cell); 804 | }).bind("sorton", function (e, list) { 805 | $(this).trigger("sortStart"); 806 | config.sortList = list; 807 | // update and store the sortlist 808 | var sortList = config.sortList; 809 | // update header count index 810 | updateHeaderSortCount(this, sortList); 811 | // set css for headers 812 | setHeadersCss(this, $headers, sortList, sortCSS); 813 | // sort the table and append it to the dom 814 | appendToTable(this, multisort(this, sortList, cache)); 815 | }).bind("appendCache", function () { 816 | appendToTable(this, cache); 817 | }).bind("applyWidgetId", function (e, id) { 818 | getWidgetById(id).format(this); 819 | }).bind("applyWidgets", function () { 820 | // apply widgets 821 | applyWidget(this); 822 | }); 823 | if ($.metadata && ($(this).metadata() && $(this).metadata().sortlist)) { 824 | config.sortList = $(this).metadata().sortlist; 825 | } 826 | // if user has supplied a sort list to constructor. 827 | if (config.sortList.length > 0) { 828 | $this.trigger("sorton", [config.sortList]); 829 | } 830 | // apply widgets 831 | applyWidget(this); 832 | }); 833 | }; 834 | this.addParser = function (parser) { 835 | var l = parsers.length, 836 | a = true; 837 | for (var i = 0; i < l; i++) { 838 | if (parsers[i].id.toLowerCase() == parser.id.toLowerCase()) { 839 | a = false; 840 | } 841 | } 842 | if (a) { 843 | parsers.push(parser); 844 | }; 845 | }; 846 | this.addWidget = function (widget) { 847 | widgets.push(widget); 848 | }; 849 | this.formatFloat = function (s) { 850 | var i = parseFloat(s); 851 | return (isNaN(i)) ? 0 : i; 852 | }; 853 | this.formatInt = function (s) { 854 | var i = parseInt(s); 855 | return (isNaN(i)) ? 0 : i; 856 | }; 857 | this.isDigit = function (s, config) { 858 | // replace all an wanted chars and match. 859 | return /^[-+]?\d*$/.test($.trim(s.replace(/[,.']/g, ''))); 860 | }; 861 | this.clearTableBody = function (table) { 862 | if ($.browser.msie) { 863 | function empty() { 864 | while (this.firstChild) 865 | this.removeChild(this.firstChild); 866 | } 867 | empty.apply(table.tBodies[0]); 868 | } else { 869 | table.tBodies[0].innerHTML = ""; 870 | } 871 | }; 872 | } 873 | }); 874 | 875 | // extend plugin scope 876 | $.fn.extend({ 877 | tablesorter: $.tablesorter.construct 878 | }); 879 | 880 | // make shortcut 881 | var ts = $.tablesorter; 882 | 883 | // add default parsers 884 | ts.addParser({ 885 | id: "text", 886 | is: function (s) { 887 | return true; 888 | }, format: function (s) { 889 | return $.trim(s.toLocaleLowerCase()); 890 | }, type: "text" 891 | }); 892 | 893 | ts.addParser({ 894 | id: "digit", 895 | is: function (s, table) { 896 | var c = table.config; 897 | return $.tablesorter.isDigit(s, c); 898 | }, format: function (s) { 899 | return $.tablesorter.formatFloat(s); 900 | }, type: "numeric" 901 | }); 902 | 903 | ts.addParser({ 904 | id: "currency", 905 | is: function (s) { 906 | return /^[£$€?.]/.test(s); 907 | }, format: function (s) { 908 | return $.tablesorter.formatFloat(s.replace(new RegExp(/[£$€]/g), "")); 909 | }, type: "numeric" 910 | }); 911 | 912 | ts.addParser({ 913 | id: "ipAddress", 914 | is: function (s) { 915 | return /^\d{2,3}[\.]\d{2,3}[\.]\d{2,3}[\.]\d{2,3}$/.test(s); 916 | }, format: function (s) { 917 | var a = s.split("."), 918 | r = "", 919 | l = a.length; 920 | for (var i = 0; i < l; i++) { 921 | var item = a[i]; 922 | if (item.length == 2) { 923 | r += "0" + item; 924 | } else { 925 | r += item; 926 | } 927 | } 928 | return $.tablesorter.formatFloat(r); 929 | }, type: "numeric" 930 | }); 931 | 932 | ts.addParser({ 933 | id: "url", 934 | is: function (s) { 935 | return /^(https?|ftp|file):\/\/$/.test(s); 936 | }, format: function (s) { 937 | return jQuery.trim(s.replace(new RegExp(/(https?|ftp|file):\/\//), '')); 938 | }, type: "text" 939 | }); 940 | 941 | ts.addParser({ 942 | id: "isoDate", 943 | is: function (s) { 944 | return /^\d{4}[\/-]\d{1,2}[\/-]\d{1,2}$/.test(s); 945 | }, format: function (s) { 946 | return $.tablesorter.formatFloat((s != "") ? new Date(s.replace( 947 | new RegExp(/-/g), "/")).getTime() : "0"); 948 | }, type: "numeric" 949 | }); 950 | 951 | ts.addParser({ 952 | id: "percent", 953 | is: function (s) { 954 | return /\%$/.test($.trim(s)); 955 | }, format: function (s) { 956 | return $.tablesorter.formatFloat(s.replace(new RegExp(/%/g), "")); 957 | }, type: "numeric" 958 | }); 959 | 960 | ts.addParser({ 961 | id: "usLongDate", 962 | is: function (s) { 963 | return s.match(new RegExp(/^[A-Za-z]{3,10}\.? [0-9]{1,2}, ([0-9]{4}|'?[0-9]{2}) (([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(AM|PM)))$/)); 964 | }, format: function (s) { 965 | return $.tablesorter.formatFloat(new Date(s).getTime()); 966 | }, type: "numeric" 967 | }); 968 | 969 | ts.addParser({ 970 | id: "shortDate", 971 | is: function (s) { 972 | return /\d{1,2}[\/\-]\d{1,2}[\/\-]\d{2,4}/.test(s); 973 | }, format: function (s, table) { 974 | var c = table.config; 975 | s = s.replace(/\-/g, "/"); 976 | if (c.dateFormat == "us") { 977 | // reformat the string in ISO format 978 | s = s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/, "$3/$1/$2"); 979 | } else if (c.dateFormat == "uk") { 980 | // reformat the string in ISO format 981 | s = s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/, "$3/$2/$1"); 982 | } else if (c.dateFormat == "dd/mm/yy" || c.dateFormat == "dd-mm-yy") { 983 | s = s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{2})/, "$1/$2/$3"); 984 | } 985 | return $.tablesorter.formatFloat(new Date(s).getTime()); 986 | }, type: "numeric" 987 | }); 988 | ts.addParser({ 989 | id: "time", 990 | is: function (s) { 991 | return /^(([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(am|pm)))$/.test(s); 992 | }, format: function (s) { 993 | return $.tablesorter.formatFloat(new Date("2000/01/01 " + s).getTime()); 994 | }, type: "numeric" 995 | }); 996 | ts.addParser({ 997 | id: "metadata", 998 | is: function (s) { 999 | return false; 1000 | }, format: function (s, table, cell) { 1001 | var c = table.config, 1002 | p = (!c.parserMetadataName) ? 'sortValue' : c.parserMetadataName; 1003 | return $(cell).metadata()[p]; 1004 | }, type: "numeric" 1005 | }); 1006 | // add default widgets 1007 | ts.addWidget({ 1008 | id: "zebra", 1009 | format: function (table) { 1010 | if (table.config.debug) { 1011 | var time = new Date(); 1012 | } 1013 | var $tr, row = -1, 1014 | odd; 1015 | // loop through the visible rows 1016 | $("tr:visible", table.tBodies[0]).each(function (i) { 1017 | $tr = $(this); 1018 | // style children rows the same way the parent 1019 | // row was styled 1020 | if (!$tr.hasClass(table.config.cssChildRow)) row++; 1021 | odd = (row % 2 == 0); 1022 | $tr.removeClass( 1023 | table.config.widgetZebra.css[odd ? 0 : 1]).addClass( 1024 | table.config.widgetZebra.css[odd ? 1 : 0]) 1025 | }); 1026 | if (table.config.debug) { 1027 | $.tablesorter.benchmark("Applying Zebra widget", time); 1028 | } 1029 | } 1030 | }); 1031 | })(jQuery); -------------------------------------------------------------------------------- /public/js/table.js: -------------------------------------------------------------------------------- 1 | jQuery(document).ready(function($) { 2 | function maybeScrollToHash() { 3 | if (window.location.hash && $(window.location.hash).length) { 4 | var newTop = $(window.location.hash).offset().top - 150; 5 | $(window).scrollTop(newTop); 6 | } 7 | } 8 | 9 | $(window).bind('hashchange', function() { 10 | maybeScrollToHash(); 11 | }); 12 | 13 | maybeScrollToHash(); 14 | 15 | $('table').tablesorter() 16 | }); 17 | -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'), 2 | path = require('path'), 3 | marked = require('marked'), 4 | _ = require('underscore'); 5 | 6 | exports.wizard = function(req, res) { 7 | if (!req.query.url) { 8 | res.render('interactive.html', { 9 | query: { 10 | head: 10, 11 | delimiter: ',' 12 | }, 13 | output: 'html' 14 | }); 15 | return; 16 | } 17 | 18 | queryStr = '?url=' + req.query.url; 19 | var tmp = _.extend({}, req.query); 20 | delete tmp.url; 21 | var output = req.query.output || 'html'; 22 | delete tmp.output; 23 | 24 | var pipes = ['csv -d ' + tmp.delimiter]; 25 | delete tmp.delimiter; 26 | 27 | pipes = pipes.concat(_.map(tmp, function(v, k) { 28 | return k + ' ' + v; 29 | })); 30 | 31 | // csv is default output format 32 | if (output !== 'csv') { 33 | pipes.push(output); 34 | } 35 | pipes = pipes.join('/'); 36 | 37 | var pipeline = req.protocol + '://' + req.get('host') + '/' + pipes + '/' + queryStr; 38 | 39 | var query = _.extend({}, tmp); 40 | query.delimiter = req.query.delimiter; 41 | 42 | var tmplData = { 43 | url: req.query.url, 44 | output: output, 45 | pipeline: pipeline, 46 | query: query 47 | }; 48 | 49 | res.render('interactive.html', tmplData); 50 | }; 51 | -------------------------------------------------------------------------------- /test/data/gla.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datopian/datapipes/827d84928230f0421a2821f063a483ff551b6d12/test/data/gla.csv -------------------------------------------------------------------------------- /test/system.js: -------------------------------------------------------------------------------- 1 | var request = require('supertest'); 2 | var assert = require('assert'); 3 | var csv = require('csv'); 4 | var _ = require('underscore'); 5 | 6 | var data_url = 'https://raw.githubusercontent.com/okfn/datapipes/master/test/data/gla.csv'; 7 | 8 | // some facts about our data 9 | var num_rows = 100; 10 | var num_cols = 8; 11 | var num_blank_rows = 1; 12 | var num_london_rows = 15; // rows containing the word LONDON 13 | 14 | describe('Setup', function(){ 15 | var app; 16 | 17 | beforeEach(function () { 18 | delete require.cache[require.resolve('../app')]; 19 | app = require('../app'); 20 | }); 21 | 22 | afterEach(function (done) { 23 | app.close(done); 24 | }); 25 | 26 | describe('GET /', function(){ 27 | it('should respond with html', function(done){ 28 | request(app) 29 | .get('/') 30 | .expect('Content-Type', /html/) 31 | .expect(200, done) 32 | ; 33 | }); 34 | }); 35 | 36 | describe('Docs', function(){ 37 | ops = ['none', 'head', 'cut', 'delete', 'grep', 'strip', 'html']; 38 | _.each(ops, function(op) { 39 | var url = '/' + op; 40 | describe('GET ' + url, function(){ 41 | it('should respond with html', function(done){ 42 | request(app) 43 | .get(url) 44 | .expect('Content-Type', /html/) 45 | .expect(200, done) 46 | ; 47 | }); 48 | }); 49 | }); 50 | }); 51 | 52 | // describe('Chrome space replace', function(){ 53 | // var url = '/csv%20-H/head?url=https://www.biglotteryfund.org.uk/-/media/Files/Research%20Documents/aOpenDataFiles/BIGGrantOpenData2004_05.csv'; 54 | // it('should fix the %20 in the path, but not break the query string', function(done){ 55 | // request(app) 56 | // .get(url) 57 | // .set('user-agent', 'Windows Chrome') 58 | // .expect(302) 59 | // .end(function(err, res) { 60 | // // %20 in path is fixed 61 | // assert.notEqual(res.header['location'].indexOf('csv -H'), -1); 62 | // // %20 in query string is left intact 63 | // assert.notEqual(res.header['location'].indexOf('Research%20Documents'), -1); 64 | // done(); 65 | // }) 66 | // ; 67 | // }); 68 | 69 | // it('should ignore requests that are not from Chrome', function(done){ 70 | // request(app) 71 | // .get(url) 72 | // .expect(200, done) 73 | // ; 74 | // }); 75 | // }); 76 | 77 | describe('none op', function(){ 78 | var url = '/none/?url=' + data_url; 79 | describe('GET ' + url, function(){ 80 | it('should return csv with ' + num_rows + ' rows and ' + num_cols + ' columns (original file)', function(done){ 81 | request(app) 82 | .get(url) 83 | .expect('Content-Type', /plain/) 84 | .expect(200) 85 | .end(function(err, res) { 86 | if (err) done(err); 87 | 88 | csv() 89 | .from.string(res.text) 90 | .on('record', function(row,index){ 91 | assert.equal(row.length, num_cols); 92 | }) 93 | .on('end', function(count) { 94 | assert.equal(count, num_rows); 95 | done(); 96 | }) 97 | ; 98 | }) 99 | ; 100 | }); 101 | }); 102 | }); 103 | 104 | describe('head op', function(){ 105 | var url = '/csv -H/head/?url=' + data_url; 106 | describe('GET ' + url, function(){ 107 | it('should return 10 csv rows', function(done){ 108 | request(app) 109 | .get(url) 110 | .expect('Content-Type', /plain/) 111 | .expect(200) 112 | .end(function(err, res) { 113 | if (err) done(err); 114 | 115 | csv() 116 | .from.string(res.text) 117 | .on('end', function(count) { 118 | assert.equal(count, 10); 119 | done(); 120 | }) 121 | ; 122 | }) 123 | ; 124 | }); 125 | }); 126 | 127 | var num_rows_head = 5; 128 | var url2 = '/csv/head -n ' + num_rows_head + '/?url=' + data_url; 129 | describe('GET ' + url2, function(){ 130 | it('should return ' + (1 + num_rows_head) + ' csv rows', function(done){ 131 | request(app) 132 | .get(url2) 133 | .expect('Content-Type', /plain/) 134 | .expect(200) 135 | .end(function(err, res) { 136 | if (err) done(err); 137 | 138 | csv() 139 | .from.string(res.text) 140 | .on('end', function(count) { 141 | assert.equal(count, (1 + num_rows_head)); 142 | done(); 143 | }) 144 | ; 145 | }) 146 | ; 147 | }); 148 | }); 149 | }); 150 | 151 | describe('tail op', function(){ 152 | var url = '/csv --no-header-row/tail/?url=' + data_url; 153 | describe('GET ' + url, function(){ 154 | it('should return 10 csv rows', function(done){ 155 | request(app) 156 | .get(url) 157 | .expect('Content-Type', /plain/) 158 | .expect(200) 159 | .end(function(err, res) { 160 | if (err) done(err); 161 | 162 | csv() 163 | .from.string(res.text) 164 | .on('end', function(count) { 165 | assert.equal(count, 10); 166 | done(); 167 | }) 168 | ; 169 | }) 170 | ; 171 | }); 172 | }); 173 | 174 | var num_rows_tail = 5; 175 | var url2 = '/csv/tail -n ' + num_rows_tail + '/?url=' + data_url; 176 | describe('GET ' + url2, function(){ 177 | it('should return ' + (1 + num_rows_tail) + ' csv rows', function(done){ 178 | request(app) 179 | .get(url2) 180 | .expect('Content-Type', /plain/) 181 | .expect(200) 182 | .end(function(err, res) { 183 | if (err) done(err); 184 | 185 | csv() 186 | .from.string(res.text) 187 | .on('end', function(count) { 188 | assert.equal(count, (1 + num_rows_tail)); 189 | done(); 190 | }) 191 | ; 192 | }) 193 | ; 194 | }); 195 | }); 196 | 197 | var url3 = '/csv/tail -n +' + num_rows_tail + '/?url=' + data_url; 198 | describe('GET ' + url3, function(){ 199 | it('should return ' + (1 + num_rows - num_rows_tail) + ' csv rows', function(done){ 200 | request(app) 201 | .get(url3) 202 | .expect('Content-Type', /plain/) 203 | .expect(200) 204 | .end(function(err, res) { 205 | if (err) done(err); 206 | 207 | csv() 208 | .from.string(res.text) 209 | .on('end', function(count) { 210 | assert.equal(count, (num_rows - num_rows_tail)); 211 | done(); 212 | }) 213 | ; 214 | }) 215 | ; 216 | }); 217 | }); 218 | }); 219 | 220 | describe('cut op', function(){ 221 | var url = '/csv/cut 0,3/?url=' + data_url; 222 | describe('GET ' + url, function(){ 223 | var num_cols_removed = 2; 224 | it('should return csv with ' + (num_cols - num_cols_removed) + ' columns (' + num_cols_removed + ' removed)', function(done){ 225 | request(app) 226 | .get(url) 227 | .expect('Content-Type', /plain/) 228 | .expect(200) 229 | .end(function(err, res) { 230 | if (err) done(err); 231 | 232 | csv() 233 | .from.string(res.text) 234 | .on('record', function(row,index){ 235 | assert.equal(row.length, (num_cols - num_cols_removed)); 236 | }) 237 | .on('end', function(count) { 238 | done(); 239 | }) 240 | ; 241 | }) 242 | ; 243 | }); 244 | }); 245 | 246 | var url2 = '/csv/cut 0,2:4/?url=' + data_url; 247 | describe('GET ' + url2, function(){ 248 | var num_cols_removed = 4; 249 | it('should return csv with ' + (num_cols - num_cols_removed) + ' columns (' + num_cols_removed + ' removed)', function(done){ 250 | request(app) 251 | .get(url2) 252 | .expect('Content-Type', /plain/) 253 | .expect(200) 254 | .end(function(err, res) { 255 | if (err) done(err); 256 | 257 | csv() 258 | .from.string(res.text) 259 | .on('record', function(row,index){ 260 | assert.equal(row.length, (num_cols - num_cols_removed)); 261 | }) 262 | .on('end', function(count) { 263 | done(); 264 | }) 265 | ; 266 | }) 267 | ; 268 | }); 269 | }); 270 | 271 | var url3 = '/csv/cut 0,2:4 --complement/?url=' + data_url; 272 | describe('GET ' + url2, function(){ 273 | var num_cols_kept = 4; 274 | it('should return csv with ' + num_cols_kept + ' columns', function(done){ 275 | request(app) 276 | .get(url3) 277 | .expect('Content-Type', /plain/) 278 | .expect(200) 279 | .end(function(err, res) { 280 | if (err) done(err); 281 | 282 | csv() 283 | .from.string(res.text) 284 | .on('record', function(row,index){ 285 | assert.equal(row.length, num_cols_kept); 286 | }) 287 | .on('end', function(count) { 288 | done(); 289 | }) 290 | ; 291 | }); 292 | }); 293 | }); 294 | 295 | var url4 = '/csv/cut 3/?url=' + data_url; 296 | describe('GET ' + url, function(){ 297 | var num_cols_removed = 1; 298 | it('should return csv with ' + (num_cols - num_cols_removed) + ' columns (' + num_cols_removed + ' removed)', function(done){ 299 | request(app) 300 | .get(url4) 301 | .expect('Content-Type', /plain/) 302 | .expect(200) 303 | .end(function(err, res) { 304 | if (err) done(err); 305 | 306 | csv() 307 | .from.string(res.text) 308 | .on('record', function(row,index){ 309 | assert.equal(row.length, (num_cols - num_cols_removed)); 310 | }) 311 | .on('end', function(count) { 312 | done(); 313 | }) 314 | ; 315 | }); 316 | }); 317 | }); 318 | }); 319 | 320 | describe('delete op', function(){ 321 | var url = '/csv/delete 10,20/?url=' + data_url; 322 | describe('GET ' + url, function(){ 323 | var num_rows_removed = 2; 324 | it('should return csv with ' + (num_rows - num_rows_removed) + ' rows (' + num_rows_removed + ' removed)', function(done){ 325 | request(app) 326 | .get(url) 327 | .expect('Content-Type', /plain/) 328 | .expect(200) 329 | .end(function(err, res) { 330 | if (err) done(err); 331 | 332 | csv() 333 | .from.string(res.text) 334 | .on('end', function(count) { 335 | assert.equal(count, (num_rows - num_rows_removed)); 336 | done(); 337 | }) 338 | ; 339 | }) 340 | ; 341 | }); 342 | }); 343 | 344 | var url2 = '/csv/delete 1,10:20/?url=' + data_url; 345 | describe('GET ' + url2, function(){ 346 | var num_rows_removed = 12; 347 | it('should return csv with ' + (num_rows - num_rows_removed) + ' rows (' + num_rows_removed + ' removed)', function(done){ 348 | request(app) 349 | .get(url2) 350 | .expect('Content-Type', /plain/) 351 | .expect(200) 352 | .end(function(err, res) { 353 | if (err) done(err); 354 | 355 | csv() 356 | .from.string(res.text) 357 | .on('end', function(count) { 358 | assert.equal(count, (num_rows - num_rows_removed)); 359 | done(); 360 | }) 361 | ; 362 | }) 363 | ; 364 | }); 365 | }); 366 | 367 | var url3 = '/csv/delete 10/?url=' + data_url; 368 | describe('GET ' + url3, function(){ 369 | var num_rows_removed = 1; 370 | it('should return csv with ' + (num_rows - num_rows_removed) + ' rows (' + num_rows_removed + ' removed)', function(done){ 371 | request(app) 372 | .get(url3) 373 | .expect('Content-Type', /plain/) 374 | .expect(200) 375 | .end(function(err, res) { 376 | if (err) done(err); 377 | 378 | csv() 379 | .from.string(res.text) 380 | .on('end', function(count) { 381 | assert.equal(count, (num_rows - num_rows_removed)); 382 | done(); 383 | }) 384 | ; 385 | }); 386 | }); 387 | }); 388 | }); 389 | 390 | describe('grep op', function(){ 391 | var url = '/csv/grep LONDON/?url=' + data_url; 392 | describe('GET ' + url, function(){ 393 | it('should return csv with ' + (num_london_rows + 1) + ' rows - 1 header plus ' + num_london_rows + ' containing the word "LONDON"', function(done){ 394 | request(app) 395 | .get(url) 396 | .expect('Content-Type', /plain/) 397 | .expect(200) 398 | .end(function(err, res) { 399 | if (err) done(err); 400 | 401 | csv() 402 | .from.string(res.text) 403 | .on('record', function(row,index){ 404 | if (index === 0) { 405 | // skip the header row 406 | return; 407 | } 408 | var contains_london = _.some(row, function(val) { 409 | return val.indexOf('LONDON') != -1; 410 | }); 411 | assert.equal(contains_london, true); 412 | }) 413 | .on('end', function(count) { 414 | assert.equal(count, (num_london_rows + 1)); 415 | done(); 416 | }) 417 | ; 418 | }) 419 | ; 420 | }); 421 | }); 422 | 423 | var url2 = '/csv/grep -i London/?url=' + data_url; 424 | describe('GET ' + url, function(){ 425 | it('should return csv with ' + (num_london_rows + 1) + ' rows - 1 header plus ' + num_london_rows + ' containing the word "London" (case-insensitive)', function(done){ 426 | request(app) 427 | .get(url2) 428 | .expect('Content-Type', /plain/) 429 | .expect(200) 430 | .end(function(err, res) { 431 | if (err) done(err); 432 | 433 | csv() 434 | .from.string(res.text) 435 | .on('record', function(row,index){ 436 | if (index === 0) { 437 | // skip the header row 438 | return; 439 | } 440 | var contains_london = _.some(row, function(val) { 441 | return val.indexOf('LONDON') != -1; 442 | }); 443 | assert.equal(contains_london, true); 444 | }) 445 | .on('end', function(count) { 446 | assert.equal(count, (num_london_rows + 1)); 447 | done(); 448 | }) 449 | ; 450 | }) 451 | ; 452 | }); 453 | }); 454 | }); 455 | 456 | describe('strip op', function(){ 457 | var url = '/csv/strip/?url=' + data_url; 458 | var num_non_blank = num_rows - num_blank_rows; 459 | describe('GET ' + url, function(){ 460 | it('should return ' + num_non_blank + ' csv rows, none of which are empty', function(done){ 461 | request(app) 462 | .get(url) 463 | .expect('Content-Type', /plain/) 464 | .expect(200) 465 | .end(function(err, res) { 466 | if (err) done(err); 467 | 468 | csv() 469 | .from.string(res.text) 470 | .on('record', function(row,index){ 471 | var emptyRow = _.every(row, function(val) { 472 | return val === ''; 473 | }); 474 | assert.notEqual(emptyRow, true); 475 | }) 476 | .on('end', function(count) { 477 | assert.equal(count, num_non_blank); 478 | done(); 479 | }) 480 | ; 481 | }) 482 | ; 483 | }); 484 | }); 485 | }); 486 | 487 | describe('outcsv op', function(){ 488 | var url = '/csv/csv --tabs/?url=' + data_url; 489 | describe('GET ' + url, function(){ 490 | it('should return ' + num_cols + ' tab-separated csv columns', function(done){ 491 | request(app) 492 | .get(url) 493 | .expect('Content-Type', /plain/) 494 | .expect(200) 495 | .end(function(err, res) { 496 | if (err) done(err); 497 | 498 | csv() 499 | .from.string(res.text, {delimiter: '\t'}) 500 | .on('record', function(row,index){ 501 | assert.equal(row.length, num_cols); 502 | }) 503 | .on('end', function(count) { 504 | done(); 505 | }) 506 | ; 507 | }) 508 | ; 509 | }); 510 | }); 511 | 512 | var url2 = '/csv/csv --delimiter . -q \'/?url=' + data_url; 513 | describe('GET ' + url, function(){ 514 | it('should return ' + num_cols + ' dot-separated csv columns, with single quote quotes', function(done){ 515 | request(app) 516 | .get(url2) 517 | .expect('Content-Type', /plain/) 518 | .expect(200) 519 | .end(function(err, res) { 520 | if (err) done(err); 521 | 522 | csv() 523 | .from.string(res.text, {delimiter: '.', quote: '\''}) 524 | .on('record', function(row,index){ 525 | assert.equal(row.length, num_cols); 526 | }) 527 | .on('end', function(count) { 528 | done(); 529 | }) 530 | ; 531 | }) 532 | ; 533 | }); 534 | }); 535 | }); 536 | 537 | describe('outhtml op', function(){ 538 | var num_rows_head = 5; 539 | var url = '/csv/head -n ' + num_rows_head + '/html/?url=' + data_url; 540 | describe('GET ' + url, function(){ 541 | it('should return ' + (1 + num_rows_head) + ' html rows', function(done){ 542 | request(app) 543 | .get(url) 544 | .expect('Content-Type', /html/) 545 | .expect(200) 546 | .end(function(err, res) { 547 | if (err) done(err); 548 | 549 | var out = res.text; 550 | var numRows = out.match(/ 2 | 3 | 4 | 5 | 6 | Home - Data Pipes 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |
19 |
20 | 21 |
22 | 25 |
26 |
27 |
28 | 29 |
30 | 33 |
34 |
35 |
36 | 37 |
38 | 41 |
42 |
43 |
44 | 45 |
46 | {% for name in ['html', 'csv'] %} 47 | 52 | {% endfor %} 53 |
54 |
55 |
56 |
57 | 58 |
59 |
60 |
61 |
62 | 63 |
64 | {% if error %} 65 |
{{error}}
66 | {% endif %} 67 | 68 | {% if pipeline %} 69 | 74 | 75 | 76 | {% endif %} 77 |
78 |
79 | 80 | 81 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /views/layout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Home - Data Pipes 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 |
22 |

23 | Data Pipes 24 |

25 | 26 |
27 | 28 |
29 | {% block content %}{% endblock %} 30 |
31 | 32 | 48 | 49 |
50 | 51 | {% block extra_js %}{% endblock %} 52 | 53 | 54 | 55 | 56 | --------------------------------------------------------------------------------