From d9478a9f6d94522c9097d50ad7239de97679a2ed Mon Sep 17 00:00:00 2001 From: Almenon Date: Tue, 13 Feb 2018 06:41:52 -0800 Subject: [PATCH 01/27] #106 CI tests --- .appveyor | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .appveyor diff --git a/.appveyor b/.appveyor new file mode 100644 index 0000000..86caeb7 --- /dev/null +++ b/.appveyor @@ -0,0 +1,24 @@ +# this script based off of https://github.com/tolbertam/mocha-appveyor-reporter/blob/master/appveyor.yml + +install: + # Get the latest stable version of Node.js + - ps: Install-Product node '' + - npm install + +build: off + +test_script: + - npm run appveyorTest + +cache: + - node_modules -> package.json + +notifications: + - provider: Email + to: + - almenon214@gmail.com + on_build_success: false + +skip_commits: + files: + - '**/*.md' \ No newline at end of file From a3cc56b47506170a6e0edbb588043e79825683df Mon Sep 17 00:00:00 2001 From: Almenon Date: Tue, 13 Feb 2018 06:44:27 -0800 Subject: [PATCH 02/27] #106 renaming correctly --- .appveyor => appveyor.yml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .appveyor => appveyor.yml (100%) diff --git a/.appveyor b/appveyor.yml similarity index 100% rename from .appveyor rename to appveyor.yml From 26eb3e2c1856e762b985d2b143a6cf9a75638103 Mon Sep 17 00:00:00 2001 From: Almenon Date: Tue, 13 Feb 2018 06:48:08 -0800 Subject: [PATCH 03/27] adding default python gitignore --- .gitignore | 111 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/.gitignore b/.gitignore index 3c3629e..a764a69 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,112 @@ node_modules +npm-debug.log.* +.vscode +personal + +# DEFAULT PYTON GITIGNORE + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +.static_storage/ +.media/ +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ \ No newline at end of file From 85fdecaa4d4cfda917ebb29d0e18572994ef0949 Mon Sep 17 00:00:00 2001 From: Almenon Date: Tue, 13 Feb 2018 06:54:15 -0800 Subject: [PATCH 04/27] #106 added appveyor reporter --- package-lock.json | 799 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 8 +- 2 files changed, 805 insertions(+), 2 deletions(-) create mode 100644 package-lock.json diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..381e20a --- /dev/null +++ b/package-lock.json @@ -0,0 +1,799 @@ +{ + "name": "python-shell", + "version": "0.4.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@types/mocha": { + "version": "2.2.48", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-2.2.48.tgz", + "integrity": "sha512-nlK/iyETgafGli8Zh9zJVCTicvU3iajSkRwOh3Hhiva598CMqNJ4NcVCGMTGKpGpTYj/9R8RLzS9NAykSSCqGw==", + "dev": true + }, + "@types/node": { + "version": "9.4.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-9.4.5.tgz", + "integrity": "sha512-DvC7bzO5797bkApgukxouHmkOdYN2D0yL5olw0RncDpXUa6n39qTVsUi/5g2QJjPgl8qn4zh+4h0sofNoWGLRg==", + "dev": true + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "asn1": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=", + "dev": true + }, + "assert-plus": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", + "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=", + "dev": true + }, + "async": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz", + "integrity": "sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==", + "dev": true, + "requires": { + "lodash": "4.17.5" + } + }, + "aws-sign2": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", + "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=", + "dev": true + }, + "aws4": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", + "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=", + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", + "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", + "dev": true, + "optional": true, + "requires": { + "tweetnacl": "0.14.5" + } + }, + "bl": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.1.2.tgz", + "integrity": "sha1-/cqHGplxOqANGeO7ukHER4emU5g=", + "dev": true, + "requires": { + "readable-stream": "2.0.6" + } + }, + "boom": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", + "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", + "dev": true, + "requires": { + "hoek": "2.16.3" + } + }, + "caseless": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", + "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.2", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + }, + "dependencies": { + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "combined-stream": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", + "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", + "dev": true, + "requires": { + "delayed-stream": "1.0.0" + } + }, + "commander": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.3.0.tgz", + "integrity": "sha1-/UMOiJgy7DU7ms0d4hfBHLPu+HM=", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "cryptiles": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", + "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", + "dev": true, + "requires": { + "boom": "2.10.1" + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "requires": { + "assert-plus": "1.0.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + } + } + }, + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "dev": true, + "requires": { + "ms": "0.7.1" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "depd": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz", + "integrity": "sha1-4b2Cxqq2ztlluXuIsX7T5SjKGMM=", + "dev": true + }, + "diff": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-1.4.0.tgz", + "integrity": "sha1-fyjS657nsVqX79ic5j3P2qPMur8=", + "dev": true + }, + "ecc-jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", + "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "dev": true, + "optional": true, + "requires": { + "jsbn": "0.1.1" + } + }, + "escape-string-regexp": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.2.tgz", + "integrity": "sha1-Tbwv5nTnGUnK8/smlc5/LcHZqNE=", + "dev": true + }, + "extend": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=", + "dev": true + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "form-data": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.1.tgz", + "integrity": "sha1-rjFduaSQf6BlUCMEpm13M0de43w=", + "dev": true, + "requires": { + "async": "2.6.0", + "combined-stream": "1.0.5", + "mime-types": "2.1.17" + } + }, + "generate-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz", + "integrity": "sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ=", + "dev": true + }, + "generate-object-property": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", + "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", + "dev": true, + "requires": { + "is-property": "1.0.2" + } + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "requires": { + "assert-plus": "1.0.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + } + } + }, + "glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", + "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0=", + "dev": true, + "requires": { + "inherits": "2.0.3", + "minimatch": "0.3.0" + } + }, + "growl": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", + "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=", + "dev": true + }, + "har-validator": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz", + "integrity": "sha1-zcvAgYgmWtEZtqWnyKtw7s+10n0=", + "dev": true, + "requires": { + "chalk": "1.1.3", + "commander": "2.14.1", + "is-my-json-valid": "2.17.1", + "pinkie-promise": "2.0.1" + }, + "dependencies": { + "commander": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.14.1.tgz", + "integrity": "sha512-+YR16o3rK53SmWHU3rEM3tPAh2rwb1yPcQX5irVn7mb0gXbwuCCrnkbV5+PBfETdfg1vui07nM6PCG1zndcjQw==", + "dev": true + } + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "hawk": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", + "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", + "dev": true, + "requires": { + "boom": "2.10.1", + "cryptiles": "2.0.5", + "hoek": "2.16.3", + "sntp": "1.0.9" + } + }, + "hoek": { + "version": "2.16.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", + "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=", + "dev": true + }, + "http-signature": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", + "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", + "dev": true, + "requires": { + "assert-plus": "0.2.0", + "jsprim": "1.4.1", + "sshpk": "1.13.1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "is-my-json-valid": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.17.1.tgz", + "integrity": "sha512-Q2khNw+oBlWuaYvEEHtKSw/pCxD2L5Rc1C+UQme9X6JdRDh7m5D7HkozA0qa3DUkQ6VzCnEm8mVIQPyIRkI5sQ==", + "dev": true, + "requires": { + "generate-function": "2.0.0", + "generate-object-property": "1.2.0", + "jsonpointer": "4.0.1", + "xtend": "4.0.1" + } + }, + "is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", + "dev": true + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "jade": { + "version": "0.26.3", + "resolved": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz", + "integrity": "sha1-jxDXl32NefL2/4YqgbBRPMslaGw=", + "dev": true, + "requires": { + "commander": "0.6.1", + "mkdirp": "0.3.0" + }, + "dependencies": { + "commander": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz", + "integrity": "sha1-+mihT2qUXVTbvlDYzbMyDp47GgY=", + "dev": true + }, + "mkdirp": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz", + "integrity": "sha1-G79asbqCevI1dRQ0kEJkVfSB/h4=", + "dev": true + } + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true, + "optional": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "jsonpointer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", + "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=", + "dev": true + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + } + } + }, + "lodash": { + "version": "4.17.5", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz", + "integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==", + "dev": true + }, + "lru-cache": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", + "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=", + "dev": true + }, + "mime-db": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", + "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=", + "dev": true + }, + "mime-types": { + "version": "2.1.17", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", + "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", + "dev": true, + "requires": { + "mime-db": "1.30.0" + } + }, + "minimatch": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", + "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=", + "dev": true, + "requires": { + "lru-cache": "2.7.3", + "sigmund": "1.0.1" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "mocha": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-2.5.3.tgz", + "integrity": "sha1-FhvlvetJZ3HrmzV0UFC2IrWu/Fg=", + "dev": true, + "requires": { + "commander": "2.3.0", + "debug": "2.2.0", + "diff": "1.4.0", + "escape-string-regexp": "1.0.2", + "glob": "3.2.11", + "growl": "1.9.2", + "jade": "0.26.3", + "mkdirp": "0.5.1", + "supports-color": "1.2.0", + "to-iso-string": "0.0.2" + } + }, + "mocha-appveyor-reporter": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/mocha-appveyor-reporter/-/mocha-appveyor-reporter-0.4.0.tgz", + "integrity": "sha1-gpOC/8Bla2Z+e+ZQoJSgba69Uk8=", + "dev": true, + "requires": { + "request-json": "0.6.2" + } + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", + "dev": true + }, + "node-uuid": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", + "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=", + "dev": true + }, + "oauth-sign": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "2.0.4" + } + }, + "process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", + "dev": true + }, + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + }, + "qs": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.2.3.tgz", + "integrity": "sha1-HPyyXBCpsrSDBT/zn138kjOQjP4=", + "dev": true + }, + "readable-stream": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", + "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "string_decoder": "0.10.31", + "util-deprecate": "1.0.2" + } + }, + "request": { + "version": "2.74.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.74.0.tgz", + "integrity": "sha1-dpPKdou7DqXIzgjAhKRe+gW4kqs=", + "dev": true, + "requires": { + "aws-sign2": "0.6.0", + "aws4": "1.6.0", + "bl": "1.1.2", + "caseless": "0.11.0", + "combined-stream": "1.0.5", + "extend": "3.0.1", + "forever-agent": "0.6.1", + "form-data": "1.0.1", + "har-validator": "2.0.6", + "hawk": "3.1.3", + "http-signature": "1.1.1", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.17", + "node-uuid": "1.4.8", + "oauth-sign": "0.8.2", + "qs": "6.2.3", + "stringstream": "0.0.5", + "tough-cookie": "2.3.3", + "tunnel-agent": "0.4.3" + } + }, + "request-json": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/request-json/-/request-json-0.6.2.tgz", + "integrity": "sha1-KMdtBdYMU8nhjUCXywFyO5UGkyI=", + "dev": true, + "requires": { + "depd": "1.1.0", + "request": "2.74.0" + } + }, + "should": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/should/-/should-6.0.3.tgz", + "integrity": "sha1-2u4weGpVdmL7x3TACNfFh5Htsdk=", + "dev": true, + "requires": { + "should-equal": "0.3.1", + "should-format": "0.0.7", + "should-type": "0.0.4" + } + }, + "should-equal": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-0.3.1.tgz", + "integrity": "sha1-vY6pemdI45+tR2o75v1y68LnK/A=", + "dev": true, + "requires": { + "should-type": "0.0.4" + } + }, + "should-format": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/should-format/-/should-format-0.0.7.tgz", + "integrity": "sha1-Hi74a9kdqcLgQSM1tWq6vZov3hI=", + "dev": true, + "requires": { + "should-type": "0.0.4" + } + }, + "should-type": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/should-type/-/should-type-0.0.4.tgz", + "integrity": "sha1-ATKgVBemEmhmQmrPEW8e1WI6XNA=", + "dev": true + }, + "sigmund": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", + "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=", + "dev": true + }, + "sntp": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", + "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", + "dev": true, + "requires": { + "hoek": "2.16.3" + } + }, + "sshpk": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", + "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", + "dev": true, + "requires": { + "asn1": "0.2.3", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.1", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.1", + "getpass": "0.1.7", + "jsbn": "0.1.1", + "tweetnacl": "0.14.5" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + } + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "stringstream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", + "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=", + "dev": true + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "supports-color": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-1.2.0.tgz", + "integrity": "sha1-/x7R5hFp0Gs88tWI4YixjYhH4X4=", + "dev": true + }, + "to-iso-string": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/to-iso-string/-/to-iso-string-0.0.2.tgz", + "integrity": "sha1-TcGeZk38y+Jb2NtQiwDG2hWCVdE=", + "dev": true + }, + "tough-cookie": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz", + "integrity": "sha1-C2GKVWW23qkL80JdBNVe3EdadWE=", + "dev": true, + "requires": { + "punycode": "1.4.1" + } + }, + "tunnel-agent": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", + "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=", + "dev": true + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true, + "optional": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "1.3.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + } + } + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", + "dev": true + } + } +} diff --git a/package.json b/package.json index 4306866..4299061 100644 --- a/package.json +++ b/package.json @@ -6,12 +6,16 @@ "python" ], "scripts": { - "test": "mocha -R spec" + "test": "mocha -R spec", + "appveyorTest": "mocha --ui tdd --reporter mocha-appveyor-reporter test/*.js" }, "dependencies": {}, "devDependencies": { + "@types/mocha": "^2.2.44", + "@types/node": "^9.3.0", "should": "^6.0.0", - "mocha": "^2.2.5" + "mocha": "^2.2.5", + "mocha-appveyor-reporter": "^0.4.0" }, "repository": { "type": "git", From dccda4755ed2bb9f1db0ac1bd82e5aab182de221 Mon Sep 17 00:00:00 2001 From: Almenon Date: Tue, 13 Feb 2018 06:57:55 -0800 Subject: [PATCH 05/27] #107 fixing failing unit tests on windows --- test/test-python-shell.js | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/test/test-python-shell.js b/test/test-python-shell.js index 1722fac..fce1245 100644 --- a/test/test-python-shell.js +++ b/test/test-python-shell.js @@ -1,5 +1,7 @@ var should = require('should'); var PythonShell = require('..'); +var path = require('path') +const newline = require('os').EOL describe('PythonShell', function () { @@ -10,7 +12,7 @@ describe('PythonShell', function () { describe('#ctor(script, options)', function () { it('should spawn a Python process', function (done) { var pyshell = new PythonShell('exit-code.py'); - pyshell.command.should.eql(['test/python/exit-code.py']); + pyshell.command.should.eql(['test' + path.sep + 'python' + path.sep + 'exit-code.py']); pyshell.terminated.should.be.false; pyshell.end(function (err) { if (err) return done(err); @@ -22,14 +24,14 @@ describe('PythonShell', function () { var pyshell = new PythonShell('exit-code.py', { pythonOptions: '-u' }); - pyshell.command.should.eql(['-u', 'test/python/exit-code.py']); + pyshell.command.should.eql(['-u', 'test' + path.sep + 'python' + path.sep + 'exit-code.py']); pyshell.end(done); }); it('should spawn a Python process with script arguments', function (done) { var pyshell = new PythonShell('echo_args.py', { args: ['hello', 'world'] }); - pyshell.command.should.eql(['test/python/echo_args.py', 'hello', 'world']); + pyshell.command.should.eql(['test' + path.sep + 'python' + path.sep + 'echo_args.py', 'hello', 'world']); pyshell.end(done); }); }); @@ -119,7 +121,7 @@ describe('PythonShell', function () { }); pyshell.send('hello').send('world').end(function (err) { if (err) return done(err); - output.should.be.exactly('hello\nworld\n'); + output.should.be.exactly('hello'+newline+'world'+newline); done(); }); }); @@ -133,7 +135,7 @@ describe('PythonShell', function () { }); pyshell.send({ a: 'b' }).send(null).send([1, 2, 3]).end(function (err) { if (err) return done(err); - output.should.be.exactly('{"a": "b"}\nnull\n[1, 2, 3]\n'); + output.should.be.exactly('{"a": "b"}'+newline+'null'+newline+'[1, 2, 3]'+newline); done(); }); }); @@ -149,7 +151,7 @@ describe('PythonShell', function () { }); pyshell.send('hello').send('world').end(function (err) { if (err) return done(err); - output.should.be.exactly('HELLO\nWORLD\n'); + output.should.be.exactly('HELLO'+newline+'WORLD'+newline+''); done(); }); }); @@ -205,7 +207,7 @@ describe('PythonShell', function () { pyshell.on('message', function (message) { message.should.be.an.Object; message.should.eql({ a: true }); - }).receive('{"a"').receive(':').receive('true}\n').end(done); + }).receive('{"a"').receive(':').receive('true}'+newline+'').end(done); }); it('should not be invoked when mode is "binary"', function (done) { var pyshell = new PythonShell('echo_args.py', { @@ -281,8 +283,8 @@ describe('PythonShell', function () { var pyshell = new PythonShell('error.py'); pyshell.on('error', function (err) { err.stack.should.containEql('----- Python Traceback -----'); - err.stack.should.containEql('File "test/python/error.py", line 6'); - err.stack.should.containEql('File "test/python/error.py", line 4'); + err.stack.should.containEql('File "test' + path.sep + 'python' + path.sep + 'error.py", line 4'); + err.stack.should.containEql('File "test' + path.sep + 'python' + path.sep + 'error.py", line 6'); done(); }); }); From 5ab0e1b421db209baec544441274255a38e6db07 Mon Sep 17 00:00:00 2001 From: Almenon Date: Tue, 13 Feb 2018 07:13:21 -0800 Subject: [PATCH 06/27] #108 fixing unit tests on python 3 --- test/python/echo_args.py | 2 +- test/python/echo_json.py | 2 +- test/python/echo_text.py | 2 +- test/python/error.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/python/echo_args.py b/test/python/echo_args.py index e5a8a86..9676d59 100644 --- a/test/python/echo_args.py +++ b/test/python/echo_args.py @@ -2,4 +2,4 @@ # simple argument echo script for v in sys.argv[1:]: - print v + print(v) diff --git a/test/python/echo_json.py b/test/python/echo_json.py index aa95d68..37b9766 100644 --- a/test/python/echo_json.py +++ b/test/python/echo_json.py @@ -2,4 +2,4 @@ # simple JSON echo script for line in sys.stdin: - print json.dumps(json.loads(line)) + print(json.dumps(json.loads(line))) diff --git a/test/python/echo_text.py b/test/python/echo_text.py index 9dd2901..04e69d0 100644 --- a/test/python/echo_text.py +++ b/test/python/echo_text.py @@ -2,4 +2,4 @@ # simple JSON echo script for line in sys.stdin: - print line[:-1] + print(line[:-1]) diff --git a/test/python/error.py b/test/python/error.py index b5ebc3f..32f03aa 100644 --- a/test/python/error.py +++ b/test/python/error.py @@ -1,6 +1,6 @@ # simple error script def divide_by_zero(): - print 1/0 + print(1/0) divide_by_zero() From 9e5c77a03120425a8fcc2de81bba780dd030392c Mon Sep 17 00:00:00 2001 From: Almenon Date: Tue, 13 Feb 2018 07:57:43 -0800 Subject: [PATCH 07/27] upgraded mocha to try to avoid appveyor error --- package-lock.json | 268 ++++++++++++++++++++++++-------------- package.json | 6 +- test/test-python-shell.js | 4 +- 3 files changed, 173 insertions(+), 105 deletions(-) diff --git a/package-lock.json b/package-lock.json index 381e20a..ec49ee1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -61,6 +61,12 @@ "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=", "dev": true }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, "bcrypt-pbkdf": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", @@ -89,6 +95,22 @@ "hoek": "2.16.3" } }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "browser-stdout": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", + "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", + "dev": true + }, "caseless": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", @@ -126,9 +148,15 @@ } }, "commander": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.3.0.tgz", - "integrity": "sha1-/UMOiJgy7DU7ms0d4hfBHLPu+HM=", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", + "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, "core-util-is": { @@ -164,12 +192,12 @@ } }, "debug": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", - "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "dev": true, "requires": { - "ms": "0.7.1" + "ms": "2.0.0" } }, "delayed-stream": { @@ -185,9 +213,9 @@ "dev": true }, "diff": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-1.4.0.tgz", - "integrity": "sha1-fyjS657nsVqX79ic5j3P2qPMur8=", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.3.1.tgz", + "integrity": "sha512-MKPHZDMB0o6yHyDryUOScqZibp914ksXwAMYMTHj6KO8UeKsRYNJD3oNCKjTqZon+V488P7N/HzXF8t7ZR95ww==", "dev": true }, "ecc-jsbn": { @@ -235,6 +263,12 @@ "mime-types": "2.1.17" } }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, "generate-function": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz", @@ -268,19 +302,23 @@ } }, "glob": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", - "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0=", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", "dev": true, "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", "inherits": "2.0.3", - "minimatch": "0.3.0" + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } }, "growl": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", - "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=", + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.3.tgz", + "integrity": "sha512-hKlsbA5Vu3xsh1Cg3J7jSmX/WaW6A5oBeqzM88oNbCRQFz+zUaXm6yxS4RVytp1scBoJzSYl4YAEOQIt6O8V1Q==", "dev": true }, "har-validator": { @@ -312,6 +350,12 @@ "ansi-regex": "2.1.1" } }, + "has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "dev": true + }, "hawk": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", @@ -324,6 +368,12 @@ "sntp": "1.0.9" } }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true + }, "hoek": { "version": "2.16.3", "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", @@ -341,6 +391,16 @@ "sshpk": "1.13.1" } }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", @@ -383,30 +443,6 @@ "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", "dev": true }, - "jade": { - "version": "0.26.3", - "resolved": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz", - "integrity": "sha1-jxDXl32NefL2/4YqgbBRPMslaGw=", - "dev": true, - "requires": { - "commander": "0.6.1", - "mkdirp": "0.3.0" - }, - "dependencies": { - "commander": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz", - "integrity": "sha1-+mihT2qUXVTbvlDYzbMyDp47GgY=", - "dev": true - }, - "mkdirp": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz", - "integrity": "sha1-G79asbqCevI1dRQ0kEJkVfSB/h4=", - "dev": true - } - } - }, "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", @@ -458,12 +494,6 @@ "integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==", "dev": true }, - "lru-cache": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", - "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=", - "dev": true - }, "mime-db": { "version": "1.30.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", @@ -480,13 +510,12 @@ } }, "minimatch": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", - "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, "requires": { - "lru-cache": "2.7.3", - "sigmund": "1.0.1" + "brace-expansion": "1.1.11" } }, "minimist": { @@ -505,21 +534,29 @@ } }, "mocha": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-2.5.3.tgz", - "integrity": "sha1-FhvlvetJZ3HrmzV0UFC2IrWu/Fg=", - "dev": true, - "requires": { - "commander": "2.3.0", - "debug": "2.2.0", - "diff": "1.4.0", - "escape-string-regexp": "1.0.2", - "glob": "3.2.11", - "growl": "1.9.2", - "jade": "0.26.3", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.0.0.tgz", + "integrity": "sha512-ukB2dF+u4aeJjc6IGtPNnJXfeby5d4ZqySlIBT0OEyva/DrMjVm5HkQxKnHDLKEfEQBsEnwTg9HHhtPHJdTd8w==", + "dev": true, + "requires": { + "browser-stdout": "1.3.0", + "commander": "2.11.0", + "debug": "3.1.0", + "diff": "3.3.1", + "escape-string-regexp": "1.0.5", + "glob": "7.1.2", + "growl": "1.10.3", + "he": "1.1.1", "mkdirp": "0.5.1", - "supports-color": "1.2.0", - "to-iso-string": "0.0.2" + "supports-color": "4.4.0" + }, + "dependencies": { + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + } } }, "mocha-appveyor-reporter": { @@ -532,9 +569,9 @@ } }, "ms": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", - "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, "node-uuid": { @@ -549,6 +586,21 @@ "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", "dev": true }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, "pinkie": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", @@ -636,44 +688,57 @@ } }, "should": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/should/-/should-6.0.3.tgz", - "integrity": "sha1-2u4weGpVdmL7x3TACNfFh5Htsdk=", + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/should/-/should-13.2.1.tgz", + "integrity": "sha512-l+/NwEMO+DcstsHEwPHRHzC9j4UOE3VQwJGcMWSsD/vqpqHbnQ+1iSHy64Ihmmjx1uiRPD9pFadTSc3MJtXAgw==", "dev": true, "requires": { - "should-equal": "0.3.1", - "should-format": "0.0.7", - "should-type": "0.0.4" + "should-equal": "2.0.0", + "should-format": "3.0.3", + "should-type": "1.4.0", + "should-type-adaptors": "1.1.0", + "should-util": "1.0.0" } }, "should-equal": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-0.3.1.tgz", - "integrity": "sha1-vY6pemdI45+tR2o75v1y68LnK/A=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-2.0.0.tgz", + "integrity": "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==", "dev": true, "requires": { - "should-type": "0.0.4" + "should-type": "1.4.0" } }, "should-format": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/should-format/-/should-format-0.0.7.tgz", - "integrity": "sha1-Hi74a9kdqcLgQSM1tWq6vZov3hI=", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz", + "integrity": "sha1-m/yPdPo5IFxT04w01xcwPidxJPE=", "dev": true, "requires": { - "should-type": "0.0.4" + "should-type": "1.4.0", + "should-type-adaptors": "1.1.0" } }, "should-type": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/should-type/-/should-type-0.0.4.tgz", - "integrity": "sha1-ATKgVBemEmhmQmrPEW8e1WI6XNA=", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz", + "integrity": "sha1-B1bYzoRt/QmEOmlHcZ36DUz/XPM=", "dev": true }, - "sigmund": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", - "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=", + "should-type-adaptors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz", + "integrity": "sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==", + "dev": true, + "requires": { + "should-type": "1.4.0", + "should-util": "1.0.0" + } + }, + "should-util": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/should-util/-/should-util-1.0.0.tgz", + "integrity": "sha1-yYzaN0qmsZDfi6h8mInCtNtiAGM=", "dev": true }, "sntp": { @@ -731,16 +796,13 @@ } }, "supports-color": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-1.2.0.tgz", - "integrity": "sha1-/x7R5hFp0Gs88tWI4YixjYhH4X4=", - "dev": true - }, - "to-iso-string": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/to-iso-string/-/to-iso-string-0.0.2.tgz", - "integrity": "sha1-TcGeZk38y+Jb2NtQiwDG2hWCVdE=", - "dev": true + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", + "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", + "dev": true, + "requires": { + "has-flag": "2.0.0" + } }, "tough-cookie": { "version": "2.3.3", @@ -789,6 +851,12 @@ } } }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, "xtend": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", diff --git a/package.json b/package.json index 4299061..7586350 100644 --- a/package.json +++ b/package.json @@ -11,10 +11,10 @@ }, "dependencies": {}, "devDependencies": { - "@types/mocha": "^2.2.44", + "@types/mocha": "^2.2.48", "@types/node": "^9.3.0", - "should": "^6.0.0", - "mocha": "^2.2.5", + "should": "^13.2.1", + "mocha": "^5.0.0", "mocha-appveyor-reporter": "^0.4.0" }, "repository": { diff --git a/test/test-python-shell.js b/test/test-python-shell.js index fce1245..2610f15 100644 --- a/test/test-python-shell.js +++ b/test/test-python-shell.js @@ -42,7 +42,7 @@ describe('PythonShell', function () { args: ['hello', 'world'] }, function (err, results) { if (err) return done(err); - results.should.be.an.Array.and.have.lengthOf(2); + results.should.be.an.Array().and.have.lengthOf(2); results.should.eql(['hello', 'world']); done(); }); @@ -101,7 +101,7 @@ describe('PythonShell', function () { args: ['hello', 'world'] }, function (err, results) { if (err) return done(err); - results.should.be.an.Array.and.have.lengthOf(2); + results.should.be.an.Array().and.have.lengthOf(2); results.should.eql(['hello', 'world']); callback(); }); From ed7036cc53dab4ce191fed85633eed731a8367b1 Mon Sep 17 00:00:00 2001 From: Almenon Date: Tue, 13 Feb 2018 08:49:20 -0800 Subject: [PATCH 08/27] fixed #109 hardcoded newlines --- index.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/index.js b/index.js index ec97180..8d14cd0 100644 --- a/index.js +++ b/index.js @@ -2,6 +2,7 @@ var EventEmitter = require('events').EventEmitter; var path = require('path'); var util = require('util'); var spawn = require('child_process').spawn; +var newline = require('os').EOL; function toArray(source) { if (typeof source === 'undefined' || source === null) { @@ -179,13 +180,13 @@ PythonShell.prototype.parseError = function (data) { if (/^Traceback/.test(text)) { // traceback data is available - var lines = (''+data).trim().split(/\n/g); + var lines = (''+data).trim().split(new RegExp(newline, 'g')); var exception = lines.pop(); error = new Error(exception); error.traceback = data; // extend stack trace - error.stack += '\n ----- Python Traceback -----\n '; - error.stack += lines.slice(1).join('\n '); + error.stack += newline+' ----- Python Traceback -----'+newline+' '; + error.stack += lines.slice(1).join(newline+' '); } else { // otherwise, create a simpler error with stderr contents error = new Error(text); @@ -202,7 +203,7 @@ PythonShell.prototype.parseError = function (data) { */ PythonShell.prototype.send = function (message) { var data = this.formatter ? this.formatter(message) : message; - if (this.mode !== 'binary') data += '\n'; + if (this.mode !== 'binary') data += newline; this.stdin.write(data); return this; }; @@ -215,7 +216,7 @@ PythonShell.prototype.send = function (message) { */ PythonShell.prototype.receive = function (data) { var self = this; - var parts = (''+data).split(/\n/g); + var parts = (''+data).split(new RegExp(newline,'g')); if (parts.length === 1) { // an incomplete record, keep buffering From c2ba5c2a822501c6181163bf25582d9caa3f0f8f Mon Sep 17 00:00:00 2001 From: Almenon Date: Tue, 13 Feb 2018 09:04:22 -0800 Subject: [PATCH 09/27] #108 making unit tests p3 compatible --- test/test-python-shell.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test-python-shell.js b/test/test-python-shell.js index 2610f15..1777596 100644 --- a/test/test-python-shell.js +++ b/test/test-python-shell.js @@ -261,7 +261,7 @@ describe('PythonShell', function () { it('should emit error when data is written to stderr', function (done) { var pyshell = new PythonShell('error.py'); pyshell.on('error', function (err) { - err.message.should.be.exactly('ZeroDivisionError: integer division or modulo by zero'); + err.message.should.be.equalOneOf('ZeroDivisionError: integer division or modulo by zero','ZeroDivisionError: division by zero'); err.should.have.property('traceback'); err.traceback.should.containEql('Traceback (most recent call last)'); done(); From 770b26d155c17a1488d2cdb9bfbbfb749fcba220 Mon Sep 17 00:00:00 2001 From: Almenon Date: Tue, 13 Feb 2018 09:08:16 -0800 Subject: [PATCH 10/27] #106 adding appveyor badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 66d690c..7652b12 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# python-shell +# python-shell [![Build status](https://ci.appveyor.com/api/projects/status/m8e3h53vvxg5wb2q?svg=true)](https://ci.appveyor.com/project/Almenon/python-shell) A simple way to run Python scripts from Node.js with basic but efficient inter-process communication and better error handling. From df47458222f2d75183e612e7ec072cc96683b0e5 Mon Sep 17 00:00:00 2001 From: Almenon Date: Tue, 13 Feb 2018 20:17:00 -0800 Subject: [PATCH 11/27] fixed #85 adding explanation for -u --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7652b12..67747ac 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ var PythonShell = require('python-shell'); var options = { mode: 'text', pythonPath: 'path/to/python', - pythonOptions: ['-u'], + pythonOptions: ['-u'], // get print results in real-time scriptPath: 'path/to/my/scripts', args: ['value1', 'value2', 'value3'] }; From f2677804bbb0e6cb6809d4eeb6fadea0c0ec6fb7 Mon Sep 17 00:00:00 2001 From: Charles Crawford Date: Tue, 7 Aug 2018 19:37:05 -0700 Subject: [PATCH 12/27] use python3 binary on unix systems --- index.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index 8d14cd0..da5d73a 100644 --- a/index.js +++ b/index.js @@ -45,9 +45,12 @@ var PythonShell = function (script, options) { var self = this; var errorData = ''; EventEmitter.call(this); - + options = extend({}, PythonShell.defaultOptions, options); - var pythonPath = options.pythonPath || 'python'; + var pythonPath; + if (!options.pythonPath) { + pythonPath = process.platform != "win32" ? "python3" : "python" + } else pythonPath = options.pythonPath; var pythonOptions = toArray(options.pythonOptions); var scriptArgs = toArray(options.args); From c8928c57747dc8414cdef49c786754a5275edb6c Mon Sep 17 00:00:00 2001 From: Almenon Date: Tue, 7 Aug 2018 20:06:03 -0700 Subject: [PATCH 13/27] #134 converting index to typescript --- .gitignore | 4 +- index.js | 260 ---------------------------------------- index.ts | 293 ++++++++++++++++++++++++++++++++++++++++++++++ package-lock.json | 206 ++++++++++++++++---------------- package.json | 12 +- tsconfig.json | 15 +++ 6 files changed, 424 insertions(+), 366 deletions(-) delete mode 100644 index.js create mode 100644 index.ts create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore index a764a69..2244430 100644 --- a/.gitignore +++ b/.gitignore @@ -2,8 +2,10 @@ node_modules npm-debug.log.* .vscode personal +*.js +*.js.map -# DEFAULT PYTON GITIGNORE +# DEFAULT PYTHON GITIGNORE # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/index.js b/index.js deleted file mode 100644 index 8d14cd0..0000000 --- a/index.js +++ /dev/null @@ -1,260 +0,0 @@ -var EventEmitter = require('events').EventEmitter; -var path = require('path'); -var util = require('util'); -var spawn = require('child_process').spawn; -var newline = require('os').EOL; - -function toArray(source) { - if (typeof source === 'undefined' || source === null) { - return []; - } else if (!Array.isArray(source)) { - return [source]; - } - return source; -} - -function extend(obj) { - Array.prototype.slice.call(arguments, 1).forEach(function (source) { - if (source) { - for (var key in source) { - obj[key] = source[key]; - } - } - }); - return obj; -} - -/** - * An interactive Python shell exchanging data through stdio - * @param {string} script The python script to execute - * @param {object} [options] The launch options (also passed to child_process.spawn) - * @constructor - */ -var PythonShell = function (script, options) { - - function resolve(type, val) { - if (typeof val === 'string') { - // use a built-in function using its name - return PythonShell[type][val]; - } else if (typeof val === 'function') { - // use a custom function - return val; - } - } - - var self = this; - var errorData = ''; - EventEmitter.call(this); - - options = extend({}, PythonShell.defaultOptions, options); - var pythonPath = options.pythonPath || 'python'; - var pythonOptions = toArray(options.pythonOptions); - var scriptArgs = toArray(options.args); - - this.script = path.join(options.scriptPath || './', script); - this.command = pythonOptions.concat(this.script, scriptArgs); - this.mode = options.mode || 'text'; - this.formatter = resolve('format', options.formatter || this.mode); - this.parser = resolve('parse', options.parser || this.mode); - this.terminated = false; - this.childProcess = spawn(pythonPath, this.command, options); - - ['stdout', 'stdin', 'stderr'].forEach(function (name) { - self[name] = self.childProcess[name]; - self.parser && self[name].setEncoding(options.encoding || 'utf8'); - }); - - // parse incoming data on stdout - if (this.parser) { - this.stdout.on('data', PythonShell.prototype.receive.bind(this)); - } - - // listen to stderr and emit errors for incoming data - this.stderr.on('data', function (data) { - errorData += ''+data; - }); - - this.stderr.on('end', function(){ - self.stderrHasEnded = true - terminateIfNeeded(); - }) - - this.stdout.on('end', function(){ - self.stdoutHasEnded = true - terminateIfNeeded(); - }) - - this.childProcess.on('exit', function (code,signal) { - self.exitCode = code; - self.exitSignal = signal; - terminateIfNeeded(); - }); - - function terminateIfNeeded() { - if(!self.stderrHasEnded || !self.stdoutHasEnded || (self.exitCode == null && self.exitSignal == null)) return; - - var err; - if (errorData || (self.exitCode && self.exitCode !== 0)) { - if (errorData) { - err = self.parseError(errorData); - } else { - err = new Error('process exited with code ' + self.exitCode); - } - err = extend(err, { - executable: pythonPath, - options: pythonOptions.length ? pythonOptions : null, - script: self.script, - args: scriptArgs.length ? scriptArgs : null, - exitCode: self.exitCode - }); - // do not emit error if only a callback is used - if (self.listeners('error').length || !self._endCallback) { - self.emit('error', err); - } - } - - self.terminated = true; - self.emit('close'); - self._endCallback && self._endCallback(err,self.exitCode,self.exitSignal); - }; -}; -util.inherits(PythonShell, EventEmitter); - -// allow global overrides for options -PythonShell.defaultOptions = {}; - -// built-in formatters -PythonShell.format = { - text: function toText(data) { - if (!data) return ''; - else if (typeof data !== 'string') return data.toString(); - return data; - }, - json: function toJson(data) { - return JSON.stringify(data); - } -}; - -// built-in parsers -PythonShell.parse = { - text: function asText(data) { - return data; - }, - json: function asJson(data) { - return JSON.parse(data); - } -}; - -/** - * Runs a Python script and returns collected messages - * @param {string} script The script to execute - * @param {Object} options The execution options - * @param {Function} callback The callback function to invoke with the script results - * @return {PythonShell} The PythonShell instance - */ -PythonShell.run = function (script, options, callback) { - if (typeof options === 'function') { - callback = options; - options = null; - } - - var pyshell = new PythonShell(script, options); - var output = []; - - return pyshell.on('message', function (message) { - output.push(message); - }).end(function (err) { - if (err) return callback(err); - return callback(null, output.length ? output : null); - }); -}; - -/** - * Parses an error thrown from the Python process through stderr - * @param {string|Buffer} data The stderr contents to parse - * @return {Error} The parsed error with extended stack trace when traceback is available - */ -PythonShell.prototype.parseError = function (data) { - var text = ''+data; - var error; - - if (/^Traceback/.test(text)) { - // traceback data is available - var lines = (''+data).trim().split(new RegExp(newline, 'g')); - var exception = lines.pop(); - error = new Error(exception); - error.traceback = data; - // extend stack trace - error.stack += newline+' ----- Python Traceback -----'+newline+' '; - error.stack += lines.slice(1).join(newline+' '); - } else { - // otherwise, create a simpler error with stderr contents - error = new Error(text); - } - - return error; -}; - -/** - * Sends a message to the Python shell through stdin - * Override this method to format data to be sent to the Python process - * @param {string|Object} data The message to send - * @returns {PythonShell} The same instance for chaining calls - */ -PythonShell.prototype.send = function (message) { - var data = this.formatter ? this.formatter(message) : message; - if (this.mode !== 'binary') data += newline; - this.stdin.write(data); - return this; -}; - -/** - * Parses data received from the Python shell stdout stream and emits "message" events - * This method is not used in binary mode - * Override this method to parse incoming data from the Python process into messages - * @param {string|Buffer} data The data to parse into messages - */ -PythonShell.prototype.receive = function (data) { - var self = this; - var parts = (''+data).split(new RegExp(newline,'g')); - - if (parts.length === 1) { - // an incomplete record, keep buffering - this._remaining = (this._remaining || '') + parts[0]; - return this; - } - - var lastLine = parts.pop(); - // fix the first line with the remaining from the previous iteration of 'receive' - parts[0] = (this._remaining || '') + parts[0]; - // keep the remaining for the next iteration of 'receive' - this._remaining = lastLine; - - parts.forEach(function (part) { - self.emit('message', self.parser(part)); - }); - - return this; -}; - -/** - * Closes the stdin stream, which should cause the process to finish its work and close - * @returns {PythonShell} The same instance for chaining calls - */ -PythonShell.prototype.end = function (callback) { - this.childProcess.stdin.end(); - this._endCallback = callback; - return this; -}; - -/** - * Closes the stdin stream, which should cause the process to finish its work and close - * @returns {PythonShell} The same instance for chaining calls - */ -PythonShell.prototype.terminate = function (signal) { - this.childProcess.kill(signal); - this.terminated = true; - return this; -}; - -module.exports = PythonShell; diff --git a/index.ts b/index.ts new file mode 100644 index 0000000..74166b3 --- /dev/null +++ b/index.ts @@ -0,0 +1,293 @@ +import {EventEmitter} from 'events'; +import { ChildProcess,spawn, SpawnOptions } from 'child_process'; +import {EOL as newline} from 'os'; +import {join} from 'path' + +function toArray(source?:T|T[]):T[] { + if (typeof source === 'undefined' || source === null) { + return []; + } else if (!Array.isArray(source)) { + return [source]; + } + return source; +} + +/** + * adds arguments as properties to obj + */ +function extend(obj:{}, ...args) { + Array.prototype.slice.call(arguments, 1).forEach(function (source) { + if (source) { + for (let key in source) { + obj[key] = source[key]; + } + } + }); + return obj; +} + +interface Options extends SpawnOptions{ + mode: 'text'|'json'|'binary' + formatter: (param:string)=>any + parser: Function + encoding: string + pythonPath: string + pythonOptions: string[] + scriptPath: string + args: string[] +} + +/** + * An interactive Python shell exchanging data through stdio + * @param {string} script The python script to execute + * @param {object} [options] The launch options (also passed to child_process.spawn) + * @constructor + */ +class PythonShell extends EventEmitter{ + script:string + command:any[] + mode:string + formatter:any + parser:any + terminated:boolean + childProcess:ChildProcess + stdin: NodeJS.WriteStream; //or writeable stream? Whats difference? + stdout: NodeJS.ReadStream; + stderr: NodeJS.ReadStream; + stderrHasEnded:boolean; + stdoutHasEnded:boolean; + exitCode:number; + exitSignal:string; + private _remaining:string + private _endCallback:(err:any, exitCode:number, exitSignal:string)=>any + + //@ts-ignore keeping it initialized to {} for backwards API compatability + static defaultOptions:Options = {}; //allow global overrides for options + + constructor(script:string, options:Options) { + super(); + + function resolve(type, val:string|Function) { + if (typeof val === 'string') { + // use a built-in function using its name + return PythonShell[type][val]; + } else if (typeof val === 'function') { + // use a custom function + return val; + } + } + + let self = this; + let errorData = ''; + EventEmitter.call(this); + + options = extend({}, PythonShell.defaultOptions, options); + let pythonPath = options.pythonPath || 'python'; + let pythonOptions = toArray(options.pythonOptions); + let scriptArgs = toArray(options.args); + + this.script = join(options.scriptPath || './', script); + this.command = pythonOptions.concat(this.script, scriptArgs); + this.mode = options.mode || 'text'; + this.formatter = resolve('format', options.formatter || this.mode); + this.parser = resolve('parse', options.parser || this.mode); + this.terminated = false; + this.childProcess = spawn(pythonPath, this.command, options); + + ['stdout', 'stdin', 'stderr'].forEach(function (name) { + self[name] = self.childProcess[name]; + self.parser && self[name].setEncoding(options.encoding || 'utf8'); + }); + + // parse incoming data on stdout + if (this.parser) { + this.stdout.on('data', this.receive.bind(this)); + } + + // listen to stderr and emit errors for incoming data + this.stderr.on('data', function (data) { + errorData += ''+data; + }); + + this.stderr.on('end', function(){ + self.stderrHasEnded = true + terminateIfNeeded(); + }) + + this.stdout.on('end', function(){ + self.stdoutHasEnded = true + terminateIfNeeded(); + }) + + this.childProcess.on('exit', function (code,signal) { + self.exitCode = code; + self.exitSignal = signal; + terminateIfNeeded(); + }); + + function terminateIfNeeded() { + if(!self.stderrHasEnded || !self.stdoutHasEnded || (self.exitCode == null && self.exitSignal == null)) return; + + let err; + if (errorData || (self.exitCode && self.exitCode !== 0)) { + if (errorData) { + err = self.parseError(errorData); + } else { + err = new Error('process exited with code ' + self.exitCode); + } + err = extend(err, { + executable: pythonPath, + options: pythonOptions.length ? pythonOptions : null, + script: self.script, + args: scriptArgs.length ? scriptArgs : null, + exitCode: self.exitCode + }); + // do not emit error if only a callback is used + if (self.listeners('error').length || !self._endCallback) { + self.emit('error', err); + } + } + + self.terminated = true; + self.emit('close'); + self._endCallback && self._endCallback(err,self.exitCode,self.exitSignal); + }; + } + + // built-in formatters + static format = { + text: function toText(data) { + if (!data) return ''; + else if (typeof data !== 'string') return data.toString(); + return data; + }, + json: function toJson(data) { + return JSON.stringify(data); + } + }; + + //built-in parsers + static parse = { + text: function asText(data) { + return data; + }, + json: function asJson(data:string) { + return JSON.parse(data); + } + }; + + /** + * Runs a Python script and returns collected messages + * @param {string} script The script to execute + * @param {Options} options The execution options + * @param {Function} callback The callback function to invoke with the script results + * @return {PythonShell} The PythonShell instance + */ + static run(script:string, options:Options, callback:(err:any, output?:any[])=>any) { + if (typeof options === 'function') { + callback = options; + options = null; + } + + let pyshell = new PythonShell(script, options); + let output = []; + + return pyshell.on('message', function (message) { + output.push(message); + }).end(function (err) { + if (err) return callback(err); + return callback(null, output.length ? output : null); + }); + }; + + /** + * Parses an error thrown from the Python process through stderr + * @param {string|Buffer} data The stderr contents to parse + * @return {Error} The parsed error with extended stack trace when traceback is available + */ + private parseError(data:string|Buffer) { + let text = ''+data; + let error; + + if (/^Traceback/.test(text)) { + // traceback data is available + let lines = (''+data).trim().split(new RegExp(newline, 'g')); + let exception = lines.pop(); + error = new Error(exception); + error.traceback = data; + // extend stack trace + error.stack += newline+' ----- Python Traceback -----'+newline+' '; + error.stack += lines.slice(1).join(newline+' '); + } else { + // otherwise, create a simpler error with stderr contents + error = new Error(text); + } + + return error; + }; + + /** + * Sends a message to the Python shell through stdin + * Override this method to format data to be sent to the Python process + * @param {string|Object} data The message to send + * @returns {PythonShell} The same instance for chaining calls + */ + send(message:string|Object) { + let data = this.formatter ? this.formatter(message) : message; + if (this.mode !== 'binary') data += newline; + this.stdin.write(data); + return this; + }; + + /** + * Parses data received from the Python shell stdout stream and emits "message" events + * This method is not used in binary mode + * Override this method to parse incoming data from the Python process into messages + * @param {string|Buffer} data The data to parse into messages + */ + receive(data:string|Buffer) { + let self = this; + let parts = (''+data).split(new RegExp(newline,'g')); + + if (parts.length === 1) { + // an incomplete record, keep buffering + this._remaining = (this._remaining || '') + parts[0]; + return this; + } + + let lastLine = parts.pop(); + // fix the first line with the remaining from the previous iteration of 'receive' + parts[0] = (this._remaining || '') + parts[0]; + // keep the remaining for the next iteration of 'receive' + this._remaining = lastLine; + + parts.forEach(function (part) { + self.emit('message', self.parser(part)); + }); + + return this; + }; + + /** + * Closes the stdin stream, which should cause the process to finish its work and close + * @returns {PythonShell} The same instance for chaining calls + */ + end(callback:(err:any, exitCode:number,exitSignal:string)=>any) { + this.childProcess.stdin.end(); + this._endCallback = callback; + return this; + }; + + /** + * Closes the stdin stream, which should cause the process to finish its work and close + * @returns {PythonShell} The same instance for chaining calls + */ + terminate(signal?:string) { + this.childProcess.kill(signal); + this.terminated = true; + return this; + }; +}; + + +module.exports = PythonShell; diff --git a/package-lock.json b/package-lock.json index ec49ee1..5d4f9e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "python-shell", - "version": "0.4.0", + "version": "1.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -46,7 +46,7 @@ "integrity": "sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==", "dev": true, "requires": { - "lodash": "4.17.5" + "lodash": "^4.14.0" } }, "aws-sign2": { @@ -74,7 +74,7 @@ "dev": true, "optional": true, "requires": { - "tweetnacl": "0.14.5" + "tweetnacl": "^0.14.3" } }, "bl": { @@ -83,7 +83,7 @@ "integrity": "sha1-/cqHGplxOqANGeO7ukHER4emU5g=", "dev": true, "requires": { - "readable-stream": "2.0.6" + "readable-stream": "~2.0.5" } }, "boom": { @@ -92,7 +92,7 @@ "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", "dev": true, "requires": { - "hoek": "2.16.3" + "hoek": "2.x.x" } }, "brace-expansion": { @@ -101,7 +101,7 @@ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "requires": { - "balanced-match": "1.0.0", + "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, @@ -123,11 +123,11 @@ "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.2", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" }, "dependencies": { "supports-color": { @@ -144,7 +144,7 @@ "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", "dev": true, "requires": { - "delayed-stream": "1.0.0" + "delayed-stream": "~1.0.0" } }, "commander": { @@ -171,7 +171,7 @@ "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", "dev": true, "requires": { - "boom": "2.10.1" + "boom": "2.x.x" } }, "dashdash": { @@ -180,7 +180,7 @@ "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", "dev": true, "requires": { - "assert-plus": "1.0.0" + "assert-plus": "^1.0.0" }, "dependencies": { "assert-plus": { @@ -225,7 +225,7 @@ "dev": true, "optional": true, "requires": { - "jsbn": "0.1.1" + "jsbn": "~0.1.0" } }, "escape-string-regexp": { @@ -258,9 +258,9 @@ "integrity": "sha1-rjFduaSQf6BlUCMEpm13M0de43w=", "dev": true, "requires": { - "async": "2.6.0", - "combined-stream": "1.0.5", - "mime-types": "2.1.17" + "async": "^2.0.1", + "combined-stream": "^1.0.5", + "mime-types": "^2.1.11" } }, "fs.realpath": { @@ -281,7 +281,7 @@ "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", "dev": true, "requires": { - "is-property": "1.0.2" + "is-property": "^1.0.0" } }, "getpass": { @@ -290,7 +290,7 @@ "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", "dev": true, "requires": { - "assert-plus": "1.0.0" + "assert-plus": "^1.0.0" }, "dependencies": { "assert-plus": { @@ -307,12 +307,12 @@ "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", "dev": true, "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } }, "growl": { @@ -327,10 +327,10 @@ "integrity": "sha1-zcvAgYgmWtEZtqWnyKtw7s+10n0=", "dev": true, "requires": { - "chalk": "1.1.3", - "commander": "2.14.1", - "is-my-json-valid": "2.17.1", - "pinkie-promise": "2.0.1" + "chalk": "^1.1.1", + "commander": "^2.9.0", + "is-my-json-valid": "^2.12.4", + "pinkie-promise": "^2.0.0" }, "dependencies": { "commander": { @@ -347,7 +347,7 @@ "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", "dev": true, "requires": { - "ansi-regex": "2.1.1" + "ansi-regex": "^2.0.0" } }, "has-flag": { @@ -362,10 +362,10 @@ "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", "dev": true, "requires": { - "boom": "2.10.1", - "cryptiles": "2.0.5", - "hoek": "2.16.3", - "sntp": "1.0.9" + "boom": "2.x.x", + "cryptiles": "2.x.x", + "hoek": "2.x.x", + "sntp": "1.x.x" } }, "he": { @@ -386,9 +386,9 @@ "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", "dev": true, "requires": { - "assert-plus": "0.2.0", - "jsprim": "1.4.1", - "sshpk": "1.13.1" + "assert-plus": "^0.2.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" } }, "inflight": { @@ -397,8 +397,8 @@ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "dev": true, "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" + "once": "^1.3.0", + "wrappy": "1" } }, "inherits": { @@ -413,10 +413,10 @@ "integrity": "sha512-Q2khNw+oBlWuaYvEEHtKSw/pCxD2L5Rc1C+UQme9X6JdRDh7m5D7HkozA0qa3DUkQ6VzCnEm8mVIQPyIRkI5sQ==", "dev": true, "requires": { - "generate-function": "2.0.0", - "generate-object-property": "1.2.0", - "jsonpointer": "4.0.1", - "xtend": "4.0.1" + "generate-function": "^2.0.0", + "generate-object-property": "^1.1.0", + "jsonpointer": "^4.0.0", + "xtend": "^4.0.0" } }, "is-property": { @@ -506,7 +506,7 @@ "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", "dev": true, "requires": { - "mime-db": "1.30.0" + "mime-db": "~1.30.0" } }, "minimatch": { @@ -515,7 +515,7 @@ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, "requires": { - "brace-expansion": "1.1.11" + "brace-expansion": "^1.1.7" } }, "minimist": { @@ -565,7 +565,7 @@ "integrity": "sha1-gpOC/8Bla2Z+e+ZQoJSgba69Uk8=", "dev": true, "requires": { - "request-json": "0.6.2" + "request-json": "^0.6.1" } }, "ms": { @@ -592,7 +592,7 @@ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, "requires": { - "wrappy": "1.0.2" + "wrappy": "1" } }, "path-is-absolute": { @@ -613,7 +613,7 @@ "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", "dev": true, "requires": { - "pinkie": "2.0.4" + "pinkie": "^2.0.0" } }, "process-nextick-args": { @@ -640,12 +640,12 @@ "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", "dev": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "string_decoder": "0.10.31", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "~1.0.0", + "process-nextick-args": "~1.0.6", + "string_decoder": "~0.10.x", + "util-deprecate": "~1.0.1" } }, "request": { @@ -654,27 +654,27 @@ "integrity": "sha1-dpPKdou7DqXIzgjAhKRe+gW4kqs=", "dev": true, "requires": { - "aws-sign2": "0.6.0", - "aws4": "1.6.0", - "bl": "1.1.2", - "caseless": "0.11.0", - "combined-stream": "1.0.5", - "extend": "3.0.1", - "forever-agent": "0.6.1", - "form-data": "1.0.1", - "har-validator": "2.0.6", - "hawk": "3.1.3", - "http-signature": "1.1.1", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.17", - "node-uuid": "1.4.8", - "oauth-sign": "0.8.2", - "qs": "6.2.3", - "stringstream": "0.0.5", - "tough-cookie": "2.3.3", - "tunnel-agent": "0.4.3" + "aws-sign2": "~0.6.0", + "aws4": "^1.2.1", + "bl": "~1.1.2", + "caseless": "~0.11.0", + "combined-stream": "~1.0.5", + "extend": "~3.0.0", + "forever-agent": "~0.6.1", + "form-data": "~1.0.0-rc4", + "har-validator": "~2.0.6", + "hawk": "~3.1.3", + "http-signature": "~1.1.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.7", + "node-uuid": "~1.4.7", + "oauth-sign": "~0.8.1", + "qs": "~6.2.0", + "stringstream": "~0.0.4", + "tough-cookie": "~2.3.0", + "tunnel-agent": "~0.4.1" } }, "request-json": { @@ -693,11 +693,11 @@ "integrity": "sha512-l+/NwEMO+DcstsHEwPHRHzC9j4UOE3VQwJGcMWSsD/vqpqHbnQ+1iSHy64Ihmmjx1uiRPD9pFadTSc3MJtXAgw==", "dev": true, "requires": { - "should-equal": "2.0.0", - "should-format": "3.0.3", - "should-type": "1.4.0", - "should-type-adaptors": "1.1.0", - "should-util": "1.0.0" + "should-equal": "^2.0.0", + "should-format": "^3.0.3", + "should-type": "^1.4.0", + "should-type-adaptors": "^1.0.1", + "should-util": "^1.0.0" } }, "should-equal": { @@ -706,7 +706,7 @@ "integrity": "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==", "dev": true, "requires": { - "should-type": "1.4.0" + "should-type": "^1.4.0" } }, "should-format": { @@ -715,8 +715,8 @@ "integrity": "sha1-m/yPdPo5IFxT04w01xcwPidxJPE=", "dev": true, "requires": { - "should-type": "1.4.0", - "should-type-adaptors": "1.1.0" + "should-type": "^1.3.0", + "should-type-adaptors": "^1.0.1" } }, "should-type": { @@ -731,8 +731,8 @@ "integrity": "sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==", "dev": true, "requires": { - "should-type": "1.4.0", - "should-util": "1.0.0" + "should-type": "^1.3.0", + "should-util": "^1.0.0" } }, "should-util": { @@ -747,7 +747,7 @@ "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", "dev": true, "requires": { - "hoek": "2.16.3" + "hoek": "2.x.x" } }, "sshpk": { @@ -756,14 +756,14 @@ "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", "dev": true, "requires": { - "asn1": "0.2.3", - "assert-plus": "1.0.0", - "bcrypt-pbkdf": "1.0.1", - "dashdash": "1.14.1", - "ecc-jsbn": "0.1.1", - "getpass": "0.1.7", - "jsbn": "0.1.1", - "tweetnacl": "0.14.5" + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "tweetnacl": "~0.14.0" }, "dependencies": { "assert-plus": { @@ -792,7 +792,7 @@ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { - "ansi-regex": "2.1.1" + "ansi-regex": "^2.0.0" } }, "supports-color": { @@ -801,7 +801,7 @@ "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", "dev": true, "requires": { - "has-flag": "2.0.0" + "has-flag": "^2.0.0" } }, "tough-cookie": { @@ -810,7 +810,7 @@ "integrity": "sha1-C2GKVWW23qkL80JdBNVe3EdadWE=", "dev": true, "requires": { - "punycode": "1.4.1" + "punycode": "^1.4.1" } }, "tunnel-agent": { @@ -826,6 +826,12 @@ "dev": true, "optional": true }, + "typescript": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.0.1.tgz", + "integrity": "sha512-zQIMOmC+372pC/CCVLqnQ0zSBiY7HHodU7mpQdjiZddek4GMj31I3dUJ7gAs9o65X7mnRma6OokOkc6f9jjfBg==", + "dev": true + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -838,9 +844,9 @@ "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", "dev": true, "requires": { - "assert-plus": "1.0.0", + "assert-plus": "^1.0.0", "core-util-is": "1.0.2", - "extsprintf": "1.3.0" + "extsprintf": "^1.2.0" }, "dependencies": { "assert-plus": { diff --git a/package.json b/package.json index 5b2470e..40207ec 100644 --- a/package.json +++ b/package.json @@ -1,21 +1,23 @@ { "name": "python-shell", - "version": "0.5.0", + "version": "1.0.0", "description": "Run Python scripts from Node.js with simple (but efficient) inter-process communication through stdio", "keywords": [ "python" ], "scripts": { - "test": "mocha -R spec", - "appveyorTest": "mocha --ui tdd --reporter mocha-appveyor-reporter test/*.js" + "test": "tsc -p ./ && mocha -R spec", + "appveyorTest": "mocha --ui tdd --reporter mocha-appveyor-reporter test/*.js", + "compile":"tsc -watch -p ./" }, "dependencies": {}, "devDependencies": { "@types/mocha": "^2.2.48", "@types/node": "^9.3.0", - "should": "^13.2.1", "mocha": "^5.0.0", - "mocha-appveyor-reporter": "^0.4.0" + "mocha-appveyor-reporter": "^0.4.0", + "should": "^13.2.1", + "typescript": "^3.0.1" }, "repository": { "type": "git", diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..3676a76 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es6", + "lib": [ + "es6" + ], + "sourceMap": true, + "rootDir": "." + }, + "exclude": [ + "node_modules", + "personal" + ] +} \ No newline at end of file From fa41649170b28bed6bae1380f29a72a8d8b749d7 Mon Sep 17 00:00:00 2001 From: Almenon Date: Wed, 8 Aug 2018 19:21:59 -0700 Subject: [PATCH 14/27] #134 doing a final touch-up on some missing types --- index.ts | 47 ++++++++++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/index.ts b/index.ts index 352d48e..afbe627 100644 --- a/index.ts +++ b/index.ts @@ -29,7 +29,7 @@ function extend(obj:{}, ...args) { interface Options extends SpawnOptions{ mode: 'text'|'json'|'binary' formatter: (param:string)=>any - parser: Function + parser: (param:string)=>any encoding: string pythonPath: string pythonOptions: string[] @@ -37,6 +37,10 @@ interface Options extends SpawnOptions{ args: string[] } +class PythonShellError extends Error{ + traceback: string | Buffer; +} + /** * An interactive Python shell exchanging data through stdio * @param {string} script The python script to execute @@ -45,21 +49,21 @@ interface Options extends SpawnOptions{ */ class PythonShell extends EventEmitter{ script:string - command:any[] + command:string[] mode:string - formatter:any - parser:any + formatter:(param:string|Object)=>any + parser:(param:string)=>any terminated:boolean childProcess:ChildProcess stdin: NodeJS.WriteStream; //or writeable stream? Whats difference? stdout: NodeJS.ReadStream; stderr: NodeJS.ReadStream; - stderrHasEnded:boolean; - stdoutHasEnded:boolean; - exitCode:number; - exitSignal:string; + private stderrHasEnded:boolean; + private stdoutHasEnded:boolean; + private exitCode:number; + private exitSignal:string; private _remaining:string - private _endCallback:(err:any, exitCode:number, exitSignal:string)=>any + private _endCallback:(err:PythonShellError, exitCode:number, exitSignal:string)=>any //@ts-ignore keeping it initialized to {} for backwards API compatability static defaultOptions:Options = {}; //allow global overrides for options @@ -67,6 +71,9 @@ class PythonShell extends EventEmitter{ constructor(script:string, options:Options) { super(); + /** + * returns either pythonshell func (if val string) or custom func (if val Function) + */ function resolve(type, val:string|Function) { if (typeof val === 'string') { // use a built-in function using its name @@ -84,6 +91,8 @@ class PythonShell extends EventEmitter{ options = extend({}, PythonShell.defaultOptions, options); let pythonPath; if (!options.pythonPath) { + // starting 2020 python2 is deprecated so we choose 3 as default + // except for windows which just has "python" command pythonPath = process.platform != "win32" ? "python3" : "python" } else pythonPath = options.pythonPath; let pythonOptions = toArray(options.pythonOptions); @@ -131,14 +140,14 @@ class PythonShell extends EventEmitter{ function terminateIfNeeded() { if(!self.stderrHasEnded || !self.stdoutHasEnded || (self.exitCode == null && self.exitSignal == null)) return; - let err; + let err:PythonShellError; if (errorData || (self.exitCode && self.exitCode !== 0)) { if (errorData) { err = self.parseError(errorData); } else { - err = new Error('process exited with code ' + self.exitCode); + err = new PythonShellError('process exited with code ' + self.exitCode); } - err = extend(err, { + err = extend(err, { executable: pythonPath, options: pythonOptions.length ? pythonOptions : null, script: self.script, @@ -159,7 +168,7 @@ class PythonShell extends EventEmitter{ // built-in formatters static format = { - text: function toText(data) { + text: function toText(data):string { if (!data) return ''; else if (typeof data !== 'string') return data.toString(); return data; @@ -171,7 +180,7 @@ class PythonShell extends EventEmitter{ //built-in parsers static parse = { - text: function asText(data) { + text: function asText(data):string { return data; }, json: function asJson(data:string) { @@ -186,7 +195,7 @@ class PythonShell extends EventEmitter{ * @param {Function} callback The callback function to invoke with the script results * @return {PythonShell} The PythonShell instance */ - static run(script:string, options:Options, callback:(err:any, output?:any[])=>any) { + static run(script:string, options:Options, callback:(err:PythonShellError, output?:any[])=>any) { if (typeof options === 'function') { callback = options; options = null; @@ -210,20 +219,20 @@ class PythonShell extends EventEmitter{ */ private parseError(data:string|Buffer) { let text = ''+data; - let error; + let error:PythonShellError; if (/^Traceback/.test(text)) { // traceback data is available let lines = (''+data).trim().split(new RegExp(newline, 'g')); let exception = lines.pop(); - error = new Error(exception); + error = new PythonShellError(exception); error.traceback = data; // extend stack trace error.stack += newline+' ----- Python Traceback -----'+newline+' '; error.stack += lines.slice(1).join(newline+' '); } else { // otherwise, create a simpler error with stderr contents - error = new Error(text); + error = new PythonShellError(text); } return error; @@ -275,7 +284,7 @@ class PythonShell extends EventEmitter{ * Closes the stdin stream, which should cause the process to finish its work and close * @returns {PythonShell} The same instance for chaining calls */ - end(callback:(err:any, exitCode:number,exitSignal:string)=>any) { + end(callback:(err:PythonShellError, exitCode:number,exitSignal:string)=>any) { this.childProcess.stdin.end(); this._endCallback = callback; return this; From 593f0958ffe7feabbc3697e5c7cb3d4d4f4bc9e9 Mon Sep 17 00:00:00 2001 From: Almenon Date: Thu, 9 Aug 2018 23:52:15 -0700 Subject: [PATCH 15/27] fixed stdin/stdout types, made certain params optional, fixed export --- index.ts | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/index.ts b/index.ts index afbe627..5d76969 100644 --- a/index.ts +++ b/index.ts @@ -2,6 +2,7 @@ import {EventEmitter} from 'events'; import { ChildProcess,spawn, SpawnOptions } from 'child_process'; import {EOL as newline} from 'os'; import {join} from 'path' +import {Readable,Writable} from 'stream' function toArray(source?:T|T[]):T[] { if (typeof source === 'undefined' || source === null) { @@ -26,15 +27,15 @@ function extend(obj:{}, ...args) { return obj; } -interface Options extends SpawnOptions{ - mode: 'text'|'json'|'binary' - formatter: (param:string)=>any - parser: (param:string)=>any - encoding: string - pythonPath: string - pythonOptions: string[] - scriptPath: string - args: string[] +export interface Options extends SpawnOptions{ + mode?: 'text'|'json'|'binary' + formatter?: (param:string)=>any + parser?: (param:string)=>any + encoding?: string + pythonPath?: string + pythonOptions?: string[] + scriptPath?: string + args?: string[] } class PythonShellError extends Error{ @@ -47,7 +48,7 @@ class PythonShellError extends Error{ * @param {object} [options] The launch options (also passed to child_process.spawn) * @constructor */ -class PythonShell extends EventEmitter{ +export class PythonShell extends EventEmitter{ script:string command:string[] mode:string @@ -55,9 +56,9 @@ class PythonShell extends EventEmitter{ parser:(param:string)=>any terminated:boolean childProcess:ChildProcess - stdin: NodeJS.WriteStream; //or writeable stream? Whats difference? - stdout: NodeJS.ReadStream; - stderr: NodeJS.ReadStream; + stdin: Writable; + stdout: Readable; + stderr: Readable; private stderrHasEnded:boolean; private stdoutHasEnded:boolean; private exitCode:number; @@ -68,7 +69,7 @@ class PythonShell extends EventEmitter{ //@ts-ignore keeping it initialized to {} for backwards API compatability static defaultOptions:Options = {}; //allow global overrides for options - constructor(script:string, options:Options) { + constructor(script:string, options?:Options) { super(); /** @@ -195,7 +196,7 @@ class PythonShell extends EventEmitter{ * @param {Function} callback The callback function to invoke with the script results * @return {PythonShell} The PythonShell instance */ - static run(script:string, options:Options, callback:(err:PythonShellError, output?:any[])=>any) { + static run(script:string, options?:Options, callback?:(err:PythonShellError, output?:any[])=>any) { if (typeof options === 'function') { callback = options; options = null; @@ -300,6 +301,3 @@ class PythonShell extends EventEmitter{ return this; }; }; - - -module.exports = PythonShell; From 59ae03491007e37c8e6f2a3066d08c761a224a95 Mon Sep 17 00:00:00 2001 From: Almenon Date: Fri, 10 Aug 2018 00:15:41 -0700 Subject: [PATCH 16/27] converting unit tests to typescript #134 --- index.ts | 5 +- ...t-python-shell.js => test-python-shell.ts} | 102 +++++++++--------- 2 files changed, 54 insertions(+), 53 deletions(-) rename test/{test-python-shell.js => test-python-shell.ts} (78%) diff --git a/index.ts b/index.ts index 5d76969..5812bc7 100644 --- a/index.ts +++ b/index.ts @@ -40,6 +40,7 @@ export interface Options extends SpawnOptions{ class PythonShellError extends Error{ traceback: string | Buffer; + exitCode?:number; } /** @@ -59,10 +60,10 @@ export class PythonShell extends EventEmitter{ stdin: Writable; stdout: Readable; stderr: Readable; + exitSignal:string; + exitCode:number; private stderrHasEnded:boolean; private stdoutHasEnded:boolean; - private exitCode:number; - private exitSignal:string; private _remaining:string private _endCallback:(err:PythonShellError, exitCode:number, exitSignal:string)=>any diff --git a/test/test-python-shell.js b/test/test-python-shell.ts similarity index 78% rename from test/test-python-shell.js rename to test/test-python-shell.ts index 1777596..a3d11a3 100644 --- a/test/test-python-shell.js +++ b/test/test-python-shell.ts @@ -1,7 +1,7 @@ -var should = require('should'); -var PythonShell = require('..'); -var path = require('path') -const newline = require('os').EOL +import * as should from 'should'; +import {PythonShell} from '..' +import {sep} from 'path' +import {EOL as newline} from 'os' describe('PythonShell', function () { @@ -11,8 +11,8 @@ describe('PythonShell', function () { describe('#ctor(script, options)', function () { it('should spawn a Python process', function (done) { - var pyshell = new PythonShell('exit-code.py'); - pyshell.command.should.eql(['test' + path.sep + 'python' + path.sep + 'exit-code.py']); + let pyshell = new PythonShell('exit-code.py'); + pyshell.command.should.eql(['test' + sep + 'python' + sep + 'exit-code.py']); pyshell.terminated.should.be.false; pyshell.end(function (err) { if (err) return done(err); @@ -21,17 +21,17 @@ describe('PythonShell', function () { }); }); it('should spawn a Python process with options', function (done) { - var pyshell = new PythonShell('exit-code.py', { - pythonOptions: '-u' + let pyshell = new PythonShell('exit-code.py', { + pythonOptions: ['-u'] }); - pyshell.command.should.eql(['-u', 'test' + path.sep + 'python' + path.sep + 'exit-code.py']); + pyshell.command.should.eql(['-u', 'test' + sep + 'python' + sep + 'exit-code.py']); pyshell.end(done); }); it('should spawn a Python process with script arguments', function (done) { - var pyshell = new PythonShell('echo_args.py', { + let pyshell = new PythonShell('echo_args.py', { args: ['hello', 'world'] }); - pyshell.command.should.eql(['test' + path.sep + 'python' + path.sep + 'echo_args.py', 'hello', 'world']); + pyshell.command.should.eql(['test' + sep + 'python' + sep + 'echo_args.py', 'hello', 'world']); pyshell.end(done); }); }); @@ -48,14 +48,14 @@ describe('PythonShell', function () { }); }); it('should try to run the script and fail appropriately', function (done) { - PythonShell.run('unknown_script.py', function (err, results) { + PythonShell.run('unknown_script.py', null, function (err, results) { err.should.be.an.Error; err.exitCode.should.be.exactly(2); done(); }); }); it('should run the script and fail with an extended stack trace', function (done) { - PythonShell.run('error.py', function (err, results) { + PythonShell.run('error.py', null, function (err, results) { err.should.be.an.Error; err.exitCode.should.be.exactly(1); err.stack.should.containEql('----- Python Traceback -----'); @@ -63,11 +63,11 @@ describe('PythonShell', function () { }); }); it('should run multiple scripts and fail with an extended stack trace for each of them', function (done) { - var numberOfTimesToRun = 20; - for (var i = 0; i < numberOfTimesToRun; i++) { + let numberOfTimesToRun = 20; + for (let i = 0; i < numberOfTimesToRun; i++) { runSingleErrorScript(end); } - var count = 0; + let count = 0; function end() { count++; if (count === numberOfTimesToRun) { @@ -75,7 +75,7 @@ describe('PythonShell', function () { } } function runSingleErrorScript(callback) { - PythonShell.run('error.py', function (err, results) { + PythonShell.run('error.py', null, function (err, results) { err.should.be.an.Error; err.exitCode.should.be.exactly(1); err.stack.should.containEql('----- Python Traceback -----'); @@ -85,11 +85,11 @@ describe('PythonShell', function () { }); it('should run multiple scripts and return output data for each of them', function (done) { - var numberOfTimesToRun = 20; - for (var i = 0; i < numberOfTimesToRun; i++) { + let numberOfTimesToRun = 20; + for (let i = 0; i < numberOfTimesToRun; i++) { runSingleScript(end); } - var count = 0; + let count = 0; function end() { count++; if (count === numberOfTimesToRun) { @@ -112,10 +112,10 @@ describe('PythonShell', function () { describe('.send(message)', function () { it('should send string messages when mode is "text"', function (done) { - var pyshell = new PythonShell('echo_text.py', { + let pyshell = new PythonShell('echo_text.py', { mode: 'text' }); - var output = ''; + let output = ''; pyshell.stdout.on('data', function (data) { output += ''+data; }); @@ -126,10 +126,10 @@ describe('PythonShell', function () { }); }); it('should send JSON messages when mode is "json"', function (done) { - var pyshell = new PythonShell('echo_json.py', { + let pyshell = new PythonShell('echo_json.py', { mode: 'json' }); - var output = ''; + let output = ''; pyshell.stdout.on('data', function (data) { output += ''+data; }); @@ -140,12 +140,12 @@ describe('PythonShell', function () { }); }); it('should use a custom formatter', function (done) { - var pyshell = new PythonShell('echo_text.py', { + let pyshell = new PythonShell('echo_text.py', { formatter: function (message) { return message.toUpperCase(); } }); - var output = ''; + let output = ''; pyshell.stdout.on('data', function (data) { output += ''+data; }); @@ -156,10 +156,10 @@ describe('PythonShell', function () { }); }); it('should write as-is when mode is "binary"', function (done) { - var pyshell = new PythonShell('echo_binary.py', { + let pyshell = new PythonShell('echo_binary.py', { mode: 'binary' }); - var output = ''; + let output = ''; pyshell.stdout.on('data', function (data) { output += ''+data; }); @@ -173,10 +173,10 @@ describe('PythonShell', function () { describe('.receive(data)', function () { it('should emit messages as strings when mode is "text"', function (done) { - var pyshell = new PythonShell('echo_text.py', { + let pyshell = new PythonShell('echo_text.py', { mode: 'text' }); - var count = 0; + let count = 0; pyshell.on('message', function (message) { count === 0 && message.should.be.exactly('hello'); count === 1 && message.should.be.exactly('world'); @@ -186,10 +186,10 @@ describe('PythonShell', function () { }).send('hello').send('world').end(done); }); it('should emit messages as JSON when mode is "json"', function (done) { - var pyshell = new PythonShell('echo_json.py', { + let pyshell = new PythonShell('echo_json.py', { mode: 'json' }); - var count = 0; + let count = 0; pyshell.send({ a: 'b' }).send(null).send([1, 2, 3, 4, 5]); pyshell.on('message', function (message) { count === 0 && message.should.eql({ a: 'b' }); @@ -201,7 +201,7 @@ describe('PythonShell', function () { }).end(done); }); it('should properly buffer partial messages', function (done) { - var pyshell = new PythonShell('echo_json.py', { + let pyshell = new PythonShell('echo_json.py', { mode: 'json' }); pyshell.on('message', function (message) { @@ -210,7 +210,7 @@ describe('PythonShell', function () { }).receive('{"a"').receive(':').receive('true}'+newline+'').end(done); }); it('should not be invoked when mode is "binary"', function (done) { - var pyshell = new PythonShell('echo_args.py', { + let pyshell = new PythonShell('echo_args.py', { args: ['hello', 'world'], mode: 'binary' }); @@ -220,13 +220,13 @@ describe('PythonShell', function () { pyshell.end(done); }); it('should use a custom parser function', function (done) { - var pyshell = new PythonShell('echo_text.py', { + let pyshell = new PythonShell('echo_text.py', { mode: 'text', parser: function (message) { return message.toUpperCase(); } }); - var count = 0; + let count = 0; pyshell.on('message', function (message) { count === 0 && message.should.be.exactly('HELLO'); count === 1 && message.should.be.exactly('WORLD!'); @@ -239,7 +239,7 @@ describe('PythonShell', function () { describe('.end(callback)', function () { it('should end normally when exit code is zero', function (done) { - var pyshell = new PythonShell('exit-code.py'); + let pyshell = new PythonShell('exit-code.py'); pyshell.end(function (err,code,signal) { if (err) return done(err); code.should.be.exactly(0); @@ -247,8 +247,8 @@ describe('PythonShell', function () { }); }); it('should emit error if exit code is not zero', function (done) { - var pyshell = new PythonShell('exit-code.py', { - args: 3 + let pyshell = new PythonShell('exit-code.py', { + args: ['3'] }); pyshell.on('error', function (err) { err.should.have.properties({ @@ -258,8 +258,8 @@ describe('PythonShell', function () { done(); }); }); - it('should emit error when data is written to stderr', function (done) { - var pyshell = new PythonShell('error.py'); + it('should emit error when error is written to stderr', function (done) { + let pyshell = new PythonShell('error.py'); pyshell.on('error', function (err) { err.message.should.be.equalOneOf('ZeroDivisionError: integer division or modulo by zero','ZeroDivisionError: division by zero'); err.should.have.property('traceback'); @@ -271,8 +271,8 @@ describe('PythonShell', function () { describe('.parseError(data)', function () { it('should extend error with context properties', function (done) { - var pyshell = new PythonShell('exit-code.py', { - args: 1 + let pyshell = new PythonShell('exit-code.py', { + args: ['1'] }); pyshell.on('error', function (err) { err.should.have.properties(['exitCode', 'script', 'options', 'args']); @@ -280,11 +280,11 @@ describe('PythonShell', function () { }); }); it('should extend err.stack with traceback', function (done) { - var pyshell = new PythonShell('error.py'); + let pyshell = new PythonShell('error.py'); pyshell.on('error', function (err) { err.stack.should.containEql('----- Python Traceback -----'); - err.stack.should.containEql('File "test' + path.sep + 'python' + path.sep + 'error.py", line 4'); - err.stack.should.containEql('File "test' + path.sep + 'python' + path.sep + 'error.py", line 6'); + err.stack.should.containEql('File "test' + sep + 'python' + sep + 'error.py", line 4'); + err.stack.should.containEql('File "test' + sep + 'python' + sep + 'error.py", line 6'); done(); }); }); @@ -292,14 +292,14 @@ describe('PythonShell', function () { describe('.terminate()', function () { it('set terminated to true', function (done) { - var pyshell = new PythonShell('infinite_loop.py'); + let pyshell = new PythonShell('infinite_loop.py'); pyshell.terminate(); pyshell.terminated.should.be.true done(); }); it('run the end callback if specified', function (done) { - var pyshell = new PythonShell('infinite_loop.py'); - var endCalled = false; + let pyshell = new PythonShell('infinite_loop.py'); + let endCalled = false; pyshell.end(()=>{ endCalled = true; }) @@ -308,8 +308,8 @@ describe('PythonShell', function () { done(); }); it('terminate with correct kill signal', function (done) { - var pyshell = new PythonShell('infinite_loop.py'); - var endCalled = false; + let pyshell = new PythonShell('infinite_loop.py'); + let endCalled = false; pyshell.end(()=>{ endCalled = true; }) From ae6cee8737c29b5d9a146a9a59e285fc01aa1956 Mon Sep 17 00:00:00 2001 From: Almenon Date: Mon, 13 Aug 2018 19:46:24 -0700 Subject: [PATCH 17/27] renamed script to scriptPath to avoid confusion and got rid of unnecessary code in run --- index.ts | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/index.ts b/index.ts index 5812bc7..a63ef2d 100644 --- a/index.ts +++ b/index.ts @@ -50,7 +50,7 @@ class PythonShellError extends Error{ * @constructor */ export class PythonShell extends EventEmitter{ - script:string + scriptPath:string command:string[] mode:string formatter:(param:string|Object)=>any @@ -70,7 +70,7 @@ export class PythonShell extends EventEmitter{ //@ts-ignore keeping it initialized to {} for backwards API compatability static defaultOptions:Options = {}; //allow global overrides for options - constructor(script:string, options?:Options) { + constructor(scriptPath:string, options?:Options) { super(); /** @@ -100,8 +100,8 @@ export class PythonShell extends EventEmitter{ let pythonOptions = toArray(options.pythonOptions); let scriptArgs = toArray(options.args); - this.script = join(options.scriptPath || './', script); - this.command = pythonOptions.concat(this.script, scriptArgs); + this.scriptPath = join(options.scriptPath || './', scriptPath); + this.command = pythonOptions.concat(this.scriptPath, scriptArgs); this.mode = options.mode || 'text'; this.formatter = resolve('format', options.formatter || this.mode); this.parser = resolve('parse', options.parser || this.mode); @@ -152,7 +152,7 @@ export class PythonShell extends EventEmitter{ err = extend(err, { executable: pythonPath, options: pythonOptions.length ? pythonOptions : null, - script: self.script, + script: self.scriptPath, args: scriptArgs.length ? scriptArgs : null, exitCode: self.exitCode }); @@ -192,18 +192,13 @@ export class PythonShell extends EventEmitter{ /** * Runs a Python script and returns collected messages - * @param {string} script The script to execute + * @param {string} scriptPath The path to the script to execute * @param {Options} options The execution options * @param {Function} callback The callback function to invoke with the script results * @return {PythonShell} The PythonShell instance */ - static run(script:string, options?:Options, callback?:(err:PythonShellError, output?:any[])=>any) { - if (typeof options === 'function') { - callback = options; - options = null; - } - - let pyshell = new PythonShell(script, options); + static run(scriptPath:string, options?:Options, callback?:(err:PythonShellError, output?:any[])=>any) { + let pyshell = new PythonShell(scriptPath, options); let output = []; return pyshell.on('message', function (message) { From 1cea78f22dc48abfd1e601000c8faa714fb92d20 Mon Sep 17 00:00:00 2001 From: Almenon Date: Tue, 14 Aug 2018 19:48:25 -0700 Subject: [PATCH 18/27] #138 adding functions to check python syntax --- index.ts | 51 +++++++++++++++++++++++++++++++++------ test/test-python-shell.ts | 18 ++++++++++++++ 2 files changed, 62 insertions(+), 7 deletions(-) diff --git a/index.ts b/index.ts index a63ef2d..854d35b 100644 --- a/index.ts +++ b/index.ts @@ -1,8 +1,9 @@ import {EventEmitter} from 'events'; -import { ChildProcess,spawn, SpawnOptions } from 'child_process'; -import {EOL as newline} from 'os'; -import {join} from 'path' +import { ChildProcess,spawn, SpawnOptions, exec } from 'child_process'; +import {EOL as newline, tmpdir} from 'os'; +import {join, sep} from 'path' import {Readable,Writable} from 'stream' +import { writeFile } from 'fs'; function toArray(source?:T|T[]):T[] { if (typeof source === 'undefined' || source === null) { @@ -67,7 +68,10 @@ export class PythonShell extends EventEmitter{ private _remaining:string private _endCallback:(err:PythonShellError, exitCode:number, exitSignal:string)=>any - //@ts-ignore keeping it initialized to {} for backwards API compatability + // starting 2020 python2 is deprecated so we choose 3 as default + // except for windows which just has "python" command + static defaultPythonPath = process.platform != "win32" ? "python3" : "python"; + static defaultOptions:Options = {}; //allow global overrides for options constructor(scriptPath:string, options?:Options) { @@ -93,9 +97,7 @@ export class PythonShell extends EventEmitter{ options = extend({}, PythonShell.defaultOptions, options); let pythonPath; if (!options.pythonPath) { - // starting 2020 python2 is deprecated so we choose 3 as default - // except for windows which just has "python" command - pythonPath = process.platform != "win32" ? "python3" : "python" + pythonPath = PythonShell.defaultPythonPath; } else pythonPath = options.pythonPath; let pythonOptions = toArray(options.pythonOptions); let scriptArgs = toArray(options.args); @@ -190,6 +192,41 @@ export class PythonShell extends EventEmitter{ } }; + /** + * checks syntax without executing code + * @param {string} code + * @returns {Promise} rejects w/ stderr if syntax failure + */ + static async checkSyntax(code:string){ + let randomInt = Math.floor(Math.random()*10000000000); + let filePath = tmpdir + sep + `pythonShellSyntaxCheck${randomInt}.py` + + // todo: replace this with util.promisify (once we no longer support node v7) + return new Promise((resolve, reject) => { + writeFile(filePath, code, (err)=>{ + if (err) reject(err); + resolve(this.checkSyntaxFile(filePath)); + }); + }); + } + + /** + * checks syntax without executing code + * @param {string} filePath + * @returns {Promise} rejects w/ stderr if syntax failure + */ + static async checkSyntaxFile(filePath:string){ + + let compileCommand = `${this.defaultPythonPath} -m py_compile ${filePath}` + + return new Promise((resolve, reject) => { + exec(compileCommand, (error, stdout, stderr) => { + if(error == null) resolve() + else reject(stderr) + }) + }) + } + /** * Runs a Python script and returns collected messages * @param {string} scriptPath The path to the script to execute diff --git a/test/test-python-shell.ts b/test/test-python-shell.ts index a3d11a3..9ac754d 100644 --- a/test/test-python-shell.ts +++ b/test/test-python-shell.ts @@ -36,6 +36,24 @@ describe('PythonShell', function () { }); }); + describe('#checkSyntax(code:string)', function () { + + // note checkSyntax is a wrapper around checkSyntaxFile + // so this tests checkSyntaxFile as well + + it('should check syntax', function ( done) { + PythonShell.checkSyntax("x=1").then(()=>{ + done(); + }) + }) + + it('should invalidate bad syntax', function ( done) { + PythonShell.checkSyntax("x=").catch(()=>{ + done(); + }) + }) + }) + describe('#run(script, options)', function () { it('should run the script and return output data', function (done) { PythonShell.run('echo_args.py', { From e001d4174110d0933d9b73e7cf86535f24e52aea Mon Sep 17 00:00:00 2001 From: Almenon Date: Tue, 14 Aug 2018 20:10:46 -0700 Subject: [PATCH 19/27] #135 added ability to run a python string --- index.ts | 28 ++++++++++++++++++++++++++-- test/test-python-shell.ts | 20 ++++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/index.ts b/index.ts index 854d35b..4f9161a 100644 --- a/index.ts +++ b/index.ts @@ -3,7 +3,7 @@ import { ChildProcess,spawn, SpawnOptions, exec } from 'child_process'; import {EOL as newline, tmpdir} from 'os'; import {join, sep} from 'path' import {Readable,Writable} from 'stream' -import { writeFile } from 'fs'; +import { writeFile, writeFileSync } from 'fs'; function toArray(source?:T|T[]):T[] { if (typeof source === 'undefined' || source === null) { @@ -198,7 +198,7 @@ export class PythonShell extends EventEmitter{ * @returns {Promise} rejects w/ stderr if syntax failure */ static async checkSyntax(code:string){ - let randomInt = Math.floor(Math.random()*10000000000); + let randomInt = PythonShell.getRandomInt(); let filePath = tmpdir + sep + `pythonShellSyntaxCheck${randomInt}.py` // todo: replace this with util.promisify (once we no longer support node v7) @@ -246,6 +246,23 @@ export class PythonShell extends EventEmitter{ }); }; + /** + * Runs the inputted string of python code and returns collected messages. DO NOT ALLOW UNTRUSTED USER INPUT HERE! + * @param {string} code The python code to execute + * @param {Options} options The execution options + * @param {Function} callback The callback function to invoke with the script results + * @return {PythonShell} The PythonShell instance + */ + static runString(code:string, options?:Options, callback?:(err:PythonShellError, output?:any[])=>any) { + + // put code in temp file + let randomInt = PythonShell.getRandomInt(); + let filePath = tmpdir + sep + `pythonShellFile${randomInt}.py` + writeFileSync(filePath, code); + + return PythonShell.run(filePath, options, callback); + }; + /** * Parses an error thrown from the Python process through stderr * @param {string|Buffer} data The stderr contents to parse @@ -272,6 +289,13 @@ export class PythonShell extends EventEmitter{ return error; }; + /** + * gets a random int from 0-10000000000 + */ + private static getRandomInt(){ + return Math.floor(Math.random()*10000000000); + } + /** * Sends a message to the Python shell through stdin * Override this method to format data to be sent to the Python process diff --git a/test/test-python-shell.ts b/test/test-python-shell.ts index 9ac754d..0da9582 100644 --- a/test/test-python-shell.ts +++ b/test/test-python-shell.ts @@ -54,6 +54,26 @@ describe('PythonShell', function () { }) }) + describe('#runString(script, options)', function () { + before(()=>{ + PythonShell.defaultOptions = {}; + }) + it('should be able to execute a string of python code', function (done) { + PythonShell.runString('print("hello");print("world")', null, function (err, results) { + if (err) return done(err); + results.should.be.an.Array().and.have.lengthOf(2); + results.should.eql(['hello', 'world']); + done(); + }); + }); + after(()=>{ + PythonShell.defaultOptions = { + // reset to match initial value + scriptPath: './test/python' + }; + }) + }); + describe('#run(script, options)', function () { it('should run the script and return output data', function (done) { PythonShell.run('echo_args.py', { From a8c0da7ac983ce046e4142e27591348f565f748c Mon Sep 17 00:00:00 2001 From: Almenon Date: Sun, 19 Aug 2018 19:02:27 -0700 Subject: [PATCH 20/27] #130 fixing stderr causing python-shell to emit error --- index.ts | 2 +- test/python/stderrLogging.py | 26 ++++++++++++++++++++++++++ test/test-python-shell.ts | 11 ++++++++++- 3 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 test/python/stderrLogging.py diff --git a/index.ts b/index.ts index 4f9161a..d12eaab 100644 --- a/index.ts +++ b/index.ts @@ -145,7 +145,7 @@ export class PythonShell extends EventEmitter{ if(!self.stderrHasEnded || !self.stdoutHasEnded || (self.exitCode == null && self.exitSignal == null)) return; let err:PythonShellError; - if (errorData || (self.exitCode && self.exitCode !== 0)) { + if (self.exitCode && self.exitCode !== 0) { if (errorData) { err = self.parseError(errorData); } else { diff --git a/test/python/stderrLogging.py b/test/python/stderrLogging.py new file mode 100644 index 0000000..a90d708 --- /dev/null +++ b/test/python/stderrLogging.py @@ -0,0 +1,26 @@ +# logging example taken from https://docs.python.org/3/howto/logging-cookbook.html +# Note that logging logs to stderr by default + +import logging + +# set up logging to file - see previous section for more details +logging.basicConfig(level=logging.DEBUG) +# define a Handler which writes INFO messages or higher to the sys.stderr +console = logging.StreamHandler() +console.setLevel(logging.INFO) +# add the handler to the root logger +logging.getLogger('').addHandler(console) + +# Now, we can log to the root logger, or any other logger. First the root... +logging.info('Jackdaws love my big sphinx of quartz.') + +# Now, define a couple of other loggers which might represent areas in your +# application: + +logger1 = logging.getLogger('myapp.area1') +logger2 = logging.getLogger('myapp.area2') + +logger1.debug('Quick zephyrs blow, vexing daft Jim.') +logger1.info('How quickly daft jumping zebras vex.') +logger2.warning('Jail zesty vixen who grabbed pay from quack.') +logger2.error('The five boxing wizards jump quickly.') \ No newline at end of file diff --git a/test/test-python-shell.ts b/test/test-python-shell.ts index 0da9582..8c42502 100644 --- a/test/test-python-shell.ts +++ b/test/test-python-shell.ts @@ -296,7 +296,7 @@ describe('PythonShell', function () { done(); }); }); - it('should emit error when error is written to stderr', function (done) { + it('should emit error when the program exits because of an unhandled exception', function (done) { let pyshell = new PythonShell('error.py'); pyshell.on('error', function (err) { err.message.should.be.equalOneOf('ZeroDivisionError: integer division or modulo by zero','ZeroDivisionError: division by zero'); @@ -305,6 +305,15 @@ describe('PythonShell', function () { done(); }); }); + it('should NOT emit error when logging is written to stderr', function (done) { + let pyshell = new PythonShell('stderrLogging.py'); + pyshell.on('error', function (err) { + done(new Error("an error should not have been raised")); + }); + pyshell.on('close', function(){ + done(); + }) + }); }); describe('.parseError(data)', function () { From fd5d4004e90b462ff389306b0edf36523ee2ac27 Mon Sep 17 00:00:00 2001 From: Almenon Date: Sun, 19 Aug 2018 20:56:12 -0700 Subject: [PATCH 21/27] #130 added recieveStderr function --- README.md | 71 +++++++++++++++++++++++++----------- index.ts | 23 +++++++++++- test/python/stderrLogging.py | 9 +---- test/test-python-shell.ts | 42 +++++++++++++++++++++ 4 files changed, 115 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 67747ac..a8019c5 100644 --- a/README.md +++ b/README.md @@ -25,8 +25,8 @@ npm test ### Running a Python script: -```js -var PythonShell = require('python-shell'); +```typescript +import {PythonShell} from 'python-shell'; PythonShell.run('my_script.py', function (err) { if (err) throw err; @@ -38,10 +38,10 @@ If the script writes to stderr or exits with a non-zero code, an error will be t ### Running a Python script with arguments and options: -```js -var PythonShell = require('python-shell'); +```typescript +import {PythonShell} from 'python-shell'; -var options = { +let options = { mode: 'text', pythonPath: 'path/to/python', pythonOptions: ['-u'], // get print results in real-time @@ -58,9 +58,9 @@ PythonShell.run('my_script.py', options, function (err, results) { ### Exchanging data between Node and Python: -```js -var PythonShell = require('python-shell'); -var pyshell = new PythonShell('my_script.py'); +```typescript +import {PythonShell} from 'python-shell'; +let pyshell = new PythonShell('my_script.py'); // sends a message to the Python script via stdin pyshell.send('hello'); @@ -92,9 +92,10 @@ For more details and examples including Python source code, take a look at the t ### Error Handling and extended stack traces -An error will be thrown if the process exits with a non-zero exit code or if data has been written to stderr. Additionally, if "stderr" contains a formatted Python traceback, the error is augmented with Python exception details including a concatenated stack trace. +An error will be thrown if the process exits with a non-zero exit code. Additionally, if "stderr" contains a formatted Python traceback, the error is augmented with Python exception details including a concatenated stack trace. Sample error with traceback (from test/python/error.py): + ``` Traceback (most recent call last): File "test/python/error.py", line 6, in @@ -103,8 +104,10 @@ Traceback (most recent call last): print 1/0 ZeroDivisionError: integer division or modulo by zero ``` + would result into the following error: -```js + +```typescript { [Error: ZeroDivisionError: integer division or modulo by zero] traceback: 'Traceback (most recent call last):\n File "test/python/error.py", line 6, in \n divide_by_zero()\n File "test/python/error.py", line 4, in divide_by_zero\n print 1/0\nZeroDivisionError: integer division or modulo by zero\n', executable: 'python', @@ -113,7 +116,9 @@ would result into the following error: args: null, exitCode: 1 } ``` + and `err.stack` would look like this: + ``` Error: ZeroDivisionError: integer division or modulo by zero at PythonShell.parseError (python-shell/index.js:131:17) @@ -141,6 +146,7 @@ Creates an instance of `PythonShell` and starts the Python process * `binary`: data is streamed as-is through `stdout` and `stdin` * `formatter`: each message to send is transformed using this method, then appended with "\n" * `parser`: each line of data (ending with "\n") is parsed with this function and its result is emitted as a message + * `stderrParser`: each line of logs (ending with "\n") is parsed with this function and its result is emitted as a message * `encoding`: the text encoding to apply on the child process streams (default: "utf8") * `pythonPath`: The path where to locate the "python" executable. Default: "python" * `pythonOptions`: Array of option switches to pass to "python" @@ -154,15 +160,16 @@ PythonShell instances have the following properties: * `command`: the full command arguments passed to the Python executable * `stdin`: the Python stdin stream, used to send data to the child process * `stdout`: the Python stdout stream, used for receiving data from the child process -* `stderr`: the Python stderr stream, used for communicating errors +* `stderr`: the Python stderr stream, used for communicating logs & errors * `childProcess`: the process instance created via `child_process.spawn` * `terminated`: boolean indicating whether the process has exited * `exitCode`: the process exit code, available after the process has ended Example: -```js + +```typescript // create a new instance -var shell = new PythonShell('script.py', options); +let shell = new PythonShell('script.py', options); ``` #### `#defaultOptions` @@ -170,7 +177,8 @@ var shell = new PythonShell('script.py', options); Configures default options for all new instances of PythonShell. Example: -```js + +```typescript // setup a default "scriptPath" PythonShell.defaultOptions = { scriptPath: '../scripts' }; ``` @@ -182,7 +190,8 @@ Runs the Python script and invokes `callback` with the results. The callback con This method is also returning the `PythonShell` instance. Example: -```js + +```typescript // run a simple script PythonShell.run('script.py', function (err, results) { // script finished @@ -194,13 +203,14 @@ PythonShell.run('script.py', function (err, results) { Sends a message to the Python script via stdin. The data is formatted according to the selected mode (text or JSON), or through a custom function when `formatter` is specified. Example: -```js + +```typescript // send a message in text mode -var shell = new PythonShell('script.py', { mode: 'text '}); +let shell = new PythonShell('script.py', { mode: 'text '}); shell.send('hello world!'); // send a message in JSON mode -var shell = new PythonShell('script.py', { mode: 'json '}); +let shell = new PythonShell('script.py', { mode: 'json '}); shell.send({ command: "do_stuff", args: [1, 2, 3] }); ``` @@ -208,6 +218,10 @@ shell.send({ command: "do_stuff", args: [1, 2, 3] }); Parses incoming data from the Python script written via stdout and emits `message` events. This method is called automatically as data is being received from stdout. +#### `.receiveStderr(data)` + +Parses incoming logs from the Python script written via stderr and emits `stderr` events. This method is called automatically as data is being received from stderr. + #### `.end(callback)` Closes the stdin stream, allowing the Python script to finish and exit. The optional callback is invoked when the process is terminated. @@ -221,20 +235,35 @@ Terminates the python script, the optional end callback is invoked if specified. Fires when a chunk of data is parsed from the stdout stream via the `receive` method. If a `parser` method is specified, the result of this function will be the message value. This event is not emitted in binary mode. Example: -```js + +```typescript // receive a message in text mode -var shell = new PythonShell('script.py', { mode: 'text '}); +let shell = new PythonShell('script.py', { mode: 'text '}); shell.on('message', function (message) { // handle message (a line of text from stdout) }); // receive a message in JSON mode -var shell = new PythonShell('script.py', { mode: 'json '}); +let shell = new PythonShell('script.py', { mode: 'json '}); shell.on('message', function (message) { // handle message (a line of text from stdout, parsed as JSON) }); ``` +#### event: `stderr` + +Fires when a chunk of logs is parsed from the stderr stream via the `receiveStderr` method. If a `stderrParser` method is specified, the result of this function will be the message value. This event is not emitted in binary mode. + +Example: + +```typescript +// receive a message in text mode +let shell = new PythonShell('script.py', { mode: 'text '}); +shell.on('stderr', function (stderr) { + // handle stderr (a line of text from stderr) +}); +``` + #### event: `close` Fires when the process has been terminated, with an error or not. diff --git a/index.ts b/index.ts index d12eaab..0342eac 100644 --- a/index.ts +++ b/index.ts @@ -32,6 +32,7 @@ export interface Options extends SpawnOptions{ mode?: 'text'|'json'|'binary' formatter?: (param:string)=>any parser?: (param:string)=>any + stderrParser?: (param:string)=>any encoding?: string pythonPath?: string pythonOptions?: string[] @@ -56,6 +57,7 @@ export class PythonShell extends EventEmitter{ mode:string formatter:(param:string|Object)=>any parser:(param:string)=>any + stderrParser:(param:string)=>any terminated:boolean childProcess:ChildProcess stdin: Writable; @@ -107,6 +109,7 @@ export class PythonShell extends EventEmitter{ this.mode = options.mode || 'text'; this.formatter = resolve('format', options.formatter || this.mode); this.parser = resolve('parse', options.parser || this.mode); + this.stderrParser = resolve('parse', options.stderrParser || this.mode); this.terminated = false; this.childProcess = spawn(pythonPath, this.command, options); @@ -123,6 +126,7 @@ export class PythonShell extends EventEmitter{ // listen to stderr and emit errors for incoming data this.stderr.on('data', function (data) { errorData += ''+data; + self.receiveStderr(data); }); this.stderr.on('end', function(){ @@ -316,6 +320,20 @@ export class PythonShell extends EventEmitter{ * @param {string|Buffer} data The data to parse into messages */ receive(data:string|Buffer) { + return this.recieveInternal(data, 'message'); + }; + + /** + * Parses data received from the Python shell stderr stream and emits "stderr" events + * This method is not used in binary mode + * Override this method to parse incoming logs from the Python process into messages + * @param {string|Buffer} data The data to parse into messages + */ + receiveStderr(data:string|Buffer) { + return this.recieveInternal(data, 'stderr'); + }; + + private recieveInternal(data:string|Buffer, emitType:'message'|'stderr'){ let self = this; let parts = (''+data).split(new RegExp(newline,'g')); @@ -332,11 +350,12 @@ export class PythonShell extends EventEmitter{ this._remaining = lastLine; parts.forEach(function (part) { - self.emit('message', self.parser(part)); + if(emitType == 'message') self.emit(emitType, self.parser(part)); + else if(emitType == 'stderr') self.emit(emitType, self.stderrParser(part)); }); return this; - }; + } /** * Closes the stdin stream, which should cause the process to finish its work and close diff --git a/test/python/stderrLogging.py b/test/python/stderrLogging.py index a90d708..919d43f 100644 --- a/test/python/stderrLogging.py +++ b/test/python/stderrLogging.py @@ -5,11 +5,6 @@ # set up logging to file - see previous section for more details logging.basicConfig(level=logging.DEBUG) -# define a Handler which writes INFO messages or higher to the sys.stderr -console = logging.StreamHandler() -console.setLevel(logging.INFO) -# add the handler to the root logger -logging.getLogger('').addHandler(console) # Now, we can log to the root logger, or any other logger. First the root... logging.info('Jackdaws love my big sphinx of quartz.') @@ -17,8 +12,8 @@ # Now, define a couple of other loggers which might represent areas in your # application: -logger1 = logging.getLogger('myapp.area1') -logger2 = logging.getLogger('myapp.area2') +logger1 = logging.getLogger('log1') +logger2 = logging.getLogger('log2') logger1.debug('Quick zephyrs blow, vexing daft Jim.') logger1.info('How quickly daft jumping zebras vex.') diff --git a/test/test-python-shell.ts b/test/test-python-shell.ts index 8c42502..8a5c3e7 100644 --- a/test/test-python-shell.ts +++ b/test/test-python-shell.ts @@ -275,6 +275,48 @@ describe('PythonShell', function () { }); }); + describe('.receiveStderr(data)', function () { + it('should emit stderr logs as strings when mode is "text"', function (done) { + let pyshell = new PythonShell('stderrLogging.py', { + mode: 'text' + }); + let count = 0; + pyshell.on('stderr', function (stderr) { + count === 0 && stderr.should.be.exactly('INFO:root:Jackdaws love my big sphinx of quartz.'); + count === 1 && stderr.should.be.exactly('DEBUG:log1:Quick zephyrs blow, vexing daft Jim.'); + count++; + }).on('close', function () { + count.should.be.exactly(5); + }).send('hello').send('world').end(done); + }); + it('should not be invoked when mode is "binary"', function (done) { + let pyshell = new PythonShell('echo_args.py', { + args: ['hello', 'world'], + mode: 'binary' + }); + pyshell.receiveStderr = function () { + throw new Error('should not emit stderr in binary mode'); + }; + pyshell.end(done); + }); + it('should use a custom parser function', function (done) { + let pyshell = new PythonShell('stderrLogging.py', { + mode: 'text', + stderrParser: function (stderr) { + return stderr.toUpperCase(); + } + }); + let count = 0; + pyshell.on('stderr', function (stderr) { + count === 0 && stderr.should.be.exactly('INFO:ROOT:JACKDAWS LOVE MY BIG SPHINX OF QUARTZ.'); + count === 1 && stderr.should.be.exactly('DEBUG:LOG1:QUICK ZEPHYRS BLOW, VEXING DAFT JIM.'); + count++; + }).on('close', function () { + count.should.be.exactly(5); + }).send('hello').send('world!').end(done); + }); + }); + describe('.end(callback)', function () { it('should end normally when exit code is zero', function (done) { let pyshell = new PythonShell('exit-code.py'); From d58a0d746a9488386ab4de0ee31dc9cb9cb456ff Mon Sep 17 00:00:00 2001 From: Almenon Date: Sun, 19 Aug 2018 20:59:22 -0700 Subject: [PATCH 22/27] forgot to put compile before appveyorTest --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 40207ec..c98b4fe 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ ], "scripts": { "test": "tsc -p ./ && mocha -R spec", - "appveyorTest": "mocha --ui tdd --reporter mocha-appveyor-reporter test/*.js", + "appveyorTest": "tsc -p ./ && mocha --ui tdd --reporter mocha-appveyor-reporter test/*.js", "compile":"tsc -watch -p ./" }, "dependencies": {}, From a8fc2803e6911f659f1114c6ff31a3180747fde0 Mon Sep 17 00:00:00 2001 From: Almenon Date: Sun, 19 Aug 2018 21:10:37 -0700 Subject: [PATCH 23/27] spaces > tabs :dagger: --- index.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/index.ts b/index.ts index 0342eac..01a323e 100644 --- a/index.ts +++ b/index.ts @@ -206,7 +206,7 @@ export class PythonShell extends EventEmitter{ let filePath = tmpdir + sep + `pythonShellSyntaxCheck${randomInt}.py` // todo: replace this with util.promisify (once we no longer support node v7) - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { writeFile(filePath, code, (err)=>{ if (err) reject(err); resolve(this.checkSyntaxFile(filePath)); @@ -221,14 +221,14 @@ export class PythonShell extends EventEmitter{ */ static async checkSyntaxFile(filePath:string){ - let compileCommand = `${this.defaultPythonPath} -m py_compile ${filePath}` + let compileCommand = `${this.defaultPythonPath} -m py_compile ${filePath}` - return new Promise((resolve, reject) => { - exec(compileCommand, (error, stdout, stderr) => { - if(error == null) resolve() - else reject(stderr) - }) - }) + return new Promise((resolve, reject) => { + exec(compileCommand, (error, stdout, stderr) => { + if(error == null) resolve() + else reject(stderr) + }) + }) } /** From dd1374e464b11981a080da4df9e9d2e104937e49 Mon Sep 17 00:00:00 2001 From: Almenon Date: Sun, 19 Aug 2018 21:19:34 -0700 Subject: [PATCH 24/27] adding runString to documentation and fixing old exit criteria --- README.md | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a8019c5..5f29c6c 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,19 @@ npm test ## Documentation +### Running python code: + +```typescript +import {PythonShell} from 'python-shell'; + +PythonShell.runString('x=1+1;print(x)', function (err) { + if (err) throw err; + console.log('finished'); +}); +``` + +If the script exits with a non-zero code, an error will be thrown. + ### Running a Python script: ```typescript @@ -34,7 +47,7 @@ PythonShell.run('my_script.py', function (err) { }); ``` -If the script writes to stderr or exits with a non-zero code, an error will be thrown. +If the script exits with a non-zero code, an error will be thrown. ### Running a Python script with arguments and options: @@ -198,6 +211,21 @@ PythonShell.run('script.py', function (err, results) { }); ``` +#### `#runString(code, options, callback)` + +Runs the Python code and invokes `callback` with the results. The callback contains the execution error (if any) as well as an array of messages emitted from the Python script. + +This method is also returning the `PythonShell` instance. + +Example: + +```typescript +// run a simple script +PythonShell.run('x=1;print(x)', function (err, results) { + // script finished +}); +``` + #### `.send(message)` Sends a message to the Python script via stdin. The data is formatted according to the selected mode (text or JSON), or through a custom function when `formatter` is specified. From 7c1116a1cae446adec7d4bcb87c6cda298d65a7a Mon Sep 17 00:00:00 2001 From: Almenon Date: Sun, 19 Aug 2018 21:22:33 -0700 Subject: [PATCH 25/27] added checkSyntax to readme --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 5f29c6c..ddcfb92 100644 --- a/README.md +++ b/README.md @@ -258,6 +258,16 @@ Closes the stdin stream, allowing the Python script to finish and exit. The opti Terminates the python script, the optional end callback is invoked if specified. A kill signal may be provided by `signal`, if `signal` is not specified SIGTERM is sent. +#### `checkSyntax(code:string)` + +Checks the syntax of the code and returns a promise. +Promise is rejected if there is a syntax error. + +#### `checkSyntaxFile(filePath:string)` + +Checks the syntax of the file and returns a promise. +Promise is rejected if there is a syntax error. + #### event: `message` Fires when a chunk of data is parsed from the stdout stream via the `receive` method. If a `parser` method is specified, the result of this function will be the message value. This event is not emitted in binary mode. From dc94446c53e936822f441e378ea7b0744478db6d Mon Sep 17 00:00:00 2001 From: Almenon Date: Mon, 20 Aug 2018 07:54:42 -0700 Subject: [PATCH 26/27] downgrading readme back to v0.5 while waiting for v1 publish --- README.md | 111 +++++++++++------------------------------------------- 1 file changed, 22 insertions(+), 89 deletions(-) diff --git a/README.md b/README.md index ddcfb92..67747ac 100644 --- a/README.md +++ b/README.md @@ -23,23 +23,10 @@ npm test ## Documentation -### Running python code: - -```typescript -import {PythonShell} from 'python-shell'; - -PythonShell.runString('x=1+1;print(x)', function (err) { - if (err) throw err; - console.log('finished'); -}); -``` - -If the script exits with a non-zero code, an error will be thrown. - ### Running a Python script: -```typescript -import {PythonShell} from 'python-shell'; +```js +var PythonShell = require('python-shell'); PythonShell.run('my_script.py', function (err) { if (err) throw err; @@ -47,14 +34,14 @@ PythonShell.run('my_script.py', function (err) { }); ``` -If the script exits with a non-zero code, an error will be thrown. +If the script writes to stderr or exits with a non-zero code, an error will be thrown. ### Running a Python script with arguments and options: -```typescript -import {PythonShell} from 'python-shell'; +```js +var PythonShell = require('python-shell'); -let options = { +var options = { mode: 'text', pythonPath: 'path/to/python', pythonOptions: ['-u'], // get print results in real-time @@ -71,9 +58,9 @@ PythonShell.run('my_script.py', options, function (err, results) { ### Exchanging data between Node and Python: -```typescript -import {PythonShell} from 'python-shell'; -let pyshell = new PythonShell('my_script.py'); +```js +var PythonShell = require('python-shell'); +var pyshell = new PythonShell('my_script.py'); // sends a message to the Python script via stdin pyshell.send('hello'); @@ -105,10 +92,9 @@ For more details and examples including Python source code, take a look at the t ### Error Handling and extended stack traces -An error will be thrown if the process exits with a non-zero exit code. Additionally, if "stderr" contains a formatted Python traceback, the error is augmented with Python exception details including a concatenated stack trace. +An error will be thrown if the process exits with a non-zero exit code or if data has been written to stderr. Additionally, if "stderr" contains a formatted Python traceback, the error is augmented with Python exception details including a concatenated stack trace. Sample error with traceback (from test/python/error.py): - ``` Traceback (most recent call last): File "test/python/error.py", line 6, in @@ -117,10 +103,8 @@ Traceback (most recent call last): print 1/0 ZeroDivisionError: integer division or modulo by zero ``` - would result into the following error: - -```typescript +```js { [Error: ZeroDivisionError: integer division or modulo by zero] traceback: 'Traceback (most recent call last):\n File "test/python/error.py", line 6, in \n divide_by_zero()\n File "test/python/error.py", line 4, in divide_by_zero\n print 1/0\nZeroDivisionError: integer division or modulo by zero\n', executable: 'python', @@ -129,9 +113,7 @@ would result into the following error: args: null, exitCode: 1 } ``` - and `err.stack` would look like this: - ``` Error: ZeroDivisionError: integer division or modulo by zero at PythonShell.parseError (python-shell/index.js:131:17) @@ -159,7 +141,6 @@ Creates an instance of `PythonShell` and starts the Python process * `binary`: data is streamed as-is through `stdout` and `stdin` * `formatter`: each message to send is transformed using this method, then appended with "\n" * `parser`: each line of data (ending with "\n") is parsed with this function and its result is emitted as a message - * `stderrParser`: each line of logs (ending with "\n") is parsed with this function and its result is emitted as a message * `encoding`: the text encoding to apply on the child process streams (default: "utf8") * `pythonPath`: The path where to locate the "python" executable. Default: "python" * `pythonOptions`: Array of option switches to pass to "python" @@ -173,16 +154,15 @@ PythonShell instances have the following properties: * `command`: the full command arguments passed to the Python executable * `stdin`: the Python stdin stream, used to send data to the child process * `stdout`: the Python stdout stream, used for receiving data from the child process -* `stderr`: the Python stderr stream, used for communicating logs & errors +* `stderr`: the Python stderr stream, used for communicating errors * `childProcess`: the process instance created via `child_process.spawn` * `terminated`: boolean indicating whether the process has exited * `exitCode`: the process exit code, available after the process has ended Example: - -```typescript +```js // create a new instance -let shell = new PythonShell('script.py', options); +var shell = new PythonShell('script.py', options); ``` #### `#defaultOptions` @@ -190,8 +170,7 @@ let shell = new PythonShell('script.py', options); Configures default options for all new instances of PythonShell. Example: - -```typescript +```js // setup a default "scriptPath" PythonShell.defaultOptions = { scriptPath: '../scripts' }; ``` @@ -203,42 +182,25 @@ Runs the Python script and invokes `callback` with the results. The callback con This method is also returning the `PythonShell` instance. Example: - -```typescript +```js // run a simple script PythonShell.run('script.py', function (err, results) { // script finished }); ``` -#### `#runString(code, options, callback)` - -Runs the Python code and invokes `callback` with the results. The callback contains the execution error (if any) as well as an array of messages emitted from the Python script. - -This method is also returning the `PythonShell` instance. - -Example: - -```typescript -// run a simple script -PythonShell.run('x=1;print(x)', function (err, results) { - // script finished -}); -``` - #### `.send(message)` Sends a message to the Python script via stdin. The data is formatted according to the selected mode (text or JSON), or through a custom function when `formatter` is specified. Example: - -```typescript +```js // send a message in text mode -let shell = new PythonShell('script.py', { mode: 'text '}); +var shell = new PythonShell('script.py', { mode: 'text '}); shell.send('hello world!'); // send a message in JSON mode -let shell = new PythonShell('script.py', { mode: 'json '}); +var shell = new PythonShell('script.py', { mode: 'json '}); shell.send({ command: "do_stuff", args: [1, 2, 3] }); ``` @@ -246,10 +208,6 @@ shell.send({ command: "do_stuff", args: [1, 2, 3] }); Parses incoming data from the Python script written via stdout and emits `message` events. This method is called automatically as data is being received from stdout. -#### `.receiveStderr(data)` - -Parses incoming logs from the Python script written via stderr and emits `stderr` events. This method is called automatically as data is being received from stderr. - #### `.end(callback)` Closes the stdin stream, allowing the Python script to finish and exit. The optional callback is invoked when the process is terminated. @@ -258,50 +216,25 @@ Closes the stdin stream, allowing the Python script to finish and exit. The opti Terminates the python script, the optional end callback is invoked if specified. A kill signal may be provided by `signal`, if `signal` is not specified SIGTERM is sent. -#### `checkSyntax(code:string)` - -Checks the syntax of the code and returns a promise. -Promise is rejected if there is a syntax error. - -#### `checkSyntaxFile(filePath:string)` - -Checks the syntax of the file and returns a promise. -Promise is rejected if there is a syntax error. - #### event: `message` Fires when a chunk of data is parsed from the stdout stream via the `receive` method. If a `parser` method is specified, the result of this function will be the message value. This event is not emitted in binary mode. Example: - -```typescript +```js // receive a message in text mode -let shell = new PythonShell('script.py', { mode: 'text '}); +var shell = new PythonShell('script.py', { mode: 'text '}); shell.on('message', function (message) { // handle message (a line of text from stdout) }); // receive a message in JSON mode -let shell = new PythonShell('script.py', { mode: 'json '}); +var shell = new PythonShell('script.py', { mode: 'json '}); shell.on('message', function (message) { // handle message (a line of text from stdout, parsed as JSON) }); ``` -#### event: `stderr` - -Fires when a chunk of logs is parsed from the stderr stream via the `receiveStderr` method. If a `stderrParser` method is specified, the result of this function will be the message value. This event is not emitted in binary mode. - -Example: - -```typescript -// receive a message in text mode -let shell = new PythonShell('script.py', { mode: 'text '}); -shell.on('stderr', function (stderr) { - // handle stderr (a line of text from stderr) -}); -``` - #### event: `close` Fires when the process has been terminated, with an error or not. From 9fbf80afab1cf1c2291bcb9366cefdc862332425 Mon Sep 17 00:00:00 2001 From: Almenon Date: Sun, 2 Sep 2018 10:30:12 -0700 Subject: [PATCH 27/27] Revert "downgrading readme back to v0.5 while waiting for v1 publish" Reverting revert back to new readme now that i can publish v1 --- README.md | 111 +++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 89 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 67747ac..ddcfb92 100644 --- a/README.md +++ b/README.md @@ -23,10 +23,23 @@ npm test ## Documentation +### Running python code: + +```typescript +import {PythonShell} from 'python-shell'; + +PythonShell.runString('x=1+1;print(x)', function (err) { + if (err) throw err; + console.log('finished'); +}); +``` + +If the script exits with a non-zero code, an error will be thrown. + ### Running a Python script: -```js -var PythonShell = require('python-shell'); +```typescript +import {PythonShell} from 'python-shell'; PythonShell.run('my_script.py', function (err) { if (err) throw err; @@ -34,14 +47,14 @@ PythonShell.run('my_script.py', function (err) { }); ``` -If the script writes to stderr or exits with a non-zero code, an error will be thrown. +If the script exits with a non-zero code, an error will be thrown. ### Running a Python script with arguments and options: -```js -var PythonShell = require('python-shell'); +```typescript +import {PythonShell} from 'python-shell'; -var options = { +let options = { mode: 'text', pythonPath: 'path/to/python', pythonOptions: ['-u'], // get print results in real-time @@ -58,9 +71,9 @@ PythonShell.run('my_script.py', options, function (err, results) { ### Exchanging data between Node and Python: -```js -var PythonShell = require('python-shell'); -var pyshell = new PythonShell('my_script.py'); +```typescript +import {PythonShell} from 'python-shell'; +let pyshell = new PythonShell('my_script.py'); // sends a message to the Python script via stdin pyshell.send('hello'); @@ -92,9 +105,10 @@ For more details and examples including Python source code, take a look at the t ### Error Handling and extended stack traces -An error will be thrown if the process exits with a non-zero exit code or if data has been written to stderr. Additionally, if "stderr" contains a formatted Python traceback, the error is augmented with Python exception details including a concatenated stack trace. +An error will be thrown if the process exits with a non-zero exit code. Additionally, if "stderr" contains a formatted Python traceback, the error is augmented with Python exception details including a concatenated stack trace. Sample error with traceback (from test/python/error.py): + ``` Traceback (most recent call last): File "test/python/error.py", line 6, in @@ -103,8 +117,10 @@ Traceback (most recent call last): print 1/0 ZeroDivisionError: integer division or modulo by zero ``` + would result into the following error: -```js + +```typescript { [Error: ZeroDivisionError: integer division or modulo by zero] traceback: 'Traceback (most recent call last):\n File "test/python/error.py", line 6, in \n divide_by_zero()\n File "test/python/error.py", line 4, in divide_by_zero\n print 1/0\nZeroDivisionError: integer division or modulo by zero\n', executable: 'python', @@ -113,7 +129,9 @@ would result into the following error: args: null, exitCode: 1 } ``` + and `err.stack` would look like this: + ``` Error: ZeroDivisionError: integer division or modulo by zero at PythonShell.parseError (python-shell/index.js:131:17) @@ -141,6 +159,7 @@ Creates an instance of `PythonShell` and starts the Python process * `binary`: data is streamed as-is through `stdout` and `stdin` * `formatter`: each message to send is transformed using this method, then appended with "\n" * `parser`: each line of data (ending with "\n") is parsed with this function and its result is emitted as a message + * `stderrParser`: each line of logs (ending with "\n") is parsed with this function and its result is emitted as a message * `encoding`: the text encoding to apply on the child process streams (default: "utf8") * `pythonPath`: The path where to locate the "python" executable. Default: "python" * `pythonOptions`: Array of option switches to pass to "python" @@ -154,15 +173,16 @@ PythonShell instances have the following properties: * `command`: the full command arguments passed to the Python executable * `stdin`: the Python stdin stream, used to send data to the child process * `stdout`: the Python stdout stream, used for receiving data from the child process -* `stderr`: the Python stderr stream, used for communicating errors +* `stderr`: the Python stderr stream, used for communicating logs & errors * `childProcess`: the process instance created via `child_process.spawn` * `terminated`: boolean indicating whether the process has exited * `exitCode`: the process exit code, available after the process has ended Example: -```js + +```typescript // create a new instance -var shell = new PythonShell('script.py', options); +let shell = new PythonShell('script.py', options); ``` #### `#defaultOptions` @@ -170,7 +190,8 @@ var shell = new PythonShell('script.py', options); Configures default options for all new instances of PythonShell. Example: -```js + +```typescript // setup a default "scriptPath" PythonShell.defaultOptions = { scriptPath: '../scripts' }; ``` @@ -182,25 +203,42 @@ Runs the Python script and invokes `callback` with the results. The callback con This method is also returning the `PythonShell` instance. Example: -```js + +```typescript // run a simple script PythonShell.run('script.py', function (err, results) { // script finished }); ``` +#### `#runString(code, options, callback)` + +Runs the Python code and invokes `callback` with the results. The callback contains the execution error (if any) as well as an array of messages emitted from the Python script. + +This method is also returning the `PythonShell` instance. + +Example: + +```typescript +// run a simple script +PythonShell.run('x=1;print(x)', function (err, results) { + // script finished +}); +``` + #### `.send(message)` Sends a message to the Python script via stdin. The data is formatted according to the selected mode (text or JSON), or through a custom function when `formatter` is specified. Example: -```js + +```typescript // send a message in text mode -var shell = new PythonShell('script.py', { mode: 'text '}); +let shell = new PythonShell('script.py', { mode: 'text '}); shell.send('hello world!'); // send a message in JSON mode -var shell = new PythonShell('script.py', { mode: 'json '}); +let shell = new PythonShell('script.py', { mode: 'json '}); shell.send({ command: "do_stuff", args: [1, 2, 3] }); ``` @@ -208,6 +246,10 @@ shell.send({ command: "do_stuff", args: [1, 2, 3] }); Parses incoming data from the Python script written via stdout and emits `message` events. This method is called automatically as data is being received from stdout. +#### `.receiveStderr(data)` + +Parses incoming logs from the Python script written via stderr and emits `stderr` events. This method is called automatically as data is being received from stderr. + #### `.end(callback)` Closes the stdin stream, allowing the Python script to finish and exit. The optional callback is invoked when the process is terminated. @@ -216,25 +258,50 @@ Closes the stdin stream, allowing the Python script to finish and exit. The opti Terminates the python script, the optional end callback is invoked if specified. A kill signal may be provided by `signal`, if `signal` is not specified SIGTERM is sent. +#### `checkSyntax(code:string)` + +Checks the syntax of the code and returns a promise. +Promise is rejected if there is a syntax error. + +#### `checkSyntaxFile(filePath:string)` + +Checks the syntax of the file and returns a promise. +Promise is rejected if there is a syntax error. + #### event: `message` Fires when a chunk of data is parsed from the stdout stream via the `receive` method. If a `parser` method is specified, the result of this function will be the message value. This event is not emitted in binary mode. Example: -```js + +```typescript // receive a message in text mode -var shell = new PythonShell('script.py', { mode: 'text '}); +let shell = new PythonShell('script.py', { mode: 'text '}); shell.on('message', function (message) { // handle message (a line of text from stdout) }); // receive a message in JSON mode -var shell = new PythonShell('script.py', { mode: 'json '}); +let shell = new PythonShell('script.py', { mode: 'json '}); shell.on('message', function (message) { // handle message (a line of text from stdout, parsed as JSON) }); ``` +#### event: `stderr` + +Fires when a chunk of logs is parsed from the stderr stream via the `receiveStderr` method. If a `stderrParser` method is specified, the result of this function will be the message value. This event is not emitted in binary mode. + +Example: + +```typescript +// receive a message in text mode +let shell = new PythonShell('script.py', { mode: 'text '}); +shell.on('stderr', function (stderr) { + // handle stderr (a line of text from stderr) +}); +``` + #### event: `close` Fires when the process has been terminated, with an error or not.