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