Package: zoro
====zoro====Before we introduce you to zoro, please do not confuse it with Zorro_ which isa networking library that has nothing to do with this one.The name 'zoro' comes from a Japanese onomatopoeic phrase 'zoro zoro' whichsignifies a sound a horde makes as it goes somewhere.Having to deal with a build process that involves both backend and frontendcode, transpiled languages like CoffeScript, LiveScript, LESS or Compass,exotic configuration files or settings that need to be swapped before goinginto production... The horde of tasks that need to be completed before aproject can be served to the masses can be hard to deal with without softwareto help us. On the other hand, learning a complex build system may seem like achore.Zoro tries to fill the gap between writing small (shell) scripts, and masteringa full build system like Buildout. I chose Python for this task, not onlybecause I develop Python software, but also because of its vast standardlibrary that can simplify many tasks without pulling in dozens of third-partylibraries. Zoro is also a simple and pure Python module, so you do not needanything other than the Python interpreter installed on your system. This makesit not only easy to install, but also portable across platforms.In fact, the ``zoro`` module itself does not hide any of the modules andfunctions it imports from the standard library, so you can ``from zoro import*`` to access them witout having to add many lines of imports. Further more,though its API, zoro tries to stay as close to bare Python as possible. Afterall, why invent a new language if there is already a good one (or ten)... contents::License=======Zoro is released under MIT license. See the source code for copyright andlicense.Installation============You can install zoro frim PyPI as usual::easy_install zoroor::pip install zoroBasic concept=============Somewhat similar to GNU Make, zoro allows you to easily define build targets,and run various commands within them. This is achieved through the use of``zoro.Make`` class. Let's take a look at a real-life example of such a classand discuss its usage. ::#!/usr/bin/env pythonfrom zoro import *class Targets(Make):"""Build my project."""def __init__(self):super(Targets, self).__init__()self._parser.add_options('-k', '--key', help='API key',dest='api_key')def dev(self):""" start test runners, servers and compilers """wait_for_interrupt(run(node_local('lsc -cwbo static/js src/ls')),run('compass watch -c tools/compass.rb'),run(python('app/main'), env={'APPENV': 'development'}),)def test(self, args, options):""" run unit tests """wait_for_interrupt(watch(python('tools/run_tests'), '.', '*.py'),)def build(self):""" prepares the project for deployment """self.clean()copytree('app', 'build/app')copytree('static', 'build/static')patch('build/app/conf.py', lambda s: self._patch_key(s))cleanup('build', '*.pyc')run(python('tools/cachebust build/app/templates'), wait=True)def _patch_key(self, s):key = self._options.k.api_keyif key is None:err('No API key specified', 1)return re.sub(r"API_KEY='[^']+'", "API_KEY='%s'" % key, s)if __name__ == '__main__':Targets()()This file is usually saved as 'zorofile' in the project's root directory.  Theshebang line at the top of the file allows us to run this file withoutexplicitly naming the interpreter (on Linux and UNIX systems at least). OnWindows we also include a 'zorofile.cmd' file to go with it. The contents ofthe file may look like this::@echo offpython zorofile %*Now we can start calling the zorofile directly.Importing zoro functions~~~~~~~~~~~~~~~~~~~~~~~~Normally, when using zoro, we import everything from ``zoro`` module with::from zoro import *This pulls in not only the functions and classes defined by zoro itself, butalso anything and evertyhing zoro itself imports. This includes (among otherthings, the ``os`` module, ``sys``, ``time``, ``platform``, ``shlex``,``datetime``, etc). For a full listing of what's imported, you should look atthe source code.Targets class~~~~~~~~~~~~~The next thing we notice is the ``Targets`` class. It's a subclass of the``zoro.Make`` class, and we use it to house all our build targets, as well asany utility methods we might need.The constructor~~~~~~~~~~~~~~~The constructor of the ``zoro.Make`` class builds a parser object (created by``optparse`` module). The parser is used to define and parse command linearguments passed to our zorofile. In our subclass, we augment the defaultparser with a new option '-k', which we will use to pass a production API keyduring the build process.Parsed positional arguments and options are stored as ``_args`` and``_options`` instance attributes respectively and can be access by all instancemethods.Targets and utilities~~~~~~~~~~~~~~~~~~~~~Let's cover the utility methods first. In our example, we have one utilitymethod which replaces the API key in our configuration module. The reason wemade it an instance method instead of a function defined outside the class isthat this way we have access to all properties on the class, including the``_options`` attribute mentioned in the previous section.The reason utility methods are prefixed with an underscore is that methodswithout a leading underscore will be treated as build targets.You will also note that we are using the ``re`` module without explicitlyimporting it. We can do that because it is already imported in the ``zoro``module.Apart from the constructor and the utility method, there are also three buildtargets: 'dev', 'test', and 'build'. All three targets are normal Pythonmethods. They have docstrings of which the first lines are used in help messagewhen the zorofile is run with the '-l' switch.The 'dev' target is used when we want to develop the application. Itfacilitates live compilation of LiveScript_ and Compass_ code and runs ourapplication's built-in development server. This is achieved by using the``zoro.run()`` function.The ``zoro.run()`` function executes commands asyncrhonously by default. Thismeans that the function itself returns before the command exits. This isconvenient because the commands in the 'dev' target will run indefinitely untilthey receive a keyboard interrupt.The first command is passed to ``zoro.node_local()`` function. This functionconstructs the correct path for the locally installed NodeJS_ dependencies. Theactual command to run is dependent on the platform we are on, and this functionalso takes care of ironing out the differences.Third command is a python script, so we are passing it to ``zoro.python()``function, which prepends 'python' and appends the '.py' extension. You willalso notice that the third command uses an ``env`` keyword argument to the``zoro.run()`` function. This allows us to override or add envionrmentvariables specifically for that command.All three commands in the 'dev' target are wrapped in``zoro.wait_for_interrupt()`` call. This function takes child process objectsor watchdog_ observers as positional arguments, and terminates them all whenthe zorofile receives a keyboard interrupt. Because ``zoro.run()`` returns achild process object for the command it executes, we can pass its return valuedirectly to ``zoro.wait_for_interrupt()``.The second target, 'test', looks very similar to the 'dev' target, but it runsits command using ``zoro.watch()`` instead of ``zoro.run()``. The``zoro.watch()`` function takes three arguments. The first one is the same as``zoro.run()``. The second argument is a path that should be monitored forchanges and the last argument is a glob pattern to use as a filter. Whenever afile or directory under the monitored path, matching the specified globpattern, is modified, the command is executed. This allows us to rerun ourtests whenever we modify a Python module.Finally, the 'build' target creates a 'build' directory and prepares the codefor deployment. It uses the ``shutil.copytree()`` function to copy thedirectories into the target directory, calls ``zoro.patch()`` to patch theconfiguration file with the help from the utility method, and uses``zoro.cleanup()`` to remove unneeded files.Running the targets~~~~~~~~~~~~~~~~~~~To run the targets, we need to call the instance of our ``Targets`` class. Thisis done in an ``if`` block so that it is only run when the zorofile is calleddirectly.API documentation=================There is no separate API documentation, but you will find the source code to bewell-documented. The code is less than 700 lines *with* inline documentation,so you should just dig in. You will find examples for each function in thedocstrings.Reporting bugs==============Please report any bugs to the project's `BitBucket bugtracker`_... _Zorro: https://pypi.python.org/pypi/Zorro.. _LiveScript: http://livescript.net/.. _Compass: http://compass-style.org/.. _watchdog: http://pythonhosted.org//watchdog/.. _BitBucket bugtracker: https://bitbucket.org/brankovukelic/zoro/issues