https://github.com/crdoconnor/commandlib
Command runner with clean API.
https://github.com/crdoconnor/commandlib
Last synced: over 1 year ago
JSON representation
Command runner with clean API.
- Host: GitHub
- URL: https://github.com/crdoconnor/commandlib
- Owner: crdoconnor
- License: mit
- Created: 2016-01-31T12:54:19.000Z (over 10 years ago)
- Default Branch: master
- Last Pushed: 2023-09-17T09:47:14.000Z (almost 3 years ago)
- Last Synced: 2025-03-17T19:48:01.643Z (over 1 year ago)
- Language: Python
- Homepage: https://hitchdev.com/commandlib/
- Size: 110 KB
- Stars: 21
- Watchers: 5
- Forks: 2
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE.txt
Awesome Lists containing this project
README
# CommandLib
Commandlib is a dependencyless library for calling external UNIX commands
(e.g. in build scripts) in a clean, readable way.
Using method chaining, you can build up Command objects that run in a specific
directory, with specified [environment variables](https://hitchdev.com/commandlib/using/alpha/environment-variables)
and [PATHs](https://hitchdev.com/commandlib/using/alpha/add-directory-to-path), etc.
For simplicity's sake, the library itself only runs commands in a blocking
way (all commands run to completion before continuing), although it contains
hooks to run non-blocking via either [icommandlib](https://github.com/crdoconnor/icommandlib)
or [pexpect](https://pexpect.readthedocs.io/en/stable/).
Pretend 'django/manage.py':
```bash
# Pretend django "manage.py" that just prints out arguments:
import sys ; sys.stdout.write(' '.join(sys.argv[1:]))
```
```python
from commandlib import Command
# Create base command
python = Command("python")
# Create command "python manage.py" that runs in the django directory
manage = python("manage.py").in_dir("django")
# Build even more specific command
dev_manage = manage.with_trailing_args("--settings", "local_settings.py")
```
```python
# Run combined command
dev_manage("runserver", "8080").run()
```
Will output:
```
runserver 8080 --settings local_settings.py
```
## Install
```sh
$ pip install commandlib
```
## Docs
- [Easily invoke commands from the current virtualenv (python_bin)](https://hitchdev.com/commandlib/using/alpha/)
- [Run commmands interactively using icommandlib or pexpect](https://hitchdev.com/commandlib/using/alpha/)
- [Piping data out to string or file (.piped)](https://hitchdev.com/commandlib/using/alpha/)
- [Easily invoke commands from one directory (CommandPath)](https://hitchdev.com/commandlib/using/alpha/)
- [Add directory to PATH (with_path)](https://hitchdev.com/commandlib/using/alpha/)
- [Piping data in from string or file (.piped)](https://hitchdev.com/commandlib/using/alpha/)
- [Run command and don't raise exception on nonzero exit code (ignore_errors())](https://hitchdev.com/commandlib/using/alpha/)
- [Change your command's environment variables (with_env)](https://hitchdev.com/commandlib/using/alpha/)
- [Capture output (.output())](https://hitchdev.com/commandlib/using/alpha/)
## Why?
Commandlib avoids the tangle of messy code that you would
get using the subprocess library directly (Popen, call, check_output(), .communicate(), etc.)
and the [confusion that results](https://stackoverflow.com/questions/89228/calling-an-external-command-in-python).
It's a [heavily dogfooded](https://hitchdev.com/principles/extreme-dogfooding) library. For humans. Because who else?
## Is subprocess really that bad?
The code will likely be longer and messier. For example, from [stack overflow](https://stackoverflow.com/questions/2231227/python-subprocess-popen-with-a-modified-environment):
```python
import subprocess, os
previous_directory = os.getcwd()
os.chdir("command_directory")
my_env = os.environ.copy()
my_env["PATH"] = "/usr/sbin:/sbin:" + my_env["PATH"]
subprocess.Popen(my_command, env=my_env)
os.chdir(previous_directory)
```
Equivalent:
```python
from commandlib import Command
Command(my_command).with_path("/usr/sbin:/sbin:").in_dir("command_directory").run()
```
## Why not use Delegator instead (Kenneth Reitz's 'subprocesses for humans')?
Kenneth Reitz (author of requests "urllib2/3 for humans"), wrote a similarly inspired "subprocess for humans"
called [envoy](https://github.com/kennethreitz/envoy). That is now deprecated and there is now a replacement called [delegator](https://github.com/kennethreitz/delegator.py), which is a very thin
wrapper around subprocess.
Features delegator has which commandlib does not:
* Delegator can chain commands, much like bash does (delegator.chain('fortune | cowsay')). Commandlib doesn't do that because while dogfooding the library I never encountered a use case where I found this to be necessary. You can, however, easily get the output of one command using .output() as a string and feed it into another using piped.from_string(string).
* Delegator runs subprocesses in both a blocking and nonblocking way (using pexpect). commandlib only does blocking by itself but if you pip install pexpect or icommandlib it can run via either one of them.
* Runs on windows
Features which both have:
* Ability to set environment variables.
* Ability to run pexpect process from command object.
Features which only commandlib has:
* Ability to set PATH easily.
* Ability call code from within the current virtualenv easily.
* Ability to pipe in strings or files and easily pipe out to strings or file (or file handles).
* Hook to easily run commands in from the current virtualenv.
## Why not use other tools?
* os.system(*) - only capable of running very simple bash commands.
* [sh](https://amoffat.github.io/sh/) - uses a lot of magic. Attempts to make python more like shell rather than making running commands more pythonic.
* [plumbum](https://plumbum.readthedocs.io/en/latest/]) - similar to amoffat's sh, tries to make a sort of "bash inside python". Also has a weird way of building commands from dict syntax (grep["-v", "\\.py"]).