ActionPythonとDagon
身辺の整理をしていたら、以前作ったものの中断しているプロジェクトがありました。もうこれ以上私が手を加えるつもりはないので、概要とソースの場所だけ説明しておきます。興味のある方はこれらのプログラムを自由にして構いません。
ActionPython
ActionPythonは、ActionScriptで実装したPython, になる予定でした。Flex上でPythonが動くことを目指して作り始めたような気がします(うろ覚えなので、正しい言い方ではないかもしれません)。
プログラムは、パーサの部分までできています。PythonのコードをASTに変換することができます(ただし、with文は除きます)。
ASTをAVMのバイトコードにコンパイルするところからはできていません(要するに肝心なところができていない)。
ソースは、http://bitbucket.org/SumiTomohiko/actionpython/overviewにあります。
Dagon
Dagonは、issues管理システムです。Mercurialのリポジトリにこっそり専用のブランチを作成し、そこに置くファイルで管理します。こうすることで、issues管理も分散できるようになることを狙って作られました(Gitにも同じ考えのソフトウェアがあったはずで、Dagonはそれを真似しているのですが、名前は忘れました)。
MercurialはPythonで書かれているので、DagonはMercurial内部のクラスを操作します。特に長いコードではないので、以下に掲載します。
#!/usr/bin/env python # -*- coding: utf-8 -*- """ File Structure: File structure must be easy to merge or must avoid conflicts. repository root (dagon branch) `+- dagon `+- ticket directory (SHA1 name) | `+- main | | * title | | * author | | * date | | * state (new, assigned, resolved, closed, reopened, not a bug) | +- SHA1 name | | * author | | * date | | * body (multiline) : : """ from hashlib import sha1 from optparse import OptionParser from os.path import basename, dirname, join from sys import argv, exit from mercurial.cmdutil import match from mercurial.context import memctx, memfilectx from mercurial.error import LookupError from mercurial.hg import repository from mercurial.ui import ui from mercurial.util import datestr, parsedate branch_name = "dagon" dagon_root = "dagon" def hash(s1, s2): m = sha1() m.update(s1) m.update(s2) return m.hexdigest() class Ticket(object): def __init__(self): self.id = "" self.title = "" self.user = "" self.date = None self.desc = "" self.assigned = "" self.status = "" @classmethod def from_data(self, data): ticket = Ticket() desc = [] in_desc = False for s in data.split("\n"): if in_desc: if s == ".": ticket.desc = "\n".join(desc).strip() in_desc = False else: if s.startswith("."): s = s[1:] desc.append(s) continue n = s.find(":") if n < 0: continue key = s[:n].strip() value = s[n + 1:].strip() if key == "title": ticket.title = value elif key == "user": ticket.user = value elif key == "date": ticket.date = parsedate(value) elif key == "description": desc = [value] in_desc = True elif key == "assigned": ticket.assigned = value elif key == "status": ticket.status = value return ticket def create_key_value(self, key, value): return "%(key)s: %(value)s" % locals() def description2data(self, desc): lines = [] for line in desc.split("\n"): if line.startswith("."): s = "." + line else: s = line lines.append(s) lines.append(".") return "\n".join(lines) def to_data(self): data = [] data.append(self.create_key_value("title", self.title)) data.append(self.create_key_value("user", self.user)) data.append(self.create_key_value("date", datestr(self.date))) data.append(self.create_key_value("assigned", self.assigned)) data.append(self.create_key_value("status", self.status)) data.append(self.create_key_value("desc", self.description2data(self.desc))) return "\n".join(data) def assign_ticket_directory(ticket, head): if ticket.id != "": return ticket.id if head is None: return hash("", ticket.title) return hash(head, ticket.title) def save_ticket(path, ticket): repos = repository(ui(), path=path) heads = repos.branchheads(branch_name) try: head = heads[0] except IndexError: head = None dir = assign_ticket_directory(ticket, head) log = "saved ticket %s" % (dir, ) path = join(dagon_root, dir, "main") def filectxfn(repo, memctx, path): if ticket.user is None: ticket.user = ctx.user() if ticket.date is None: ticket.date = ctx.date() return memfilectx(path, ticket.to_data(), False, False, None) ctx = memctx(repos, (head, None), log, [path], filectxfn, extra={ "branch": branch_name }) repos.commitctx(ctx) def find_ticket(path, id): repos = repository(ui(), path=path) heads = repos.branchheads(branch_name) try: head = heads[0] except IndexError: return None; changeset = repos[head] for file in changeset.walk(match(repos)): name = basename(file) if name != "main": continue node = basename(dirname(file)) if not node.startswith(id): continue data = changeset.filectx(file).data() ticket = Ticket.from_data(data) ticket.id = node return ticket return None def do_list(path, args): repos = repository(ui(), path=path) heads = repos.branchheads(branch_name) try: head = heads[0] except IndexError: return; changeset = repos[head] tickets = [] for file in changeset.walk(match(repos)): name = basename(file) if name != "main": continue id = basename(dirname(file)) data = changeset.filectx(file).data() ticket = Ticket.from_data(data) ticket.id = id tickets.append(ticket) tickets.sort(key=lambda ticket: - ticket.date[0]) for ticket in tickets: l = "%(id)s %(title)-47s %(status)-8s %(assigned)s" % { "id": ticket.id[:6], "title": ticket.title, "status": ticket.status, "assigned": ticket.assigned } print l.strip() def help_new(): print "dagon new <title>" def do_new(path, args): parser = OptionParser() parser.add_option("-d", "--desc", dest="desc", help="description") (options, args) = parser.parse_args(args) desc = options.desc or "" try: title = args[0] except IndexError: help_new() exit(-1) ticket = Ticket() ticket.title = title ticket.desc = desc ticket.assigned = "" ticket.status = "new" save_ticket(path, ticket) def help_assign(): print "dagon assign <id> <name>" def do_assign(path, args): try: id = args[0] name = args[1] except IndexError: help_assign() exit(-1) ticket = find_ticket(path, id) if ticket is None: raise Exception("can't find ticket of %(id)s" % { "id": id }) ticket.assigned = name if ticket.status == "new": ticket.status = "assigned" save_ticket(path, ticket) def help_status(): print "dagon status <id> <status>" def do_status(path, args): try: id = args[0] status = args[1] except IndexError: help_status() exit(-1) ticket = find_ticket(path, id) if ticket is None: raise Exception("can't find ticket of %(id)s" % { "id": id }) ticket.status = status save_ticket(path, ticket) def main(args): parser = OptionParser() parser.add_option("-R", "--repository", dest="path", help="repository path") (options, args) = parser.parse_args(args) path = options.path or "" try: cmd = args[0] except IndexError: parser.print_help() exit(-1) commands = { "list": do_list, "new": do_new, "status": do_status, "assign": do_assign } commands[cmd](path, args[1:]) """ def filectxfn(repo, memctx, path): print filectxfn return memfilectx(path, data + "42", False, False, None) repos = repository(ui(), path=path) heads = repos.branchheads(branch_name) try: head = heads[0] from mercurial.node import short print short(head) recent_changeset = repos[head] print recent_changeset.files() print type(recent_changeset) path = "dagon.dat" from mercurial.cmdutil import match for s in recent_changeset.walk(match(repos)): print s try: data = recent_changeset.filectx(path).data() except LookupError: print "lookup error" data = "" except: head = None data = "" # TODO: do command ctx = memctx(repos, (head, None), "update", [path], filectxfn, extra={ "branch": branch_name }) repos.commitctx(ctx) """ main(argv[1:]) # vim: tabstop=4 shiftwidth=4 expandtab softtabstop=4 filetype=python
以上を含めたファイルは、http://bitbucket.org/SumiTomohiko/dagonにあります。