Package cliutils
[hide private]
[frames] | no frames]

Source Code for Package cliutils

  1  r""" 
  2  A collection of utilities easing the creation of command line scripts. 
  3   
  4  cliutils is pure Python with no dependencies. 
  5   
  6  The package provides several features that may aid the simple CLI 
  7  utility-writer. Process objects give a simple way to get output from shell 
  8  commands; the C{persistence} module provides easy access to .ini-style 
  9  configuration files; a collection of decorators eases some common patterns. 
 10   
 11  Process objects 
 12  =============== 
 13      Although it isn't very difficult to execute shell commands from a Python 
 14      script, there are several lines of overhead in the standard pattern. 
 15      Process objects reduce the entire pattern to a single line. In addition, 
 16      they are more flexible; they may be piped into each other, just as regular 
 17      processes may be on the bash command line. 
 18   
 19          >>> Process("echo 'spam and eggs'") 
 20          spam and eggs 
 21          >>> s = Process("echo 'spam and eggs'").stdout 
 22          >>> s 
 23          'spam and eggs' 
 24          >>> p = Process("echo 'spam and eggs'") | Process("wc -w") 
 25          >>> p.stdout 
 26          '3' 
 27   
 28      For convenience, a singleton object (L{sh}) is provided that is able to 
 29      create process objects from given attributes. 
 30   
 31          >>> sh.echo("spam and eggs") | sh.wc("-w") | sh.cat() 
 32          3 
 33   
 34      Arguments passed to Process objects are split using the C{shlex} module, so 
 35      most simple strings will work just fine. More complex arguments should be 
 36      passed in as lists: 
 37   
 38          >>> sh.echo(["spam", "and", "eggs"]) 
 39          spam and eggs 
 40   
 41   
 42  Persistence 
 43  =========== 
 44      There's a bit of overhead involved in finding a writable directory suitable 
 45      for storing a config file or a persistent settings hash. The L{storage_dir} 
 46      function removes that overhead. It accepts an optional directory name; if 
 47      it represents an absolute path, it will be treated as such. Otherwise, it 
 48      will be treated as a path relative to a writeable directory. On Windows, 
 49      that directory wil be the roaming profile Application Data directory; on 
 50      *nix, it will be the current user's home directory. If the resulting path 
 51      doesn't exist, it will be created. 
 52   
 53      For example, finding a path to store a persistent configuration file is as 
 54      easy as: C{f = storage_dir('.myscript.cfg')}. Of course, it's easier still 
 55      with the L{config} function (explained below). 
 56   
 57      The L{config} function loads or creates a .ini-style config file at a given 
 58      directory, using ConfigParser; however, it is an improvement in two 
 59      respects. First, it is passed through L{storage_dir}, so locating the 
 60      config file is easier.  Second, it returns an instance of the 
 61      L{persistence.ConfigStorage} class, which wraps a ConfigParser instance to 
 62      provide a dictionary-like interface. Sections and options may be accessed 
 63      like nested dictionaries. In addition, the file is automatically saved when 
 64      values are set. 
 65   
 66          >>> import tempfile; filename = tempfile.mkstemp()[1] 
 67          >>> cfg = config(filename) 
 68          >>> cfg['sec1']['option2'] = 75 
 69          >>> cfg['sec2']['option1'] = "Some String" 
 70          >>> cfg['sec2']['option2'] = "Another value" 
 71          >>> f = file(filename) 
 72          >>> print f.read() 
 73          [sec1] 
 74          option2 = 75 
 75          <BLANKLINE> 
 76          [sec2] 
 77          option2 = Another value 
 78          option1 = Some String 
 79          <BLANKLINE> 
 80          <BLANKLINE> 
 81   
 82      Finally, the L{db} function returns a persistent dictionary, again run 
 83      through L{storage_dir} to make file creation and access simple. It uses 
 84      the C{shelve} module to create or load a pickled dictionary from a given 
 85      filename. When the dictionary is modified, the pickle is saved. This allows 
 86      for a simple, flexible database when it's unimportant that the user be able 
 87      to modify directly the data stored therein. For all intents and purposes, 
 88      it may be treated as a regular dictionary in the code. 
 89   
 90          >>> import tempfile; filename = tempfile.mkstemp()[1] 
 91          >>> cfg = db('.test-ignore') 
 92          >>> cfg['option1'] = 10L 
 93          >>> cfg['option2'] = [1, 2, 3, 4, (5, 6)] 
 94          >>> print cfg 
 95          {'option2': [1, 2, 3, 4, (5, 6)], 'option1': 10L} 
 96   
 97      See the C{shelve} documentation for more details. The only thing added by 
 98      this package is the mutation of the file path by L{storage_dir}. 
 99   
100  Decorators 
101  ========== 
102   
103  The L{cliargs} decorator 
104  ------------------------ 
105      A common pattern for shell scripts is:: 
106   
107          def main(): 
108              parser = make_an_option_parser() 
109              parser.parse(sys.argv[1:]) 
110              do_some_stuff_with_options() 
111   
112          if __name__=="__main__": 
113              main() 
114   
115      Creation of shell scripts using C{setuptools}' C{entry_points} results in a 
116      similar pattern; a function is called with no arguments, and must do its 
117      own command-line argument parsing. This makes sense in some cases, where 
118      complex argument parsing is required. In simple cases, however, where 
119      parsing of a few arguments or keywords is required, the L{cliargs} 
120      decorator will be of use. It does a simple parse of C{sys.argv}, using a 
121      parsing algorithm based on some code in C{getopt}, and calls the decorated 
122      function with the results:: 
123   
124          @cliargs 
125          def myScript(anarg, anotherarg, someval="default") 
126              "Usage: myscript anarg anotherarg [--someval VALUE]" 
127              print anarg anotherarg someval 
128   
129      When that function is called as a result of a command line script, such 
130      as:: 
131   
132          $ myscript val1 val2 --someflag somevalue  
133   
134      L{cliargs} will parse C{sys.argv} and pass the results into myScript. If 
135      improper arguments are passed such that a C{TypeError} is raised, the 
136      docstring of the function will be printed; this makes that an ideal place 
137      to include a usage string. 
138   
139      L{cliargs} is of course limited to very simple cases. More complex argument 
140      parsing will require the use of the C{getopt} or C{optparse} modules. 
141   
142  L{redirect} 
143  ----------- 
144      L{redirect} is an almost trivially simple decorator factory. When 
145      called with a file-like object, it returns a decorator that redirects 
146      C{sys.stdout} to that file for the duration of the execution of the 
147      decorated function. 
148   
149          >>> from StringIO import StringIO 
150          >>> logfile = StringIO() 
151          >>> logger = redirect(logfile) 
152          >>> @logger 
153          ... def func(): 
154          ...     print "ABCDEFGHIJK" 
155          ...  
156          >>> func() 
157          >>> logfile.seek(0) 
158          >>> logfile.read().strip() 
159          'ABCDEFGHIJK' 
160   
161  L{indir} 
162  -------- 
163      L{indir} is a decorator factory that runs the decorated function in a given 
164      directory, changing back to the original directory on completion. 
165       
166          >>> import os 
167          >>> d = os.path.realpath('/etc') 
168          >>> curdir = os.path.realpath(os.curdir) 
169          >>> @indir(d) 
170          ... def whereami(): 
171          ...     return os.path.realpath(os.curdir) 
172          ... 
173          >>> whereami() == d 
174          True 
175          >>> os.path.realpath(os.curdir) == curdir 
176          True 
177   
178  """ 
179  __version__="0.1.3" 
180  __all__=["sh", "Process", "cliargs", "redirect_decorator", "redirect", "indir", 
181           "db", "config"] 
182   
183  from process import sh, Process 
184  from decorators import cliargs, logged, log_decorator, redirect, indir 
185  from persistence import * 
186