importing bzrfruit into git
[bzrfruit.git] / browser.py
blobe836294711be4430f900cf964c9fd23e9fa5e194
1 # Branch Browser for Bazaar
2 # Run this with web.py
4 from os import listdir
5 from os.path import join, isdir, abspath, expanduser
6 from datetime import datetime
7 from configobj import ConfigObj
9 from bzrlib.branch import BzrBranch
10 from bzrlib.errors import NoSuchRevision
11 from bzrlib.osutils import format_date
12 import bzrlib
14 from pygments import highlight
15 from pygments.formatters import HtmlFormatter
16 from pygments.lexers import TextLexer, guess_lexer_for_filename
18 import web
19 web.webapi.internalerror = web.debugerror
21 from genshi.template import TemplateLoader
22 from genshi.core import Markup
25 # Read config file from $HOME
26 usr_config = ConfigObj(expanduser("~/.bzrfruit.conf"))
27 config = ConfigObj(dict(web=dict(extra_css="",
28 skel_template="skel.html"),
29 bzr=dict()))
30 config.merge(usr_config) # merge with the default values
33 loader = TemplateLoader(["templates"], auto_reload=True)
34 def render(name, **namespace):
35 "Render the genshi template `name' using `namespace'"
36 tmpl = loader.load(name)
37 namespace.update(globals()) # we need not pass common methods explicitly
38 stream = tmpl.generate(**namespace)
39 web.header("Content-Type","%s; charset=utf-8" % "text/html")
40 print stream.render('html')
43 # This directory contains the bzr branches
44 BRANCHES_PATH = expanduser(config["bzr"]["branches_path"])
45 # $ bzr get BRANCH_URL+nick
46 BRANCH_URL = config["web"]["branch_url_base"]
48 WWW_URL = config["web"]["www_url_base"]
50 def link_to(*segs):
51 "Sorry.. this function is difficult to explain. May be some other time."
52 # root = web.prefixurl()
53 root = WWW_URL
54 segs = map(lambda s: str(s).lstrip("/"), segs)
55 return root + "/".join(segs)
57 # http://trac.edgewall.org/browser/trunk/trac/util/datefmt.py
58 def pretty_timedelta(time1, time2, resolution=None):
59 """Calculate time delta (inaccurately, only for decorative purposes ;-) for
60 prettyprinting. If time1 is None, the current time is used."""
61 if time1 > time2:
62 time2, time1 = time1, time2
63 units = ((3600 * 24 * 365, 'year', 'years'),
64 (3600 * 24 * 30, 'month', 'months'),
65 (3600 * 24 * 7, 'week', 'weeks'),
66 (3600 * 24, 'day', 'days'),
67 (3600, 'hour', 'hours'),
68 (60, 'minute', 'minutes'))
69 diff = time2 - time1
70 age_s = int(diff.days * 86400 + diff.seconds)
71 if resolution and age_s < resolution:
72 return ''
73 if age_s < 60:
74 return '%i second%s' % (age_s, age_s != 1 and 's' or '')
75 for u, unit, unit_plural in units:
76 r = float(age_s) / float(u)
77 if r >= 0.9:
78 r = int(round(r))
79 return '%d %s' % (r, r == 1 and unit or unit_plural)
80 return ''
82 def pretty_size(size):
83 jump = 512
84 if size < jump:
85 return "%d bytes" % size
87 units = ['kB', 'MB', 'GB', 'TB']
88 i = 0
89 while size >= jump and i < len(units):
90 i += 1
91 size /= 1024.0
93 return "%.1f %s" % (size, units[i - 1])
96 class Branch:
98 def __init__(self, url):
99 self.branch = BzrBranch.open(url)
101 def name(self):
102 return self.branch.nick
104 def inventory(self):
105 "Return the Inventory for last revision"
106 b = self.branch
107 return b.repository.get_revision_inventory(b.last_revision())
109 def revision_tree(self):
110 b = self.branch
111 return b.repository.revision_tree(b.last_revision())
114 def path2inventory(branch, path):
115 # XXX: investigate if this can be done using bzrlib itself
116 i = branch.inventory().root
117 path = path.strip("/")
118 if path == "": return i
119 for component in path.split("/"):
120 i = i.children[component]
121 return i
124 class BranchPath:
126 def __init__(self, branch, path, inventory):
127 self.path = path
128 self.branch = branch
129 self.inventory = inventory
130 self.name = inventory.name
132 def has_text(self):
133 "Usually calls self.inventory.has_text"
134 return self.inventory.has_text()
136 def revision(self):
137 return self.branch.branch.repository.get_revision(
138 self.inventory.revision)
140 def rev(self):
141 try:
142 revno = self.branch.branch.revision_id_to_revno(
143 self.inventory.revision)
144 except NoSuchRevision:
145 # FIXME: eek!
146 revno = "??" # self.inventory.revision
147 return revno, self.inventory.revision
149 def age(self):
150 r = self.revision()
151 dt = datetime.fromtimestamp(r.timestamp)
152 return format_date(r.timestamp, r.timezone), \
153 pretty_timedelta(dt, datetime.now())
155 def demo(self):
156 h = self.branch.branch.revision_history()
157 last_revid = h[-1]
158 r = bzrlib.tsort.merge_sort(
159 self.branch.branch.repository.get_revision_graph( ),last_revid)
160 l=list([ revid for (seq, revid, merge_depth, end_of_merge) in r ])
161 return '\n'.join(l)
164 class BranchDir(BranchPath):
165 "Represents a directory in branch"
167 def get_children(self):
168 children = self.inventory.sorted_children()
170 # directories first
171 for name, inv in children:
172 if not inv.has_text():
173 yield BranchDir(self.branch, self.path+inv.name+"/", inv)
175 # then files
176 for name, inv in children:
177 if inv.has_text():
178 yield BranchFile(self.branch, self.path+inv.name, inv)
181 class BranchFile(BranchPath):
182 "Represents a file is branch"
184 def content(self):
185 tree = self.branch.revision_tree()
186 return tree.get_file_text(self.inventory.file_id)
188 def highlighted_html(self):
189 "Return the pygments highlighted HTML"
190 code = self.content()
191 try:
192 lexer = guess_lexer_for_filename(self.path, code)
193 except:
194 lexer = TextLexer()
195 html = highlight(code, lexer, HtmlFormatter())
196 # TODO: this is genshi's bug. It removes double \n in Markup()
197 # since we use <pre>, \n are important.
198 # let's use <br/> as a workaround.
199 # In near future, this must be fixed in genshi.
200 html = html.replace('\n', '<br/>')
201 return html
204 urls = (
205 "/", "branch_list",
206 "/([^/]*)/(.*)", "branch_browser"
210 class branch_list:
211 def GET(self):
212 def available_branches():
213 for dir in listdir(BRANCHES_PATH):
214 branch_path = join(BRANCHES_PATH, dir)
215 # Bazaar branch directory should contain
216 # a .bzr sub-directory
217 if isdir(join(branch_path, ".bzr")):
218 yield Branch(branch_path)
220 render('index.html',
221 title="Branches",
222 branches=list(available_branches()))
225 class branch_browser:
226 def GET(self, name, p):
227 p = "/" + p
228 b = Branch(join(BRANCHES_PATH, name))
229 if p.endswith("/"):
230 path = BranchDir(b, p, path2inventory(b, p))
231 else:
232 path = BranchFile(b, p, path2inventory(b, p))
234 # breadcrumb
235 breadcrumb = []
236 components = path.path.strip('/').split('/')
237 cumulative = [b.name()]
238 for c in components:
239 cumulative.append(c)
240 url = link_to(*cumulative) + '/'
241 breadcrumb.append([c, url])
242 breadcrumb[-1][1] = None # "current" page, so need not link
244 render('browser.html',
245 title="%s%s" % (b.name(), path.path),
246 branch=b,
247 branch_url=BRANCH_URL+b.name(),
248 path=path,
249 breadcrumb=breadcrumb)