JythonBook / chapter8.rst

Josh Juneau d9b2bb8 














































































































































































































































Chapter 8:  Scripting With Jython
+++++++++++++++++++++++++++++++++

In this chapter we will look at scripting with jython.  For our purposes, I
will define "scripting" as the writing of small programs to help out with daily
tasks.  These tasks are things like deleting and creating directories,
mananging files and programs, or anything else that feels repetitive that you
might be able to express as a small program.  In practice however, scripts can
become so large that the line between a script and a full sized program can
blur.

We'll start with an overview of some of the most helpful modules that come with
jython for these tasks. These modules are os, shutil, getopt, optparse,
subprocess. We will just be giving you a quick feel for these modules.  For
details you should look at reference documentation like
http://jyton.org/currentdocs.  Then we'll cover a medium sized task to show the
use of a few of these modules together.

Parsing Commandline Options
===========================
Many scripts are simple one-offs that you write once, use, and forget.  Others
become part of your weekly or even daily use over time.  When you find that you
are using a script over and over again, you often find it helpful to pass in
command line options.  There are three main ways that this is done in jython.
The first way is to hand parse the arguments, the second is the getopt module,
and the third is the newer, more flexible optparse module.

Let's say we have a script called foo.py and you want to be able to give it
some parameters when you invoke it the name of the script and the arguments
passed can be examined by importing the sys module and inspecting sys.argv like
so:

    # script foo.py
    import sys
    print sys.argv

If you run the above script with a, b, and c as arguments: ::

    $ jython foo.py a b c
    $ ['foo.py', 'a', 'b', 'c']

The name of the script ended up in sys.argv[0], and the rest in sys.argv[1:].  Often you will see this instead in jython programs:

    # script foo2.py
    import sys
    
    args = sys.argv[1:]
    print args

which will result in: ::

    $ jython foo2.py a b c
    $ ['a', 'b', 'c']

If you are going to do more than just feed the arguments to your script
directly, than parsing these arguments by hand can get pretty tedious.  The
jython libraries include two modules that you can use to avoid tedius hand
parsing.  Those modules are getopt and optparse.  The optparse module is the
newer, more flexible option, so I'll cover that one.  The getopt module is
still useful since it requires a little less code for simpler expected
arguments.  Here is a basic optparse script: ::

    # script foo3.py
    from optparse import optionparser
    parser = optionparser()
    parser.add_option("-f", "--foo" help="set foo option")
    parser.add_option("-b", "--bar" help="set bar option")
    (options, args) = parser.parse_args()
    print "options: %s" % options
    print "args: %s" % args

running the above: ::

    $ jython foo3.py -b a --foo b c d
    $ options: {'foo': 'b', 'bar': 'a'}
    $ args: ['c', 'd']

I'll come back to the optparse module with a more concrete example later in
this chapter.

Scripting The Filesystem
========================
We'll start with what is probably the simplest thing that you can do to a
filesystem, and that is listing the file contents of a directory. ::

    >>> import os
    >>> os.listdir('.')
    ['ast', 'doc', 'grammar', 'lib', 'license.txt', 'news', 'notice.txt', 'src']

First we imported the os module, and then we executed listdir on the current
directory, indicated by the '.'.  Of course your output will reflect the
contents of your local directory.  The os module contains many of the sorts of
functions that you would expect to see for working with your operating system.
The os.path module contains functions that help in working with filesystem
paths.

Compiling Java Source
=====================

While compiling java source is not strictly a typical scripting task, it is a
task that I'd like to show off in my bigger example starting in the next
section.  The api I am about to cover was introduced in jdk 6, and is optional
for jvm vendors to implement.  I know that it works on the jdk 6 from sun and
on the jdk 6 that ships with mac os x.  For more details of the javacompiler
api, a good starting point is here: http://java.sun.com/javase/6/docs/api/javax/tools/javacompiler.html.  The following is a simple example of the use of this api from jython ::

    compiler = toolprovider.getsystemjavacompiler()
    diagnostics = diagnosticcollector()
    manager = compiler.getstandardfilemanager(diagnostics, none, none)
    units = manager.getjavafileobjectsfromstrings(names)
    comp_task = compiler.gettask(none, manager, diagnostics, none, none, units)
    success = comp_task.call()
    manager.close()

Example Script: builder.py
==========================

So I've discussed a few of the modules that tend to come in handy when writing
scripts for jython.  Now I'll put together a simple script to show off what can
be done.  I've chosen to write a script that will help handle the compilation
of java files to .class files in a directory, and clean the directory of .class
files as a separate task.  I will want to be able to create a
directory structure, delete the directory structure for a clean build, and of
course compile my java source files. ::

    import os
    import sys
    import glob

    from javax.tools import (forwardingjavafilemanager, toolprovider,
            diagnosticcollector,)

    tasks = {}

    def task(func):
        tasks[func.func_name] = func

    @task
    def clean():
        files = glob.glob("*.class")
        for file in files:
            os.unlink(file)

    @task
    def compile():
        files = glob.glob("*.java")
        _log("compiling %s" % files)
        if not _compile(files):
            quit()
        _log("compiled")

    def _log(message):
        if options.verbose:
            print message

    def _compile(names):
        compiler = toolprovider.getsystemjavacompiler()
        diagnostics = diagnosticcollector()
        manager = compiler.getstandardfilemanager(diagnostics, none, none)
        units = manager.getjavafileobjectsfromstrings(names)
        comp_task = compiler.gettask(none, manager, diagnostics, none, none, units)
        success = comp_task.call()
        manager.close()
        return success
     
    if __name__ == '__main__':
        from optparse import optionparser
        parser = optionparser()
        parser.add_option("-q", "--quiet", 
                action="store_false", dest="verbose", default=true,
                help="don't print out task messages.")
        parser.add_option("-p", "--projecthelp", 
                action="store_true", dest="projecthelp",
                help="print out list of tasks.")
        (options, args) = parser.parse_args()
        
        if options.projecthelp:
            for task in tasks:
                print task
            sys.exit(0)

        if len(args) < 1:
            print "usage: jython builder.py [options] task"
            sys.exit(1)
        try:
            current = tasks[args[0]]
        except keyerror:
            print "task %s not defined." % args[0]
            sys.exit(1)
        current()

The script defines a "task" decorator that gathers the names of the functions
and puts them in a dictionary.  We have an optionparser class that defines two
options --projecthelp and --quiet.  By default the script logs its actions to
standard out.  --quiet turns this logging off.  --projecthelp lists the
available tasks.  We have defined two tasks, "compile" and "clean".  The
"compile" task globs for all of the .java files in your directory and compiles
them.  The "clean" task globs for all of the .class files in your directory and
deletes them.  Do be careful!  The .class files are deleted without prompting!

So lets give it a try.  If you create a Java class in the same directory as
builer.py, say the classic "Hello World" program:

HelloWorld.java
===============
::

    public class HelloWorld {
       public static void main(String[] args) {
           System.out.println("Hello, World");
       }
    }

You could then issue these commands to builder.py with these results: ::

      [frank@pacman chapter8]$ jython builder.py --help
      Usage: builder.py [options]

      Options:
        -h, --help         show this help message and exit
        -q, --quiet        Don't print out task messages.
        -p, --projecthelp  Print out list of tasks.
      [frank@pacman chapter8]$ jython builder.py --projecthelp
      compile
      clean
      [frank@pacman chapter8]$ jython builder.py compile
      compiling ['HelloWorld.java']
      compiled
      [frank@pacman chapter8]$ ls
      DEBUG.classicHelloWorld.java
      HelloWorld.classicHelloWorldbuilder.py
      [frank@pacman chapter8]$ jython builder.py clean
      [frank@pacman chapter8]$ ls
      HelloWorld.javabuilder.py
      [frank@pacman chapter8]$ jython builder.py --quiet compile
      [frank@pacman chapter8]$ ls
      DEBUG.classicHelloWorldHelloWorld.java
      HelloWorld.classicHelloWorldHelloWorldbuilder.py
      [frank@pacman chapter8]$ 
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.