# # # patch "index.psp" # from [43de300221be3969e8017655a613f196e9633466] # to [91b20f95e6c81b15fa20307e27ab590700ea8200] # # patch "monotone.py" # from [d29711cd1c0d4ac4e3f48ca98444bba31f25a399] # to [fe810be90d92a76684a2035d9e7aa6b1a77347c9] # # patch "revision.psp" # from [d9bd56ea820aa74a61bd5e640b80c3f722f1108b] # to [51027eadc7443245e40030623274abe7202eef2b] # # patch "wrapper.py" # from [7a219e0f0134aa363238ed8fdfa0175d03410d89] # to [7444ff00e621412e16056b61cc1e9663219b417b] # ============================================================ --- index.psp 43de300221be3969e8017655a613f196e9633466 +++ index.psp 91b20f95e6c81b15fa20307e27ab590700ea8200 @@ -15,8 +15,8 @@ hq = common.html_escape() mt = Monotone(config.monotone, config.dbfile) - branches = mt.branches() +del mt %> @@ -48,6 +48,8 @@ <% + req.write(template.footer(info)) + %> ============================================================ --- monotone.py d29711cd1c0d4ac4e3f48ca98444bba31f25a399 +++ monotone.py fe810be90d92a76684a2035d9e7aa6b1a77347c9 @@ -16,14 +16,12 @@ # id_re = re.compile(r'^[0-9a-f]+$') - dash_re = re.compile(r'^-+$') cert_value_re = re.compile(r'^(\S*) *: (.*)$') - basic_io_re = re.compile(r'^ *(\S+) [\"\[](.*)[\"\]]$') - +basic_io_hex_re = re.compile(r'^ *(\S+) (\[[0-9A-Za-z]+\])$') +basic_io_string_re = re.compile(r'^ *(\S+) (\".*)$') manifest_entry_re = re.compile(r'^(\S+) *(.*)$') - log_entry_re = re.compile(r'^(\S+): (.*)$') def colour_from_string(str): @@ -35,11 +33,61 @@ sat = f(2) * 0.5 + .5 return ''.join(map(lambda x: "%.2x" % int(x * 256), hls_to_rgb(hue, li, sat))) +class Automation: + def __init__(self, mt, dbfile): + self.mt = mt + self.dbfile = dbfile + self.command = "%s --db=%s automate stdio" % (self.mt, pipes.quote(self.dbfile)) + #self.command = "/bin/cat" + self.process = None + def __del__(self): + if self.process: + try: + self.process.tochild.close() + self.process.fromchild.close() + self.process.wait() + except: pass + self.process = None + def start(self): + self.process = popen2.Popen3(self.command) + set_nonblocking(self.process.fromchild) + def run(self, command, args): + if self.process == None: self.start() + enc = "l%d:%s" % (len(command), command) + enc += ''.join(map(lambda x: "%d:%s" % (len(x), x), args)) + 'e' + self.process.tochild.write(enc) + self.process.tochild.flush() + r = RecvPacket() + complete = False + result_string = "" + result_code = None + while not complete: + ro, rw, re = select.select([self.process.fromchild], [], [], None) + if not ro and not rw and not re: + break + if self.process.fromchild in ro: + recv = self.process.fromchild.read() + if recv == "": break + tv = r.process_data(recv) + if tv != None: + cmdnum, error, length, is_last, result = tv + if result_code == None: result_code = int(error) + result_string += result + if is_last: + complete = True + else: + # in case there is anything left over we + # didn't parse + r = RecvPacket(r.buffer) + return result_code, result_string + + class Monotone: def __init__(self, mt, dbfile): self.mt = mt self.dbfile = dbfile self.base_command = "%s --db=%s" % (self.mt, pipes.quote(self.dbfile)) + self.automate = Automation(self.mt, self.dbfile) def branches(self): result = utility.run_command(self.base_command + " ls branches") if result['exitcode'] != 0: @@ -53,39 +101,64 @@ else: return map(lambda x: x.split(' ', 2), filter(None, result['fromchild'].split('\n'))) def heads(self, branch): - result = utility.run_command(self.base_command + " automate heads %s" % (pipes.quote(branch))) - if result['exitcode'] != 0: + error, result = self.automate.run('heads', [branch]) + if error != 0: raise Exception("Unable to get list of heads: %s" % (result['childerr'])) else: - return filter(None, result['fromchild'].split('\n')) - def certs(self, id): + return filter(None, result.split('\n')) + def basic_io_parser(self, data): + "returns a list of lists of (key, type, value) tuples." + def unescape_string_value(str): + rv = "" + is_terminated = False + in_escape = False + if str[0] != '"': + raise Exception("basic_io parse error; not a string.") + for c in str[1:]: + if in_escape: + if c != '\\' and c != '\"': + raise Exception("basic_io parse error; expected \" or \\") + rv += c + in_escape = False + else: + if c == '\\': in_escape = True + if c == '"': + if is_terminated: + raise Exception("basic_io parse error; string ends twice!") + is_terminated = True + else: rv += c + return is_terminated, rv rv = [] - c_cert = None - c_key = c_value = None - for line in utility.iter_command(self.base_command + " ls certs %s" % (pipes.quote(id))): - if dash_re.match(line): - if c_cert: - if c_key != None: - c_cert[c_key] = c_value - c_key = c_value = None - rv.append(c_cert) - c_cert = {} - elif c_cert != None: - m = cert_value_re.match(line) - if not m: continue - key, value = m.groups() - if key != '': - # then we don't have a continuation - if c_key != None: c_cert[c_key] = c_value - c_key, c_value = key, [value] - else: - c_value.append(value) - if c_cert: - if c_key != None: - c_cert[c_key] = c_value - c_key = c_value = None - rv.append(c_cert) + stanza = [] + ongoing_string = None + for line in data.split('\n'): + if not ongoing_string: + if line == '' and len(stanza) != 0: + rv.append(stanza) + stanza = [] + m = basic_io_hex_re.match(line) + if m: + key, value = m.groups() + stanza.append((key, value)) + continue + m = basic_io_string_re.match(line) + if m: + key, value = m.groups() + is_terminated, e_value = unescape_string_value(value) + if not is_terminated: ongoing_string = value + else: stanza.append((key, e_value)) + continue + else: + ongoing_string += '\n' + line + is_terminated, e_value = unescape_string_value(ongoing_string) + if is_terminated: + stanza.append((key, e_value)) + ongoing_string = None return rv + def certs(self, id): + error, data = self.automate.run('certs', [id]) + if error != 0: raise Exception("Error obtaining cert for %s: %s" % (id, data)) + return self.basic_io_parser(data) def revision(self, id): rv = {} cv = [] @@ -256,52 +329,3 @@ if len(self.result) == self.length: return (self.cmdnum, self.error, self.length, self.is_last, self.result) else: return None - -class Automation: - def __init__(self, mt, dbfile): - self.mt = mt - self.dbfile = dbfile - self.command = "%s --db=%s automate stdio" % (self.mt, pipes.quote(self.dbfile)) - #self.command = "/bin/cat" - self.process = None - def __del__(self): - if self.process: - try: - self.process.tochild.close() - self.process.fromchild.close() - self.process.wait() - except: pass - self.process = None - def start(self): - self.process = popen2.Popen3(self.command) - set_nonblocking(self.process.fromchild) - def run(self, command, args): - if self.process == None: self.start() - enc = "l%d:%s" % (len(command), command) - enc += ''.join(map(lambda x: "%d:%s" % (len(x), x), args)) + 'e' - self.process.tochild.write(enc) - self.process.tochild.flush() - r = RecvPacket() - complete = False - result_string = "" - result_code = None - while not complete: - ro, rw, re = select.select([self.process.fromchild], [], [], None) - if not ro and not rw and not re: - break - if self.process.fromchild in ro: - recv = self.process.fromchild.read() - if recv == "": break - tv = r.process_data(recv) - if tv != None: - cmdnum, error, length, is_last, result = tv - if result_code == None: result_code = error - result_string += result - if is_last: - complete = True - else: - # in case there is anything left over we - # didn't parse - r = RecvPacket(r.buffer) - return result_code, result_string - ============================================================ --- revision.psp d9bd56ea820aa74a61bd5e640b80c3f722f1108b +++ revision.psp 51027eadc7443245e40030623274abe7202eef2b @@ -7,6 +7,7 @@ import template from template import header,footer from monotone import Monotone +reload(monotone) from common import link # @@ -55,8 +56,11 @@ <% certs = mt.certs(id) for cert in certs: - if not cert.has_key("Name") or not cert.has_key("Value"): continue - name, value = cert["Name"][0], hq(cert["Value"]) + name, value = None, None + for k, v in cert: + if k == "name": name = v + elif k == "value": value = '
'.join(v.split('\n')) + if name == None or value == None: continue if name == "branch": value = link("branch", value) req.write('%s%s' % (prettify(name), value)) @@ -171,6 +175,7 @@ <% req.write(footer(info)) +del mt %> ============================================================ --- wrapper.py 7a219e0f0134aa363238ed8fdfa0175d03410d89 +++ wrapper.py 7444ff00e621412e16056b61cc1e9663219b417b @@ -1,8 +1,9 @@ from mod_python import apache,psp,util import config import monotone import mimetypes +import urllib from monotone import Monotone import template import tarfile @@ -20,9 +21,13 @@ id = form['id'] if not monotone.is_valid_id(id): return apache.HTTP_BAD_REQUEST - mime_type = None + if form.has_key('plain'): + mime_type = "text/plain" + else: + mime_type = None if form.has_key('path'): - mime_type = mimetypes.guess_type(form['path'])[0] + if mime_type == None: mime_type = mimetypes.guess_type(form['path'])[0] + req.headers_out["Content-Disposition"] = "attachment; filename=%s" % urllib.quote(form['path']) if mime_type == None: mime_type = "text/plain" req.content_type = mime_type req.write(mt.file(id))