root/livinglogic.python.xist/src/ll/scripts/uls.py @ 4422:fe09ca906d4e

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

Bump copyright year. Change encoding of remaining files to UTF-8. Remove trailing whitespace.

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