root/livinglogic.python.xist/src/ll/scripts/uls.py @ 4437:6f3d4e845072

Revision 4437:6f3d4e845072, 10.6 KB (checked in by Walter Doerwald <walter@…>, 8 years ago)

Fix typos in script documentation. Add examples. Bump version number.

Line 
1#!/usr/local/bin/python
2# -*- coding: utf-8 -*-
3
4
5## Copyright 2009-2011 by LivingLogic AG, Bayreuth/Germany.
6## Copyright 2009-2011 by Walter Dörwald
7##
8## All Rights Reserved
9##
10## See ll/__init__.py for the license
11
12
13"""
14Purpose
15-------
16
17``uls`` is a script that lists the content of directories. It is an URL-enabled
18version of the ``ls`` system command. Via :mod:`ll.url` and :mod:`ll.orasql`
19``uls`` supports ``ssh`` and ``oracle`` URLs too.
20
21
22Options
23-------
24
25``uls`` supports the following options:
26
27    ``urls``
28        Zero or more URLs. If no URL is given the current directory is listed.
29
30    ``-c``, ``--color`` : ``yes``, ``no`` or ``auto``
31        Should the ouput be colored. If ``auto`` is specified (the default) then
32        the output is colored if stdout is a terminal.
33
34    ``-1``, ``--one`` : ``false``, ``no``, ``0``, ``true``, ``yes`` or ``1``
35        Force output to be one URL per line. The default is to output URLs in
36        multiple columns (as many as fit on the screen).
37
38    ``-l``, ``--long`` : ``false``, ``no``, ``0``, ``true``, ``yes`` or ``1``
39        Ouput in long format: One URL per line containing the following information:
40        file mode, owner name, group name, number of bytes in the file,
41        number of links, URL.
42
43    ``-s``, ``--human-readable-sizes`` : ``false``, ``no``, ``0``, ``true``, ``yes`` or ``1``
44        Output the file size in human readable form (e.g. ``42M`` for 42 megabytes).
45
46    ``-r``, ``--recursive`` : ``false``, ``no``, ``0``, ``true``, ``yes`` or ``1``
47        List directories recursively.
48
49    ``-w``, ``--spacing`` : integer
50        The number of spaces (or padding characters) between columns (only
51        relevant for multicolumn ouput, i.e. when neither ``--long`` nor
52        ``--one`` is specified).
53
54    ``-P``, ``--padding`` : characters
55        The characters used for padding output in multicolumn or long format.
56
57    ``-S``, ``--separator`` : characters
58        The characters used for separating columns in long format.
59
60    ``-i``, ``--include`` : regular expression
61        Only URLs matching this regular expression will be output.
62
63    ``-e``, ``--exclude`` : regular expression
64        URLs matching this regular expression will be not be output.
65
66    ``-a``, ``--all`` :  ``false``, ``no``, ``0``, ``true``, ``yes`` or ``1``
67        Output dot files (i.e. files and directories whose name starts with a
68        ``.``). Note that the content of directories whose name start with a dot
69        will still be listed.
70
71
72Examples
73--------
74
75List the current directory::
76
77    $ uls
78    CREDITS.rst   installer.bmp   NEWS.rst           scripts/    test/
79    demos/        Makefile        OLDMIGRATION.rst   setup.cfg
80    docs/         MANIFEST.in     OLDNEWS.rst        setup.py
81    INSTALL.rst   MIGRATION.rst   README.rst         src/
82
83List the current directory in long format with human readable file sizes::
84
85    $ uls -s -l
86    rw-r--r--  walter    staff      1114    1  2008-01-06 22:27:15  CREDITS.rst
87    rwxr-xr-x  walter    staff       170    5  2007-12-03 23:35:33  demos/
88    rwxr-xr-x  walter    staff       340   10  2010-12-08 16:48:53  docs/
89    rw-r--r--  walter    staff        2K    1  2010-12-08 16:48:53  INSTALL.rst
90    rw-r--r--  walter    staff       35K    1  2007-12-03 23:35:33  installer.bmp
91    rw-r--r--  walter    staff      1763    1  2011-01-21 17:22:32  Makefile
92    rw-r--r--  walter    staff       346    1  2011-02-25 11:13:18  MANIFEST.in
93    rw-r--r--  walter    staff       34K    1  2011-03-04 13:48:35  MIGRATION.rst
94    rw-r--r--  walter    staff      107K    1  2011-03-04 18:18:42  NEWS.rst
95    rw-r--r--  walter    staff        8K    1  2010-12-08 16:48:53  OLDMIGRATION.rst
96    rw-r--r--  walter    staff       75K    1  2010-12-08 16:48:53  OLDNEWS.rst
97    rw-r--r--  walter    staff        3K    1  2010-12-08 16:48:53  README.rst
98    rwxr-xr-x  walter    staff       578   17  2010-12-08 16:48:53  scripts/
99    rw-r--r--  walter    staff        39    1  2010-12-08 16:48:53  setup.cfg
100    rw-r--r--  walter    staff        7K    1  2011-03-03 13:33:21  setup.py
101    rwxr-xr-x  walter    staff       136    4  2007-12-04 01:43:13  src/
102    rwxr-xr-x  walter    staff        2K   68  2011-03-03 13:27:46  test/
103
104Recursively list a remote directory::
105
106    uls ssh://user@www.example.org/~/dir/ -r
107    ...
108
109Recursively list the schema objects in an Oracle database::
110
111    uls oracle://user:pwd@oracle.example.org/ -r
112    ...
113"""
114
115
116import sys, re, argparse, contextlib, datetime, pwd, grp, stat, curses
117
118from ll import misc, url
119
120try:
121    import astyle
122except ImportError:
123    from ll import astyle
124
125try:
126    from ll import orasql # Activate oracle URLs
127except ImportError:
128    pass
129
130
131__docformat__ = "reStructuredText"
132
133
134style_file = astyle.Style.fromstr("white:black")
135style_dir = astyle.Style.fromstr("yellow:black")
136style_pad = astyle.Style.fromstr("black:black:bold")
137style_sizeunit = astyle.Style.fromstr("cyan:black")
138
139
140def encodedstring(s):
141    return s.decode(sys.stdin.encoding)
142
143
144def main(args=None):
145    uids = {}
146    gids = {}
147    modedata = (
148        (stat.S_IRUSR, "-r"),
149        (stat.S_IWUSR, "-w"),
150        (stat.S_IXUSR, "-x"),
151        (stat.S_IRGRP, "-r"),
152        (stat.S_IWGRP, "-w"),
153        (stat.S_IXGRP, "-x"),
154        (stat.S_IROTH, "-r"),
155        (stat.S_IWOTH, "-w"),
156        (stat.S_IXOTH, "-x"),
157    )
158    curses.setupterm()
159    width = curses.tigetnum('cols')
160
161    def rpad(s, l):
162        meas = str(s)
163        if not isinstance(s, (basestring, astyle.Text)):
164            s = str(s)
165        if len(meas) < l:
166            size = l-len(meas)
167            psize = len(args.padding)
168            repeats = (size+psize-1)//psize
169            padding = (args.padding*repeats)[-size:]
170            return astyle.style_default(s, style_pad(padding))
171        return s
172
173    def lpad(s, l):
174        meas = str(s)
175        if not isinstance(s, (basestring, astyle.Text)):
176            s = str(s)
177        if len(meas) < l:
178            size = l-len(meas)
179            psize = len(args.padding)
180            repeats = (size+psize-1)//psize
181            padding = (args.padding*repeats)[:size]
182            return astyle.style_default(style_pad(padding), s)
183        return s
184
185    def match(url):
186        strurl = str(url)
187        if args.include is not None and args.include.search(strurl) is None:
188            return False
189        if args.exclude is not None and args.exclude.search(strurl) is not None:
190            return False
191        if not args.all:
192            if url.file:
193                name = url.file
194            elif len(url.path) >=2:
195                name = url.path[-2]
196            else:
197                name = ""
198            if name.startswith("."):
199                return False
200        return True
201
202    def findcolcount(urls):
203        def width4cols(numcols):
204            cols = [0]*numcols
205            rows = (len(urls)+numcols-1)//numcols
206            for (i, (u, su)) in enumerate(urls):
207                cols[i//rows] = max(cols[i//rows], len(su))
208            return (sum(cols) + (numcols-1)*args.spacing, rows, cols)
209
210        numcols = len(urls)
211        if numcols:
212            while True:
213                (s, rows, cols) = width4cols(numcols)
214                if s <= width or numcols == 1:
215                    return (rows, cols)
216                numcols -= 1
217        else:
218            return (0, 0)
219
220    def printone(url):
221        if args.long:
222            sep = style_pad(args.separator)
223            stat = url.stat()
224            owner = url.owner()
225            group = url.group()
226            mtime = datetime.datetime.fromtimestamp(stat.st_mtime).strftime("%Y-%m-%d %H:%M:%S")
227            mode = "".join([text[bool(stat.st_mode&bit)] for (bit, text) in modedata])
228            size = stat.st_size
229            if args.human:
230                s = "BKMGTP"
231                for c in s:
232                    if size < 2048:
233                        if c == "B":
234                            size = str(int(size))
235                        else:
236                            size = astyle.style_default(str(int(size)), style_sizeunit(c))
237                        break
238                    size /= 1024.
239            stdout.write(mode, sep, rpad(owner, 8), sep, rpad(group, 8), sep, lpad(size, 5 if args.human else 12), sep, lpad(stat.st_nlink, 3), sep, mtime, sep)
240        if url.isdir():
241            stdout.writeln(style_dir(str(url)))
242        else:
243            stdout.writeln(style_file(str(url)))
244
245    def printblock(url, urls):
246        if url is not None:
247            stdout.writeln(style_dir(str(url)), ":")
248        (rows, cols) = findcolcount(urls)
249        for i in xrange(rows):
250            for (j, w) in enumerate(cols):
251                index = i+j*rows
252                try:
253                    (u, su) = urls[index]
254                except IndexError:
255                    pass
256                else:
257                    if u.isdir():
258                        su = style_dir(su)
259                    else:
260                        su = style_file(su)
261                    if index + rows < len(urls):
262                        su = rpad(su, w+args.spacing)
263                    stdout.write(su)
264            stdout.writeln()
265
266    def printall(base, url):
267        if url.isdir():
268            if url.path.segments[-1]:
269                url.path.segments.append("")
270            if not args.long and not args.one:
271                if args.recursive:
272                    urls = [(url/child, str(child)) for child in url.files() if match(url/child)]
273                    if urls:
274                        printblock(url, urls)
275                    for child in url.dirs():
276                        printall(base, url/child)
277                else:
278                    urls = [(url/child, str(child)) for child in url.listdir() if match(url/child)]
279                    printblock(None, urls)
280            else:
281                for child in url.listdir():
282                    child = url/child
283                    if match(child):
284                        if not args.recursive or child.isdir(): # For files the print call is done by the recursive call to ``printall``
285                            printone(child)
286                    if args.recursive:
287                        printall(base, child)
288        else:
289            if match(url):
290                printone(url)
291
292    p = argparse.ArgumentParser(description="List the content of one or more URLs")
293    p.add_argument("urls", metavar="url", help="URLs to be listed (default: current dir)", nargs="*", default=[url.Dir("./", scheme=None)], type=url.URL)
294    p.add_argument("-c", "--color", dest="color", help="Color output (default: %(default)s)", default="auto", choices=("yes", "no", "auto"))
295    p.add_argument("-1", "--one", dest="one", help="One entry per line? (default: %(default)s)", action=misc.FlagAction, default=False)
296    p.add_argument("-l", "--long", dest="long", help="Long format? (default: %(default)s)", action=misc.FlagAction, default=False)
297    p.add_argument("-s", "--human-readable-sizes", dest="human", help="Human readable file sizes? (default: %(default)s)", action=misc.FlagAction, default=False)
298    p.add_argument("-r", "--recursive", dest="recursive", help="Recursive listing? (default: %(default)s)", action=misc.FlagAction, default=False)
299    p.add_argument("-w", "--spacing", dest="spacing", metavar="INTEGER", help="Space between columns (default: %(default)s)", type=int, default=3)
300    p.add_argument("-P", "--padding", dest="padding", metavar="CHARS", help="Characters used for column padding (default: %(default)s)", default=u" ", type=encodedstring)
301    p.add_argument("-S", "--separator", dest="separator", metavar="CHARS", help="Characters used for separating columns in long format (default: %(default)s)", default=u"  ", type=encodedstring)
302    p.add_argument("-i", "--include", dest="include", metavar="PATTERN", help="Include only URLs matching PATTERN (default: %(default)s)", type=re.compile)
303    p.add_argument("-e", "--exclude", dest="exclude", metavar="PATTERN", help="Exclude URLs matching PATTERN (default: %(default)s)", type=re.compile)
304    p.add_argument("-a", "--all", dest="all", help="Include dot files? (default: %(default)s)", action=misc.FlagAction, default=False)
305
306    args = p.parse_args(args)
307
308    if args.color == "yes":
309        color = True
310    elif args.color == "no":
311        color = False
312    else:
313        color = None
314    stdout = astyle.Stream(sys.stdout, color)
315    stderr = astyle.Stream(sys.stderr, color)
316
317    with url.Context():
318        for u in args.urls:
319            printall(u, u)
320
321
322if __name__ == "__main__":
323    sys.exit(main())
Note: See TracBrowser for help on using the browser.