# parse project_planning.xml # and put the parameters into the WP files. # # CT 03-SEP-11 import xml.sax import mkxref class Task: def __init__(self, id, container): self.id = id self.title = "no title" self.children = [] self.constraints = [] self.parties = {} self.duration = 0 self.dur_calculated = False self.container = container self.buffer = "" if container: container.append(self) self.start = 0 self.start_calculated = False def append(self, child): self.children.append(child) def remove(self, child): self.children.remove(child) def calc_duration(self): if not self.dur_calculated: durs = [child.calc_duration() for child in self.children] if not durs: dur = 0 else: dur = max(durs) self.duration = max(dur, self.duration) self.dur_calculated = True return self.duration def get_constraints(self): # merge with container constraints co = self.constraints[:] task = self.container while task: co.extend(task.constraints) task = task.container return co def calc_start(self): if not self.start_calculated: all = all_dict(self) co = [all[tn] for tn in self.get_constraints() if tn in all] ends = [t.calc_start() + t.calc_duration() for t in co] self.start = max(ends+[self.start]) self.start_calculated = True return self.start class MyHandler(xml.sax.ContentHandler): def __init__(self): self.current = Task("project", None) xml.sax.ContentHandler.__init__(self) def startElement(self, name, attrs): if name == "task": id = attrs["id"] self.current = Task(id, self.current) return if name == "use-resource": idref = attrs["idref"] cost = attrs["cost"] party = idref.split("_")[1].upper() if party == "LOGILAB": party = "Logilab" cost = float(cost) #permonth = cost / self.duration # opps, we don't want this, but toal! #print cost, usage, self.duration, permonth task = self.current task.parties[party] = task.parties.get(party, 0) #self.parties[party] += permonth task.parties[party] += cost def endElement(self, name): if name == "task": self.current = self.current.container return if name == "constraint": # we are after constraint and have the buffer wp = self.current.buffer.strip() self.current.constraints.append(wp) return if name == "duration": self.current.duration = int(self.current.buffer.strip()) return if name == "label": self.current.title = self.current.buffer.split("-", 1)[-1].strip() def characters(self, content): self.current.buffer = content template = """\ .. |title| replace:: |e| .. |wp| replace:: |e| .. |start| replace:: |e| .. |p1| replace:: |e| .. |m1| replace:: |e| .. |p2| replace:: |e| .. |m2| replace:: |e| .. |p3| replace:: |e| .. |m3| replace:: |e| .. |p4| replace:: |e| .. |m4| replace:: |e| .. |p5| replace:: |e| .. |m5| replace:: |e| .. |p6| replace:: |e| .. |m6| replace:: |e| """ def edit_wp_text(s, wp, title, start, parties): pre, rest = s.split(".. |title|") ign, post = rest.split(".. |m6| replace:: |e|") pre = pre.strip()+"\n\n" post = "\n" + post.strip() + "\n" t = template t = t.replace("|e|", title, 1) t = t.replace("|e|", wp, 1) t = t.replace("|e|", str(start), 1) # trick: use negative dur for sorting rev = [ (-dur, name) for name, dur in parties.iteritems() ] rev.sort() for dur, name in rev: dur = abs(dur) t = t.replace("|e|", name, 1) durtext = "%1.3g" % dur durt2 = "%0.2f" % dur if len(durt2) < len(durtext): durtext = durt2 t = t.replace("|e|", durtext, 1) return pre + t + post def print_tree(task, lev=0): indent = " " * (lev+lev+1) self = task print indent, self.id, self.title print indent, "Duration", self.duration print indent, "Parties", self.parties print indent, "Constraints", self.constraints print indent, "Start", self.calc_start() for child in self.children: print_tree(child, lev+1) print indent, self.id, "END" def all_dict(task): while task.container: task = task.container root = task res = {} todo = [root] while todo: next = todo.pop(0) res[next.id] = next todo.extend(next.children) return res # special hack for wp 6 def merge_task(task): # merge a task with its direct dependency # must be a leaf # both with the same parent if len(task.constraints) != 1: raise ValueError, "merge must have exactly one contraint" all = all_dict(task) other = all[task.constraints[0]] if task.container != other.container: raise ValueError, "merged tasks must have the same container" if task.children or other.children: raise ValueError, "cannot merge with children" # now we extend other's duration and add parties other.duration += task.duration for party, dur in task.parties.iteritems(): dur += other.parties.get(party, 0) other.parties[party] = dur # remove ourself task.container.remove(task) def merge_dotted_tasks(root): all = all_dict(root) groups = {} for id in all.keys(): if len(id.split(".")) == 2: wp, subid = id.split(".") groups[wp] = groups.get(wp, []) groups[wp].append(int(subid)) # now merge groups for group, subs in groups.items(): # work backwards subs.sort() subs.reverse() # merge all but first last = subs.pop() for num in subs: id = "%s.%d" % (group, num) merge_task(all[id]) # assign new name id = "%s.%d" % (group, last) all[id].id = group f = file('project_planning.xml', 'r') handler = MyHandler() xml.sax.parse(f, handler) result = handler.current merge_dotted_tasks(result) result.calc_duration() #print_tree(result) all = all_dict(result) for wp, fname in mkxref.get_wps_from_dir().iteritems(): id = "wp%d" % int(wp[2:]) task = all[id] txt = file(fname).read() newtxt = edit_wp_text(txt, wp, task.title, task.calc_start(), task.parties) if newtxt != txt: print "writing", wp file(fname, "w").write(newtxt)