{ "info": { "author": "Shmuel Amar", "author_email": "", "bugtrack_url": null, "classifiers": [ "Development Status :: 4 - Beta", "Intended Audience :: Developers", "Intended Audience :: Information Technology", "Intended Audience :: System Administrators", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3 :: Only" ], "description": "# CBOX - CLI ToolBox\n\n[![PyPI](https://img.shields.io/pypi/v/cbox.svg)](https://pypi.python.org/pypi/cbox/0.1.0)\n[![PyPI](https://img.shields.io/pypi/pyversions/cbox.svg)](https://pypi.python.org/pypi/cbox/0.1.0)\n[![Build Status](https://travis-ci.org/shmuelamar/cbox.svg?branch=master)](https://travis-ci.org/shmuelamar/cbox)\n[![AppVeyor](https://img.shields.io/appveyor/ci/gruntjs/grunt.svg)](https://ci.appveyor.com/project/shmuelamar/cbox)\n[![Codecov](https://img.shields.io/codecov/c/github/shmuelamar/cbox.svg)](https://codecov.io/gh/shmuelamar/cbox)\n[![PyPI](https://img.shields.io/pypi/wheel/cbox.svg)]()\n[![PyPI](https://img.shields.io/pypi/l/cbox.svg)]()\n\n### convert any python function to unix-style command\n\nThe Unix Philosophy (from [wikipedia](https://en.wikipedia.org/wiki/Unix_philosophy#Origin)):\n> * *Write programs that do one thing and do it well.*\n> * *Write programs to work together.*\n> * *Write programs to handle text streams, because that is a universal interface.*\n\n
\n\n## Features\n* supports pipes\n* concurrency (threading or asyncio)\n* supports error handling (redirected to stderr)\n* supports for inline code in cli style\n* various output processing options (filtering, early stopping..)\n* supports multiple types of pipe processing (lines, chars..)\n* automatic docstring parsing for description and arguments help\n* automatic type annotation and defaults parsing\n* returns the correct exitcode based on errors\n* supports only python3 (yes this is a feature)\n* supports subcommands\n\n## Quickstart\n\n**install:**\n\n```bash\npip install -U cbox\n```\n\n**example usage:**\n```python\n#!/usr/bin/env python3\n# hello.py\nimport cbox\n\n@cbox.cmd\ndef hello(name: str):\n \"\"\"greets a person by its name.\n\n :param name: the name of the person\n \"\"\"\n print(f'hello {name}!')\n\nif __name__ == '__main__':\n cbox.main(hello)\n```\n\n**run it:**\n\n```bash\n$ ./hello.py --name world\nhello world!\n\n$ ./hello.py --help\nusage: hello.py [-h] --name NAME\n\ngreets a person by its name.\n\noptional arguments:\n -h, --help show this help message and exit\n --name NAME the name of the person\n```\n\n**cli inline example:**\n\n```bash\n$ echo -e \"192.168.1.1\\n192.168.2.3\\ngoogle.com\" | cbox --modules re 're.findall(\"(?:\\d+\\.)+\\d+\", s)'\n192.168.1.1\n192.168.2.3\n```\n\n*for more info about cbox inline run `cbox --help`*\n\n\n## The Story\nonce upon a time, a python programmer named dave, had a simple text file. \n\n**langs.txt**\n```text\npython http://python.org\nlisp http://lisp-lang.org\nruby http://ruby-lang.org\n```\n\nall dave wanted is to get the list of languages from that file.\n\nour dave heard that unix commands are the best, so he started googling them out.\n\nhe started reading about *awk*, *grep*, *sed*, *tr*, *cut* and others but couldn't \nremember how to use all of them - after all he is a python programmer and wants to use python.\n\nfortunately, our little dave found out about **`cbox`** - a simple way to convert \nany python function into unix-style command line!\n\nnow dave can process files using python easily!\n\n### simple example\n```python\n#!/usr/bin/env python3\n# first.py\nimport cbox\n\n@cbox.stream()\ndef first(line):\n return line.split()[0]\n\nif __name__ == '__main__':\n cbox.main(first)\n```\n\nrunning it:\n\n```bash\n$ cat langs.txt | ./first.py \npython\nlisp\nruby\n```\n\n**or inline cli style:**\n\n```bash\n$ cat langs.txt | cbox 's.split()[0]'\n```\n\n*note: **`s`** is the input variable*\n\n\nnow dave is satisfied, so like every satisfied programmer - he wants more!\n\ndave now wants to get a list of the langs urls.\n\n### arguments and help message\n\n```python\n#!/usr/bin/env python3\n# nth-item.py\nimport cbox\n\n@cbox.stream()\n# we can pass default values and use type annotations for correct types\ndef nth_item(line, n: int = 0):\n \"\"\"returns the nth item from each line.\n\n :param n: the number of item position starting from 0\n \"\"\"\n return line.split()[n]\n\nif __name__ == '__main__':\n cbox.main(nth_item)\n```\n\nrunning it:\n\n```bash\n#!/usr/bin/env python3\n$ ./nth-item.py --help\nusage: nth-item.py [-h] [-n N]\n\nreturns the nth item from each line.\n\noptional arguments:\n -h, --help show this help message and exit\n -n N the number of item position starting from 0\n```\n\n```bash\n$ cat langs.txt | ./nth-item.py \npython\nlisp\nruby\n```\n\n```bash\n$ cat langs.txt | ./nth-item.py -n 1\nhttp://python.org\nhttp://lisp-lang.org\nhttp://ruby-lang.org\n```\n\nnow dave wants to get the status out of each url, for this we can use `requests`.\n\nbut to process a large list it will take too long, so he better off use threads.\n\n### threading example\n\n```python\n#!/usr/bin/env python3\n# url-status.py\nimport cbox\nimport requests\n\n@cbox.stream(worker_type='thread', max_workers=4)\ndef url_status(line):\n resp = requests.get(line)\n return f'{line} - {resp.status_code}'\n\nif __name__ == '__main__':\n cbox.main(url_status)\n```\n\n**running it:**\n\n```bash\n$ cat langs.txt | ./nth-line.py -n 1 | ./url-status.py \nhttp://python.org - 200\nhttp://lisp-lang.org - 200\nhttp://ruby-lang.org - 200\n```\n\n**or inline cli style**\n\n```bash\n$ cat langs.txt | cbox 's.split()[1]' | cbox -m requests -w thread -c 4 'f\"{s} - {requests.get(s).status_code}\"'\nhttp://python.org - 200\nhttp://lisp-lang.org - 200\nhttp://ruby-lang.org - 200\n```\n\n\n## Advanced Usage\n### Error handling\n\n```python\n#!/usr/bin/env python3\n# numbersonly.py\nimport cbox\n\n@cbox.stream()\ndef numbersonly(line):\n \"\"\"returns the lines containing only numbers. bad lines reported to stderr.\n if any bad line is detected, exits with exitcode 2.\n \"\"\"\n if not line.isnumeric():\n raise ValueError('{} is not a number'.format(line))\n return line\n\nif __name__ == '__main__':\n cbox.main(numbersonly)\n```\n\nall errors are redirected to `stderr`:\n\n```bash\n$ echo -e \"123\\nabc\\n567\" | ./numbersonly.py\n123\nTraceback (most recent call last):\n File \"/home/shmulik/cs/cbox/cbox/concurrency.py\", line 54, in _simple_runner\n yield func(item, **kwargs), None\n File \"numbersonly.py\", line 11, in numbersonly\n raise ValueError('{} is not a number'.format(line))\nValueError: abc is not a number\n\n567\n\n```\n\nwe can ignore the `stderr` stream by redirecting it to `/dev/null`:\n```bash\n$ echo -e \"123\\nabc\\n567\" | ./numbersonly.py 2>/dev/null\n123\n567\n```\n\nour command returns 2 as the [exit code](https://en.wikipedia.org/wiki/Exit_status#Shell_and_scripts), \nindicating an error, we can get the last error code by running `echo $?`:\n\n```bash\n$ echo $?\n2\n```\n\n### Filtering\n\n`cbox.stream` supports three types of return values - `str`, `None` and `iterable` of `str`s.\n\n`None` skips and outputs nothing, `str` is outputted normally and each item in the `iterable` is treated as `str`.\n\nhere is a simple example:\n\n```python\n#!/usr/bin/env python3\n# extract-domains.py\nimport re\nimport cbox\n\n@cbox.stream()\ndef extract_domains(line):\n \"\"\"tries to extract all the domains from the input using simple regex\"\"\"\n return re.findall(r'(?:\\w+\\.)+\\w+', line) or None # or None can be omitted\n\nif __name__ == '__main__':\n cbox.main(extract_domains)\n```\n\nwe can now run it (notice that we can have multiple domains or zero domains on each line):\n```bash\n$ echo -e \"google.com cbox.com\\nhello\\nfacebook.com\" | ./extract-domains.py \ngoogle.com\ncbox.com\nfacebook.com\n```\n\n### Early Stopping\n`cbox.stream` supports early stopping, i.e. stopping before reading the whole `stdin`\n\nexample implementing a simple `head` command\n```python\n#!/usr/bin/env python3\n# head.py\nimport cbox\n\ncounter = 0\n\n\n@cbox.stream()\ndef head(line, n: int):\n \"\"\"returns the first `n` lines\"\"\"\n global counter\n counter += 1\n\n if counter > n:\n raise cbox.Stop() # can also raise StopIteration()\n return line\n\n\nif __name__ == '__main__':\n cbox.main(head)\n```\n\ngetting the first 2 lines:\n\n```bash\n$ echo -e \"1\\n2\\n3\\n4\" | ./head.py -n 2\n1\n2\n```\n\n\n### Concurrency\n\n`cbox` supports **simple (default)**, **asyncio** and **thread** workers. we can use asyncio like this:\n\n```python\n#!/usr/bin/env python3\n# tcping.py\nimport asyncio\nimport cbox\n\n@cbox.stream(worker_type='asyncio', workers_window=30)\nasync def tcping(domain, timeout: int=3):\n loop = asyncio.get_event_loop()\n\n fut = asyncio.open_connection(domain, 80, loop=loop)\n try:\n reader, writer = await asyncio.wait_for(fut, timeout=timeout)\n writer.close()\n status = 'up'\n except (OSError, asyncio.TimeoutError):\n status = 'down'\n\n return '{} is {}'.format(domain, status)\n\nif __name__ == '__main__':\n cbox.main(tcping)\n```\n\nthis will try open up to 30 connections in parallel using asyncio. \n\nrunning it:\n\n```bash\n$ echo -e \"192.168.1.1\\n192.168.2.3\\ngoogle.com\" | ./tcping.py\n192.168.1.1 is down\n192.168.2.3 is down\ngoogle.com is up\n```\n\n__more examples can be found on `examples/` dir__\n\n## Contributing\ncbox is an open source software and intended for everyone. please feel free to create PRs, add examples to examples/ dir, request features and ask questions.\n\n### Creating Local Dev Env\n\nafter cloning the repo, you'll need to install test dependencies from `test-requirements.txt`.\n\nthere is a simple `make` command to install them (you'll need [`miniconda`](https://conda.io/miniconda.html) installed):\n\n```bash\n$ make test-setup\n```\n\nor you can use `pip install -r test-requirements.txt` (preferably in new virtualenv).\n\nnow ensure all tests passes and runs locally:\n\n```bash\n$ make test\n```", "description_content_type": null, "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/shmuelamar/cbox", "keywords": "unix command pipes cli", "license": "MIT", "maintainer": "", "maintainer_email": "", "name": "cbox", "package_url": "https://pypi.org/project/cbox/", "platform": "", "project_url": "https://pypi.org/project/cbox/", "project_urls": { "Homepage": "https://github.com/shmuelamar/cbox" }, "release_url": "https://pypi.org/project/cbox/0.5.0/", "requires_dist": null, "requires_python": "", "summary": "convert any python function to unix-style command", "version": "0.5.0" }, "last_serial": 3663030, "releases": { "0.1.0": [ { "comment_text": "", "digests": { "md5": "076145495b4c3e61f921328c62d81457", "sha256": "3ec19a3d91b4471f50a0c21533be2de0b387fd13bbd81e7beedd50c15abcb281" }, "downloads": -1, "filename": "cbox-0.1.0-py3-none-any.whl", "has_sig": true, "md5_digest": "076145495b4c3e61f921328c62d81457", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 9709, "upload_time": "2017-08-31T00:04:44", "url": "https://files.pythonhosted.org/packages/0a/a7/d76586cfde39ab9c056e0ada064465d853b5fc1b7cabb83e22e99452476a/cbox-0.1.0-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "7b0494bd484d2142512e1ebb614e3c41", "sha256": "79ca45303be9defaa0a2bfc4d0c95e62038e6341d695dcb2f0ebdf3602f3da28" }, "downloads": -1, "filename": "cbox-0.1.0.tar.gz", "has_sig": true, "md5_digest": "7b0494bd484d2142512e1ebb614e3c41", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 7363, "upload_time": "2017-08-31T00:05:19", "url": "https://files.pythonhosted.org/packages/a8/d7/e2f870ffc387c8ad6e7bfad0d0856bedd88907351a5b4c1f31853a5de039/cbox-0.1.0.tar.gz" } ], "0.2.0": [ { "comment_text": "", "digests": { "md5": "27ce445035f866f550d8c04e0a6ff838", "sha256": "dd218a29796d0f9f6b735fa048bb685595f4211df25aa6f7a288598063f558aa" }, "downloads": -1, "filename": "cbox-0.2.0-py3-none-any.whl", "has_sig": true, "md5_digest": "27ce445035f866f550d8c04e0a6ff838", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 13222, "upload_time": "2017-09-08T15:39:36", "url": "https://files.pythonhosted.org/packages/35/30/da123cdf89e1ce7e85abe20939347d224218b66ea91d0903761df2cf3aa1/cbox-0.2.0-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "f12acde7e2c59b5b7b499efaa7645ec4", "sha256": "264651028ec943e9d34edca6524f181b6bb1dcc52f9299dda329f7796a3a8c11" }, "downloads": -1, "filename": "cbox-0.2.0.tar.gz", "has_sig": true, "md5_digest": "f12acde7e2c59b5b7b499efaa7645ec4", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 9772, "upload_time": "2017-09-08T15:38:26", "url": "https://files.pythonhosted.org/packages/61/db/fe43b528e14156c3576ebbbf4c9fd90b80fdd5c88eee4d890505a5a5bf59/cbox-0.2.0.tar.gz" } ], "0.3.0": [ { "comment_text": "", "digests": { "md5": "341669e666ca530ec83075739616c153", "sha256": "c2188c8f1874570097b281651d98b5a69853f650467ad96edc8677e1c28fae9f" }, "downloads": -1, "filename": "cbox-0.3.0-py3-none-any.whl", "has_sig": true, "md5_digest": "341669e666ca530ec83075739616c153", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 14119, "upload_time": "2017-10-07T08:25:37", "url": "https://files.pythonhosted.org/packages/30/08/4dda89a987c66c0388806f5252d06b7f1fd186eceed03fd604c7ed85b014/cbox-0.3.0-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "14e7ea455c33557bfd321f792567328b", "sha256": "394268ce4ca6e158a2735500a71459f370b2cacddd49e9a34c69dd7815c0088b" }, "downloads": -1, "filename": "cbox-0.3.0.tar.gz", "has_sig": true, "md5_digest": "14e7ea455c33557bfd321f792567328b", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 10371, "upload_time": "2017-10-07T08:25:27", "url": "https://files.pythonhosted.org/packages/e4/1b/a80ddd7478864dd9fe090bf9fb536b2de1f83f15077d99e30f90e5456011/cbox-0.3.0.tar.gz" } ], "0.4.0": [ { "comment_text": "", "digests": { "md5": "393d06ee6036fe84581882504616a636", "sha256": "3bbf742f167694e5e07bed9e5ba0a0b080844188f08d8f3d6631b14cb2b53079" }, "downloads": -1, "filename": "cbox-0.4.0-py3-none-any.whl", "has_sig": true, "md5_digest": "393d06ee6036fe84581882504616a636", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 16165, "upload_time": "2017-11-18T23:05:29", "url": "https://files.pythonhosted.org/packages/7a/9d/57a68bb4dd155b2c1e0009caa7fc3ab7108e6a492c4e5ab472262481a56e/cbox-0.4.0-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "7e3bd80c8af816163b2f842d1c50d1e0", "sha256": "a540370367437c3d67b50b9209f302113f0767594f9d1548f530410b91fd144c" }, "downloads": -1, "filename": "cbox-0.4.0.tar.gz", "has_sig": true, "md5_digest": "7e3bd80c8af816163b2f842d1c50d1e0", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 11613, "upload_time": "2017-11-18T23:05:22", "url": "https://files.pythonhosted.org/packages/91/f5/30f74ea27777b31dd182f2c40e2b29381f3f61139e462a064d85120852a8/cbox-0.4.0.tar.gz" } ], "0.5.0": [ { "comment_text": "", "digests": { "md5": "a5a20ab918aad5d710c651c7d1a6ee40", "sha256": "28356f7d5ac6b2095a92701dbadfae2bbe2df5c80c530fdbb5b8c033468c6f8e" }, "downloads": -1, "filename": "cbox-0.5.0-py3-none-any.whl", "has_sig": true, "md5_digest": "a5a20ab918aad5d710c651c7d1a6ee40", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 16884, "upload_time": "2018-03-12T20:22:15", "url": "https://files.pythonhosted.org/packages/0d/b6/0f8a37986a0cddff8e33bb871040dbaab1f21797abf8ad85f250c57c4c6c/cbox-0.5.0-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "23249ac2a0179b8e2f2573ec140e177c", "sha256": "bd98ba08a7b2807c6b657b4deda64e0f652977049f01b7598e9e92588a600016" }, "downloads": -1, "filename": "cbox-0.5.0.tar.gz", "has_sig": true, "md5_digest": "23249ac2a0179b8e2f2573ec140e177c", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 12140, "upload_time": "2018-03-12T20:22:07", "url": "https://files.pythonhosted.org/packages/96/47/13a3715ef774cb7366ce1d87ed74dd6d1b74bcd96c8a7c85d7ae4a456246/cbox-0.5.0.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "a5a20ab918aad5d710c651c7d1a6ee40", "sha256": "28356f7d5ac6b2095a92701dbadfae2bbe2df5c80c530fdbb5b8c033468c6f8e" }, "downloads": -1, "filename": "cbox-0.5.0-py3-none-any.whl", "has_sig": true, "md5_digest": "a5a20ab918aad5d710c651c7d1a6ee40", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 16884, "upload_time": "2018-03-12T20:22:15", "url": "https://files.pythonhosted.org/packages/0d/b6/0f8a37986a0cddff8e33bb871040dbaab1f21797abf8ad85f250c57c4c6c/cbox-0.5.0-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "23249ac2a0179b8e2f2573ec140e177c", "sha256": "bd98ba08a7b2807c6b657b4deda64e0f652977049f01b7598e9e92588a600016" }, "downloads": -1, "filename": "cbox-0.5.0.tar.gz", "has_sig": true, "md5_digest": "23249ac2a0179b8e2f2573ec140e177c", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 12140, "upload_time": "2018-03-12T20:22:07", "url": "https://files.pythonhosted.org/packages/96/47/13a3715ef774cb7366ce1d87ed74dd6d1b74bcd96c8a7c85d7ae4a456246/cbox-0.5.0.tar.gz" } ] }