Changeset 1694
- Timestamp:
- 12/14/06 18:51:34 (2 years ago)
- Files:
-
- revtreeplugin/0.10/revtree/api.py (added)
- revtreeplugin/0.10/revtree/enhancer.py (modified) (3 diffs)
- revtreeplugin/0.10/revtree/htdocs/css/revtree.css (modified) (1 diff)
- revtreeplugin/0.10/revtree/__init__.py (modified) (1 diff)
- revtreeplugin/0.10/revtree/logenhancer.py (deleted)
- revtreeplugin/0.10/revtree/model.py (modified) (4 diffs)
- revtreeplugin/0.10/revtree/svgview.py (modified) (11 diffs)
- revtreeplugin/0.10/revtree/web_ui.py (modified) (9 diffs)
- revtreeplugin/0.10/setup.py (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
revtreeplugin/0.10/revtree/enhancer.py
r1652 r1694 13 13 # 14 14 15 from revtree import IRevtreeEnhancer 15 16 from revtree.svgview import SvgOperation, SvgGroup 17 from trac.core import * 16 18 17 19 18 class Enhancer(object): 19 """Enhance the appearance of the RevTree with site-specific properties 20 This file is a very basic skeleton that needs to customized, to provide 21 SvgOperation, SvgGroup and other widgets in the RevTree graphic""" 20 class SimpleContainer(object): 21 """Simple container for enhancer parameters""" 22 22 23 def __init__(self, repos, svgrevtree): 24 self._repos = repos 25 self._creations = [] 26 self._deliveries = [] 27 self._groups = [] 28 self._svgrevtree = svgrevtree 23 def __init__(self): 24 pass 25 26 27 class SimpleEnhancer(Component): 28 """Enhance the appearance of the RevTree with site-specific properties. 29 30 Create branch clone operation (on branch/tag operations) 31 32 This class is a very basic skeleton that needs to customized, to provide 33 SvgOperation, SvgGroup and other widgets in the RevTree graphic 34 """ 35 36 implements(IRevtreeEnhancer) 37 38 def create(self, env, req, repos, svgrevtree): 39 """Creates the internal data from the repository""" 40 enhancer = SimpleContainer() 41 enhancer.repos = repos 42 enhancer.creations = [] 43 enhancer.deliveries = [] 44 enhancer.groups = [] 45 enhancer.svgrevtree = svgrevtree 29 46 # z-depth indexed widgets: back=1, fore=2 30 self._widgets = ([], [], []) 31 32 def create(self): 33 """Creates the internal data from the repository""" 34 self._sort() 35 for branch in self._repos.branches().values(): 36 svgbranch = self._svgrevtree.svgbranch(branch=branch) 47 enhancer.widgets = ([], [], []) 48 49 #self._sort() 50 for branch in enhancer.repos.branches().values(): 51 svgbranch = enhancer.svgrevtree.svgbranch(branch=branch) 37 52 if not svgbranch: 38 53 # branch has probably been filtered out … … 45 60 svgbranch.svgchangeset(firstchgset).invert_color() 46 61 (rev, path) = branch.source() 47 srcchg = self._repos.changeset(rev)62 srcchg = enhancer.repos.changeset(rev) 48 63 if srcchg is None: 49 64 continue 50 65 # .. and create an operation between both changesets 51 self._creations.append((srcchg, firstchgset))66 enhancer.creations.append((srcchg, firstchgset)) 52 67 lastchgset = branch.youngest() 53 68 if lastchgset: … … 56 71 # tweak the color of this changeset 57 72 svgbranch.svgchangeset(lastchgset).kill() 73 return enhancer 58 74 59 def build(self ):75 def build(self, enhancer): 60 76 """Build the enhanced widgets""" 61 for (srcchg, dstchg) in self._creations:62 svgsrcbr = self._svgrevtree.svgbranch(branchname=srcchg.branchname)77 for (srcchg, dstchg) in enhancer.creations: 78 svgsrcbr = enhancer.svgrevtree.svgbranch(branchname=srcchg.branchname) 63 79 if svgsrcbr is None: 64 80 continue 65 81 svgsrcchg = svgsrcbr.svgchangeset(srcchg) 66 svgdstbr = self._svgrevtree.svgbranch(branchname=dstchg.branchname)82 svgdstbr = enhancer.svgrevtree.svgbranch(branchname=dstchg.branchname) 67 83 if svgdstbr is None: 68 84 continue 69 85 svgdstchg = svgdstbr.svgchangeset(dstchg) 70 op = SvgOperation(self._svgrevtree, svgsrcchg, svgdstchg, '#5faf5f') 71 self._widgets[2].append(op) 86 op = SvgOperation(enhancer.svgrevtree, svgsrcchg, svgdstchg, \ 87 '#5faf5f') 88 enhancer.widgets[2].append(op) 72 89 73 for wl in self._widgets:90 for wl in enhancer.widgets: 74 91 map(lambda w: w.build(), wl) 75 92 76 def render(self, level): 77 """Renders widgets, from background plane to foreground plane""" 78 if level < len(self._widgets): 79 map(lambda w: w.render(), self._widgets[level]) 93 def render(self, enhancer, level): 94 """Renders the widgets, from background plane to foreground plane""" 95 if level < len(enhancer.widgets): 96 map(lambda w: w.render(), enhancer.widgets[level]) 97 98 99 80 100 81 def optimize(self, branches):82 """Provides a list of branches, sorted from left-most to rigtht-most83 position.84 Optimal placement is required to reduce the number of operation links85 that cross each other on the rendered graphic"""86 return [b for b in self._obranches if b in branches]87 88 def _sort(self):89 """Computes the optimal placement of branches.90 This example is FAR from providing optimal placements ;-)"""91 graph = {}92 for v in self._repos.branches().values():93 k = v.name94 src = v.source()95 if src:96 (rev, path) = src97 if graph.has_key(path):98 graph[path].append(k)99 else:100 graph[path] = [k]101 density = []102 for (p, v) in graph.items():103 density.append((p,len(v)))104 density.sort(lambda a,b: cmp(a[1],b[1]))105 density.reverse()106 order = []107 cur = 0108 for (branch, weight) in density:109 order.insert(cur, branch)110 if cur:111 cur = 0112 else:113 cur = len(order)114 branches = []115 for br in graph.values():116 branches.extend(br)117 branches.extend([br.name for br in self._repos.branches().values() \118 if br.name not in branches])119 for branch in branches:120 if branch in order:121 continue122 order.insert(cur, branch)123 if cur:124 cur = 0125 else:126 cur = len(order)127 self._obranches = [self._repos.branch(name) for name in order]128 revtreeplugin/0.10/revtree/htdocs/css/revtree.css
r1675 r1694 31 31 /* FIXME */ 32 32 width: 100%; 33 border-top: 4px solid red;34 background-color: red;35 33 margin-top: 0; 36 34 margin-left: 0; revtreeplugin/0.10/revtree/__init__.py
r1633 r1694 12 12 # history and logs, available at http://projects.edgewall.com/trac/. 13 13 14 from revtree. model import Repository, ChangesetEmptyRange15 from revtree.svgview import SvgRevtree 14 from revtree.api import EmptyRangeError, IRevtreeEnhancer, \ 15 IRevtreeOptimizer, RevtreeSystem revtreeplugin/0.10/revtree/model.py
r1675 r1694 15 15 import re 16 16 import time 17 18 from revtree import IRevtreeOptimizer 17 19 from trac.versioncontrol import Node, Changeset 18 from trac.core import TracError 19 20 from trac.core import * 20 21 # only for get_revision_properties 21 22 from svn import fs 22 23 23 class ChangesetEmptyRange(TracError): 24 """Defines a RevTree error (no changeset in the selected range)""" 25 def __init__(self, msg=None): 26 TracError.__init__(self, "%sNo changeset" \ 27 % (msg and '%s: ' % msg or '')) 24 25 class DefaultRevtreeOptimizer(Component): 26 """Default optmizer""" 27 28 implements(IRevtreeOptimizer) 29 30 def optimize(self, repos, branches): 31 """Computes the optimal placement of branches. 32 33 Optimal placement is recommended to reduce the number of operation 34 links that cross each other on the rendered graphic. 35 This rudimentary example is FAR from providing optimal placements... 36 """ 37 # FIXME: really stupid algorithm 38 graph = {} 39 for v in repos.branches().values(): 40 k = v.name 41 src = v.source() 42 if src: 43 (rev, path) = src 44 if graph.has_key(path): 45 graph[path].append(k) 46 else: 47 graph[path] = [k] 48 density = [] 49 for (p, v) in graph.items(): 50 density.append((p,len(v))) 51 density.sort(lambda a,b: cmp(a[1],b[1])) 52 density.reverse() 53 order = [] 54 cur = 0 55 for (branch, weight) in density: 56 order.insert(cur, branch) 57 if cur: 58 cur = 0 59 else: 60 cur = len(order) 61 nbranches = [] 62 for br in graph.values(): 63 nbranches.extend(br) 64 nbranches.extend([br.name for br in repos.branches().values() \ 65 if br.name not in nbranches]) 66 for branch in nbranches: 67 if branch in order: 68 continue 69 order.insert(cur, branch) 70 if cur: 71 cur = 0 72 else: 73 cur = len(order) 74 obranches = [repos.branch(name) for name in order] 75 # FIXME: use filter() 76 return [b for b in obranches if b in branches] 77 28 78 29 79 class BranchChangeset(object): … … 284 334 """Builds an internal representation of the repository, which 285 335 is used to generate a graphical view of it""" 286 self._crepos.sync()287 336 start = 0 288 337 stop = int(time.time()) … … 305 354 vcsort = [(c.rev, c) for c in vcchangesets] 306 355 if len(vcsort) < 1: 307 raise ChangesetEmptyRange356 raise EmptyRangeError 308 357 vcsort.sort() 309 358 self._revrange = (vcsort[0][1].rev,vcsort[-1][1].rev) … … 322 371 (br, len(self._branches[br])) 323 372 return msg 373 revtreeplugin/0.10/revtree/svgview.py
r1675 r1694 16 16 import os 17 17 18 from revtree import ChangesetEmptyRange18 from colorsys import rgb_to_hsv, hsv_to_rgb 19 19 from math import sqrt 20 20 from random import randrange, seed 21 from colorsys import rgb_to_hsv, hsv_to_rgb 21 from revtree import EmptyRangeError, IRevtreeEnhancer, IRevtreeOptimizer 22 from trac.core import * 22 23 23 24 UNIT = 25 … … 48 49 return "javascript:window.parent.location.href='%s'" % url 49 50 50 51 51 52 class SvgColor(object): 52 53 """Helpers for color management (conversion, generation, ...)""" … … 224 225 self._strokecolor, 225 226 self._parent.strokewidth()) 226 g = SVG.group(' rev%d' % self._revision,227 g = SVG.group('grp%d' % self._revision, 227 228 elements=[self._widget, lt, lb]) 228 229 self._widget = g … … 752 753 """Main object that represents the revision tree as a SVG graph""" 753 754 754 def __init__(self, env, repos, urlbase ):755 def __init__(self, env, repos, urlbase, enhancers, optimizer): 755 756 """Construct a new SVG revision tree""" 756 757 # Environment … … 763 764 self.revrange = None 764 765 # Optional enhancers 765 self._enhancers = [] 766 self.enhancers = enhancers 767 # Optimizer 768 self.optimizer = optimizer 766 769 # Trunk branches 767 770 self.trunks = self.env.config.get('revtree', 'trunks', … … 777 780 # Operation points 778 781 self._oppoints = {} 782 # Add-on elements (from enhancers) 783 self._addons = {} 779 784 # Init color generator with a predefined value 780 785 seed(0) 781 782 def add_enhancer(self, enhancer): 783 self._enhancers.append(enhancer) 784 786 785 787 def position(self): 786 788 """Return the position of the revision tree widget""" … … 816 818 return 'url(#%s)' % self._arrows.create(color, head) 817 819 818 def create(self, re visions=None, branches=None, authors=None,820 def create(self, req, revisions=None, branches=None, authors=None, 819 821 hidetermbranch=False, style='compact'): 820 822 if revisions is not None: … … 845 847 self._vtimes[r] = vtime 846 848 vtime += 1 847 map(lambda e: e.create(), self._enhancers) 849 for enhancer in self.enhancers: 850 self._addons[enhancer] = \ 851 enhancer.create(self.env, req, self.repos, self) 848 852 849 853 def build(self): 850 854 """Build the graph""" 851 branches = [svgbr.branch() for svgbr in self._svgbranches.values()] 852 for enhancer in self._enhancers: 853 f_branches = enhancer.optimize(branches) 854 if f_branches: 855 # stop on the first enhancer 856 branches = f_branches 857 break 855 branches = self.optimizer.optimize(self.repos, \ 856 [svgbr.branch() for svgbr in self._svgbranches.values()]) 858 857 branch_xpos = UNIT 859 858 svgbranches = [self.svgbranch(branch=b) for b in branches] 860 859 for svgbranch in svgbranches: 861 860 svgbranch.build((branch_xpos, UNIT/6)) 862 branch_xpos += svgbranch.header().extent()[0] + UNIT 863 for enhancer in self._enhancers:864 enhancer.build()861 branch_xpos += svgbranch.header().extent()[0] + UNIT 862 map(lambda e: e.build(self._addons[e]), self.enhancers) 863 # FIXME: why not using svgbranches ? 865 864 svgbranches = self._svgbranches.values() 866 865 svgbranches.sort() … … 871 870 maxheight = h 872 871 if not svgbranches: 873 raise ChangesetEmptyRange872 raise EmptyRangeError 874 873 w = svgbranches[-1].position()[0] - svgbranches[0].position()[0] + \ 875 874 svgbranches[-1].extent()[0] + 2*UNIT … … 913 912 914 913 def __str__(self): 915 """ Renderthe revision tree as a SVG string"""914 """Dump the revision tree as a SVG string""" 916 915 import cStringIO 917 916 xml=cStringIO.StringIO() … … 926 925 927 926 def render(self, scale=1, width=None, height=None, linkparent=False): 927 """Render the revision tree""" 928 928 self._svg = SVG.svg((0,0,self._extent[0],self._extent[1]), 929 929 scale*self._extent[0], scale*self._extent[1]) 930 930 self._arrows.render() 931 map(lambda e: e.render(1), self._enhancers) 931 # FIXME: only two levels for enhancers (background, foreground) 932 map(lambda e: e.render(self._addons[e], 1), self.enhancers) 932 933 map(lambda b: b.render(), self._svgbranches.values()) 933 map(lambda e: e.render( 2), self._enhancers)934 map(lambda e: e.render(self._addons[e], 2), self.enhancers) 934 935 #dbgDump(self._svg) revtreeplugin/0.10/revtree/web_ui.py
r1675 r1694 17 17 import time 18 18 19 from revtree.api import EmptyRangeError, RevtreeSystem 20 from revtree.model import Repository 19 21 from trac.core import * 20 22 from trac.perm import IPermissionRequestor … … 27 29 from trac.wiki import wiki_to_html, WikiSystem 28 30 29 from revtree import Repository, SvgRevtree, ChangesetEmptyRange30 from revtree.enhancer import Enhancer31 31 32 32 class RevtreeStore(object): … … 134 134 135 135 def __getitem__(self, name): 136 """ getter (dictionary)"""136 """Getter (dictionary)""" 137 137 return self.values[name] 138 138 139 139 def __setitem__(self, name, value): 140 """ setter (dictionnary)"""140 """Setter (dictionnary)""" 141 141 self.values[name] = value 142 142 … … 147 147 implements(IPermissionRequestor, INavigationContributor, \ 148 148 IRequestHandler, ITemplateProvider) 149 149 150 150 PERIODS = { 1 : 'day', 2 : '2 days', 3 : '3 days', 7: 'week', 151 151 14 : 'fortnight', 31 : 'month', 61 : '2 months', … … 184 184 else: 185 185 return self._process_revtree(req) 186 186 187 # ITemplateProvider 188 189 def get_htdocs_dirs(self): 190 """Return the absolute path of a directory containing additional 191 static resources (such as images, style sheets, etc). 192 """ 193 from pkg_resources import resource_filename 194 return [('revtree', resource_filename(__name__, 'htdocs'))] 195 196 def get_templates_dirs(self): 197 """Return the absolute path of the directory containing the provided 198 ClearSilver templates. 199 """ 200 from pkg_resources import resource_filename 201 return [resource_filename(__name__, 'templates')] 202 203 # end of interface implementation 204 205 def __init__(self): 206 """Reads the configuration and run sanity checks""" 207 if self.config.get('trac', 'repository_type') != 'svn': 208 raise TracError, "Revtree only supports Subversion repositories" 209 bre = self.config.get('revtree', 'branch_re', 210 r'^(?P<branch>branches/[^/]+|trunk|data)' 211 r'(?:/(?P<path>.*))?$') 212 self.bcre = re.compile(bre) 213 self.trunks = self.env.config.get('revtree', 'trunks', 214 'trunk').split(' ') 215 self.scale = float(self.env.config.get('revtree', 'scale', '1')) 216 repos = self.env.get_repository() 217 self.oldest = int(self.env.config.get('revtree', 'revbase', 218 repos.get_oldest_rev())) 219 self.youngest = repos.get_youngest_rev() 220 if self.config.getbool('revtree', 'reltime', True): 221 self.timebase = repos.get_changeset(self.youngest).date 222 else: 223 self.timebase = None 224 self.style = self.config.get('revtree', 'style', 'compact') 225 if self.style not in [ 'compact', 'timeline']: 226 raise TracError, "Unsupported style: %s" % self.style 227 187 228 def _process_log(self, req): 188 229 """Handle AJAX log requests""" … … 230 271 try: 231 272 if not revstore.can_be_rendered(): 232 raise ChangesetEmptyRange273 raise EmptyRangeError 233 274 234 275 repos = Repository(self.env, req.authname) … … 239 280 filename = self._get_filename() 240 281 241 svgrevtree = SvgRevtree(self.env, repos, self.urlbase) 242 enhancer = Enhancer(repos, svgrevtree) 243 svgrevtree.add_enhancer(enhancer) 244 svgrevtree.create(revstore.revrange, revstore.get_branches(), 282 svgrevtree = RevtreeSystem(self.env).get_revtree(repos) 283 284 #enhancer = Enhancer(repos, svgrevtree) 285 #svgrevtree.add_enhancer(enhancer) 286 287 svgrevtree.create(req, revstore.revrange, revstore.get_branches(), 245 288 revstore.get_authors(), 246 289 revstore.get_hidetermbranch(), … … 266 309 revstore.save(req.session) 267 310 268 except ChangesetEmptyRange:311 except EmptyRangeError: 269 312 req.hdf['revtree.errormsg'] = "Selected filters cannot render" \ 270 313 " a revision tree" … … 290 333 add_stylesheet(req, 'revtree/css/revtree.css') 291 334 return 'revtree.cs', 'application/xhtml+xml' 292 293 # ITemplateProvider294 295 def get_htdocs_dirs(self):296 """Return the absolute path of a directory containing additional297 static resources (such as images, style sheets, etc).298 """299 from pkg_resources import resource_filename300 return [('revtree', resource_filename(__name__, 'htdocs'))]301 302 def get_templates_dirs(self):303 """Return the absolute path of the directory containing the provided304 ClearSilver templates.305 """306 from pkg_resources import resource_filename307 return [resource_filename(__name__, 'templates')]308 309 # end of interface implementation310 311 def __init__(self):312 if self.config.get('trac', 'repository_type') != 'svn':313 raise TracError, "Revtree only supports Subversion repositories"314 bre = self.config.get('revtree', 'branch_re',315 r'^(?P<branch>branches/[^/]+|trunk|data)'316 r'(?:/(?P<path>.*))?$')317 self.bcre = re.compile(bre)318 self.urlbase = self.config.get('trac', 'base_url')319 self.trunks = self.env.config.get('revtree', 'trunks',320 'trunk').split(' ')321 self.scale = float(self.env.config.get('revtree', 'scale', '1'))322 if not self.urlbase:323 raise TracError, "Base URL not defined"324 repos = self.env.get_repository()325 self.oldest = int(self.env.config.get('revtree', 'revbase',326 repos.get_oldest_rev()))327 self.youngest = repos.get_youngest_rev()328 if self.config.getbool('revtree', 'reltime', True):329 self.timebase = repos.get_changeset(self.youngest).date330 else:331 self.timebase = None332 self.style = self.config.get('revtree', 'style', 'compact')333 if self.style not in [ 'compact', 'timeline']:334 raise TracError, "Unsupported style: %s" % self.style335 335 336 336 def _get_periods(self): revtreeplugin/0.10/setup.py
r1669 r1694 16 16 17 17 PACKAGE = 'RevtreePlugin' 18 VERSION = '0.4. 2'18 VERSION = '0.4.3' 19 19 20 20 setup ( … … 39 39 'trac.plugins': [ 40 40 'revtree.web_ui = revtree.web_ui', 41 'revtree.enhancer = revtree.enhancer' 41 42 ] 42 43 }
