chiark / gitweb /
wrapper.fhtml: Put `html' in lowercase in the DOCTYPE declaration.
[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 grp as GR
28 import os as OS
29 import pwd as PW
30 import shlex as SL
31 import shutil as SH
32 import subprocess as SUB
33 import sys as SYS
34 import tarfile as TAR
35 import tempfile as TF
36 import time as T
37
38 from cStringIO import StringIO
39
40 from auto import PACKAGE, VERSION
41 import util as U
42
43 @CTX.contextmanager
44 def tempdir():
45   d = TF.mkdtemp()
46   try: yield d
47   finally: SH.rmtree(d, ignore_errors = True)
48
49 def dirs_to_dump():
50   dirs = set()
51   for m in SYS.modules.itervalues():
52     try: f = m.__file__
53     except AttributeError: continue
54     d = OS.path.realpath(OS.path.dirname(f))
55     if d.startswith('/usr/') and not d.startswith('/usr/local/'): continue
56     dirs.add(d)
57   dirs = sorted(dirs)
58   last = '!'
59   dump = []
60   for d in dirs:
61     if d.startswith(last): continue
62     dump.append(d)
63     last = d
64   return dump
65
66 def exists_subdir(subdir):
67   return lambda dir: OS.path.isdir(OS.path.join(dir, subdir))
68
69 def filez(cmd):
70   def _(dir):
71     kid = SUB.Popen(SL.split(cmd), stdout = SUB.PIPE, cwd = dir)
72     left = ''
73     while True:
74       buf = kid.stdout.read(16384)
75       if not buf: break
76       buf = left + buf
77       i = 0
78       while True:
79         z = buf.find('\0', i)
80         if z < 0: break
81         f = buf[i:z]
82         i = z + 1
83         if f == '.': continue
84         if f.startswith('./'): f = f[2:]
85         yield f
86       left = buf[i:]
87     if left:
88       raise U.ExpectedError, \
89             (500, "trailing junk from `%s' in `%s'" % (cmd, dir))
90   return _
91
92 DUMPERS = [
93   (exists_subdir('.git'), [filez('git ls-files -coz --exclude-standard'),
94                            filez('find .git -print0')]),
95   (lambda d: True, [filez('find . ( ! -perm +004 -prune ) -o -print0')])]
96
97 def dump_dir(name, dir, dirmap, tf, root):
98   for test, listers in DUMPERS:
99     if test(dir): break
100   else:
101     raise U.ExpectedError, (500, "no dumper for `%s'" % dir)
102   tf.add(dir, OS.path.join(root, name), recursive = False)
103   for lister in listers:
104     for file in lister(dir):
105       full = OS.path.join(dir, file)
106       tarname = OS.path.join(root, name, file)
107       skip = False
108       if OS.path.islink(full):
109         dest = OS.path.realpath(full)
110         for d, local in dirmap:
111           if dest.startswith(d):
112             fix = OS.path.relpath(OS.path.join('/', local, dest[len(d):]),
113                                   OS.path.join('/', name,
114                                                OS.path.dirname(file)))
115             st = OS.stat(full)
116             ti = tf.gettarinfo(full, tarname)
117             ti.linkname = fix
118             tf.addfile(ti)
119             skip = True
120       if not skip:
121         tf.add(full, tarname, recursive = False)
122
123 def source(out):
124   if SYS.version_info >= (2, 6):
125     tf = TAR.open(fileobj = out, mode = 'w|gz', format = TAR.USTAR_FORMAT)
126   else:
127     tf = TAR.open(fileobj = out, mode = 'w|gz')
128     tf.posix = True
129   root = '%s-%s' % (PACKAGE, VERSION)
130   seen = set()
131   dirmap = []
132   festout = StringIO()
133   for dir in dirs_to_dump():
134     dir = dir.rstrip('/')
135     base = OS.path.basename(dir)
136     if base not in seen:
137       name = base
138     else:
139       for i in I.count():
140         name = '%s.%d' % (base, i)
141         if name not in seen: break
142     dirmap.append((dir + '/', name))
143     festout.write('%s = %s\n' % (name, dir))
144   fest = festout.getvalue()
145   ti = TAR.TarInfo(OS.path.join(root, 'MANIFEST'))
146   ti.size = len(fest)
147   ti.mtime = T.time()
148   ti.mode = 0664
149   ti.type = TAR.REGTYPE
150   uid = OS.getuid(); ti.uid, ti.uname = uid, PW.getpwuid(uid).pw_name
151   gid = OS.getgid(); ti.gid, ti.gname = gid, GR.getgrgid(gid).gr_name
152   tf.addfile(ti, fileobj = StringIO(fest))
153   for dir, name in dirmap:
154     dump_dir(name, dir, dirmap, tf, root)
155   tf.close()
156
157 ###----- That's all, folks --------------------------------------------------