chiark / gitweb /
format.py: Allow general format controls more widely.
[chopwood] / agpl.py
1 ### -*-python-*-
2 ###
3 ### GNU Affero General Public License compliance
4 ###
5 ### (c) 2013 Mark Wooding
6 ###
7
8 ###----- Licensing notice ---------------------------------------------------
9 ###
10 ### This file is part of Chopwood: a password-changing service.
11 ###
12 ### Chopwood is free software; you can redistribute it and/or modify
13 ### it under the terms of the GNU Affero General Public License as
14 ### published by the Free Software Foundation; either version 3 of the
15 ### License, or (at your option) any later version.
16 ###
17 ### Chopwood is distributed in the hope that it will be useful,
18 ### but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 ### GNU Affero General Public License for more details.
21 ###
22 ### You should have received a copy of the GNU Affero General Public
23 ### License along with Chopwood; if not, see
24 ### <http://www.gnu.org/licenses/>.
25
26 import contextlib as CTX
27 import os as OS
28 import shlex as SL
29 import shutil as SH
30 import subprocess as SUB
31 import sys as SYS
32 import tarfile as TAR
33 import tempfile as TF
34
35 from auto import PACKAGE, VERSION
36 import util as U
37
38 @CTX.contextmanager
39 def tempdir():
40   d = TF.mkdtemp()
41   try: yield d
42   finally: SH.rmtree(d, ignore_errors = True)
43
44 def dirs_to_dump():
45   dirs = set()
46   for m in SYS.modules.itervalues():
47     try: f = m.__file__
48     except AttributeError: continue
49     d = OS.path.realpath(OS.path.dirname(f))
50     if d.startswith('/usr/') and not d.startswith('/usr/local/'): continue
51     dirs.add(d)
52   dirs = sorted(dirs)
53   last = '!'
54   dump = []
55   for d in dirs:
56     if d.startswith(last): continue
57     dump.append(d)
58     last = d
59   return dump
60
61 def exists_subdir(subdir):
62   return lambda dir: OS.path.isdir(OS.path.join(dir, subdir))
63
64 def filez(cmd):
65   def _(dir):
66     kid = SUB.Popen(SL.split(cmd), stdout = SUB.PIPE, cwd = dir)
67     left = ''
68     while True:
69       buf = kid.stdout.read(16384)
70       if not buf: break
71       buf = left + buf
72       i = 0
73       while True:
74         z = buf.find('\0', i)
75         if z < 0: break
76         f = buf[i:z]
77         if f.startswith('./'): f = f[2:]
78         yield f
79         i = z + 1
80       left = buf[i:]
81     if left:
82       raise U.ExpectedError, \
83             (500, "trailing junk from `%s' in `%s'" % (cmd, dir))
84   return _
85
86 DUMPERS = [
87   (exists_subdir('.git'), [filez('git ls-files -coz --exclude-standard'),
88                            filez('find .git -print0')]),
89   (lambda d: True, [filez('find . ( ! -perm +004 -prune ) -o -print0')])]
90
91 def dump_dir(dir, tf, root):
92   for test, listers in DUMPERS:
93     if test(dir): break
94   else:
95     raise U.ExpectedError, (500, "no dumper for `%s'" % dir)
96   for lister in listers:
97     base = OS.path.basename(dir)
98     for file in lister(dir):
99       tf.add(OS.path.join(dir, file), OS.path.join(root, base, file),
100              recursive = False)
101
102 def source(out):
103   if SYS.version_info >= (2, 6):
104     tf = TAR.open(fileobj = out, mode = 'w|gz', format = TAR.USTAR_FORMAT)
105   else:
106     tf = TAR.open(fileobj = out, mode = 'w|gz')
107     tf.posix = True
108   for d in dirs_to_dump():
109     dump_dir(d, tf, '%s-%s' % (PACKAGE, VERSION))
110   tf.close()
111
112 ###----- That's all, folks --------------------------------------------------