Skip to content

Latest commit

 

History

History
219 lines (123 loc) · 10.5 KB

File metadata and controls

219 lines (123 loc) · 10.5 KB

Marrow Scripting

Enabling simplified command line script creation.

© 2010-2011, Alice Bevan-McGregor

https://github.com/marrow/marrow.script

1. What is Marrow Scripting?

The marrow.script package is a small library for turning average every-day callables (such as functions and class methods) into command-line scripts while automatically determining argument naming, typecasting, and generating things like help and version information. All behavior can be overridden by you, the developer, giving you a flexible and easy to develop with command line parsing library to replace optparse and argparse. This package is not a wrapper around existing parsing libraries, and attempts to match the syntax common to GNU software.

In a larger scope marrow.script aims to replace other high-level command-line scripting libraries such as Paste Script and Commandline while also implementing Python 3 compatibility.

2. Installation

Installing marrow.script is easy, just execute the following in a terminal:

pip install marrow.script

If you add marrow.script to the install_requires argument of the call to setup() in your application’s setup.py file, marrow.script will be automatically installed and made available when your own application is installed.

2.1. Development Version

Development takes place on GitHub in the marrow.script project. Issue tracking, documentation, and downloads are provided there.

Installing the current development version requires Git, a distributes source code management system. If you have Git, you can run the following to download and link the development version into your Python runtime:

git clone https://github.com/marrow/marrow.script.git
(cd marrow.script; python setup.py develop)

You can upgrade to the latest version at any time:

(cd marrow.script; git pull; python setup.py develop)

If you would like to make changes and contribute them back to the project, fork the GitHub project, make your changes, and submit a pull request. This process is beyond the scope of this documentation; for more information, see GitHub’s documentation.

3. Basic Usage

You can pass any callable object (function, method, class, or class instance with a __call__ method). For example, the following function illustrates a script that takes no arguments:

def hello():
    print "Hello world!"

To tell marrow.script to execute this function when run, add the following to the module containing the hello function:

if __name__ == '__main__':
    from marrow.script import execute
    execute(hello)

3.1. Script Meta-Data

Included are several decorators which help add metadata to your callable, describing the callable itself or adding additional detail to the argument list. First an example, then we’ll describe its parts in more detail:

from marrow.script import script

@script(
        title='ls clone',
        version="1.0",
        copyright="Copyright 2010 Alice Bevan-McGregor"
    )
def catalog():
    pass

Currently the script decorator accepts the following keyword arguments:

title Shown in the help and version text in parentheses after the script filename.
version Shown when the script is called with -V or --version.
copyright Displayed on its own line after the version information.
min The minimum number of positional arguments, used if the callable accepts *args.
max The maximum number of positional arguments, used if the callable accepts *args.

These values are stored in a dictionary attribute, _cmd_script, of the callable. The decorator does not modify code execution flow, merely returning the original callable after adding that property.

3.2. Help and Version Text

Marrow Script automatically generates help text for your callable based on the Python module’s name, the callable’s docstring (if present), and argument list. It automatically adds a --help/-h switch and handles its presence in the command line automatically. Additionally, if the command line fails to describe the required fields (or describes fields that the callable can’t handle) the help text is displayed. The help text for our hello example, above, would be displayed thusly:

Usage: hello.py [OPTIONS] 

OPTIONS may be one or more of:

 --help, -h     Display this help and exit.
 --version, -V  Show version and copyright information, then exit.

You can override the help text generator (which must be a callable accepting two arguments: the callable and callable specification) by utilizing the Parser class directly:

import sys
from marrow.script import Parser

def hello():
    print "Hello world!"

def myhelpfunc(v, spec):
    print "Custom help text."

sys.exit(Parser(hello, help=myhelpfunc)(sys.argv[1:]))

Executing this script with -h or --help will display:

Custom help text.

Similarly, you can override the version text generator by passing version=callable to the Parser class. The version text callable has the same argument list as the help text callable.

3.3. Exit Status Codes

The value returned by the callable should be None (exit code of zero) or a number. This number is passed through to sys.exit(). An example of capturing errors gracefully:

def graceful():
    try:
        pass # do some work
    
    except:
        return 1

3.4. Testing

By utilizing the Parser class directly, as in the custom parser example above, you can pass a custom list of arguments and capture the return code. This can aid in unit testing of your code, for example:

from marrow.script import Parser

def hello():
    print "Hello world!"

assert Parser(hello)([]) == 0 # Ensure the return code is zero.

4. Argument Handling

Marrow Script introspects your callable to determine the argument list. Not only does it examine the names of the arguments, but it also understands the following in the argument list:

required Required positional arguments.
value=None Explicit values, these are converted to strings.
name="world"
switch=False Explicit boolean switches. If matched on the command-line, the switch is flipped to its opposite value.
age=18 Explicit numeric values are converted to numbers automatically.
*args Unlimited positional arguments.
**kwargs A rare use-case, your callable can accept an unlimited number of name=value pairs from the command line.

4.1. Descriptive Decorators

Marrow Script comes with a number of decorators which expand upon the knowledge that can be gained via introspection. These are:

annotate Explicitly defines the typecasting for each argument.12
describe Overrides the default help text for each argument.2
short Defines a mapping of single-character to full variable name.
callbacks Defines callbacks executed by passing the command-line value (after typecasting) and argument specification. Returns an alternate value to store.

1 Typecasting is performed by any callable that accepts a string value as input; the built-in types str, unicode, int, etc. all support this. Additionally, to facilitate easier conversion of booleans and lists you can use marrow.util conversion routines like boolean and array.

2 Arguments to these decorators are name=value pairs whereby the name is the name of the argument to your callable.

5. Advanced Usage

You can pass a class as the callable to execute. Arguments to __init__ are processed first, then the first unmatched non-switch argument is used as the method to call. Arguments ignored by __init__ are then processed using the method’s call spec. For example, consider the following class definition:

class Service(object):
    def __init__(self, verbose=False):
        pass # configure logging based on `verbose`
    
    def start(self):
        pass # start something
    
    def stop(self, force=False):
        pass # stop doing things
    
    def restart(self):
        self.stop()
        self.start()

You can call the script using a number of different forms:

  • ./myscript.py or ./myscript.py --help — display the help text for the class’s init and a list of viable sub-commands.
  • ./myscript.py [start|stop|restart] --help — display help for the sub-command.
  • ./myscript.py start --verbose — Arguments don’t have a hard limit on the order.
  • ./myscript.py --verbose stop --force

6. Marrow Script License

Marrow Script has been released under the MIT Open Source license.

6.1. The MIT License

Copyright © 2010-2011 Alice Bevan-McGregor and contributors.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.