def process_request(self, req): from tractags.api import TagEngine from trac.web.chrome import add_stylesheet add_stylesheet(req, 'tags/css/tractags.css') pagename = req.args.get('page', 'WikiStart') action = req.args.get('action', 'view') engine = TagEngine(self.env) wikitags = engine.tagspace.wiki tags = list(wikitags.get_tags([pagename])) tags.sort() if action == 'edit': req.hdf['tags'] = req.args.get('tags', ', '.join(tags)) elif action == 'view': hdf_tags = [] for tag in tags: href, title = engine.get_tag_link(tag) hdf_tags.append({'name': tag, 'href': href, 'title': title}) req.hdf['tags'] = hdf_tags result = WikiModule.process_request(self, req) if result is None: return None if result[0] == 'wiki.cs': return 'tagswiki.cs', None return result
def set_password(self, user, password): import re if len(user) < 3: raise TracError("user name must be at least 3 characters long") if not re.match(r"^\w+$", user): raise TracError("user name must consist only of alpha-numeric characters") if user not in self.get_users(): from trac.wiki.model import WikiPage db = self.env.get_db_cnx() page = WikiPage(self.env, user, db=db) # User creation with existing page if page.exists: raise TracError('wiki page "%s" already exists' % user) else: from tractags.api import TagEngine tagspace = TagEngine(self.env).tagspace.wiki tagspace.add_tags(None, user, ["user"]) page.text = """= %(user)s =\n\n[[ListTagged(%(user)s)]]\n\n[[TagIt(user)]]""" % {"user": user} page.save(user, "New user %s registered" % user, None) self.env.log.debug("New user %s registered" % user) HtPasswdStore.set_password(self, user, password)
def screenshot_changed(self, screenshot, old_screenshot): # Add tags to screenshot. old_screenshot.update(screenshot) tags = TagEngine(self.env).tagspace.screenshots tag_names = self._get_tags(old_screenshot) tags.replace_tags(None, old_screenshot['id'], list(sets.Set(tag_names)))
def set_password(self, user, password): import re if len(user) < 3: raise TracError('user name must be at least 3 characters long') if not re.match(r'^\w+$', user): raise TracError( 'user name must consist only of alpha-numeric characters') if user not in self.get_users(): from trac.wiki.model import WikiPage db = self.env.get_db_cnx() page = WikiPage(self.env, user, db=db) # User creation with existing page if page.exists: raise TracError('wiki page "%s" already exists' % user) else: from tractags.api import TagEngine tagspace = TagEngine(self.env).tagspace.wiki tagspace.add_tags(None, user, ['user']) page.text = '''= %(user)s =\n\n[[ListTagged(%(user)s)]]\n\n[[TagIt(user)]]''' % { 'user': user } page.save(user, 'New user %s registered' % user, None) self.env.log.debug("New user %s registered" % user) HtPasswdStore.set_password(self, user, password)
def render_listtags(self, req, *tags, **kwargs): """ List tags. For backwards compatibility, can accept a list of tags. This will simply call ListTagged. Optional keyword arguments are tagspace=wiki, tagspaces=(wiki, ticket, ...) and shownames=true. """ if tags: # Backwards compatibility return self.render_listtagged(req, *tags, **kwargs) page = self._current_page(req) engine = TagEngine(self.env) showpages = kwargs.get('showpages', None) or kwargs.get('shownames', 'false') if 'tagspace' in kwargs: tagspaces = [kwargs['tagspace']] else: tagspaces = kwargs.get('tagspaces', []) or \ list(TagEngine(self.env).tagspaces) out = StringIO() out.write('<ul class="listtags">\n') tag_details = {} for tag, names in sorted(engine.get_tags(tagspaces=tagspaces, detailed=True).iteritems()): href, title = engine.get_tag_link(tag) htitle = wiki_to_oneliner(title, self.env) out.write('<li><a href="%s" title="%s">%s</a> %s <span class="tagcount">(%i)</span>' % (href, title, tag, htitle, len(names))) if showpages == 'true': out.write('\n') out.write(self.render_listtagged(req, tag, tagspaces=tagspaces)) out.write('</li>\n') out.write('</ul>\n') return out.getvalue()
def _page_titles(self, pages): """ Extract page titles, if possible. """ titles = {} tagspace = TagEngine(self.env).tagspace.wiki for pagename in pages: href, link, title = tagspace.name_details(pagename) titles[pagename] = title return titles
def _update_tags(self, req, page): newtags = set([t.strip() for t in _tag_split.split(req.args.get('tags')) if t.strip()]) wikitags = TagEngine(self.env).tagspace.wiki oldtags = wikitags.get_tags([page.name]) if oldtags != newtags: wikitags.replace_tags(req, page.name, newtags)
def render_listtagged(self, req, *tags, **kwargs): """ List tagged objects. Optionally accepts a list of tags to match against. The special tag '''. (dot)''' inserts the current Wiki page name. `[[ListTagged(<tag>, ...)]]` ||'''Argument'''||'''Description'''|| ||`tagspace=<tagspace>`||Specify the tagspace the macro should operate on.|| ||`tagspaces=(<tagspace>,...)`||Specify a set of tagspaces the macro should operate on.|| ||`operation=intersection|union`||The set operation to perform on the discovered objects.|| ||`showheadings=true|false`||List objects under the tagspace they occur in.|| """ if 'tagspace' in kwargs: tagspaces = [kwargs.get('tagspace', None)] else: tagspaces = kwargs.get('tagspaces', '') or \ list(TagEngine(self.env).tagspaces) showheadings = kwargs.get('showheadings', 'false') operation = kwargs.get('operation', 'intersection') if operation not in ('union', 'intersection'): raise TracError("Invalid tag set operation '%s'" % operation) engine = TagEngine(self.env) page_name = req.hdf.get('wiki.page_name') if page_name: tags = [tag == '.' and page_name or tag for tag in tags] taginfo = {} out = StringIO() out.write('<ul class="listtagged">') # Cull empty names tagged_names = [(tagspace, names) for tagspace, names in engine.get_tagged_names(tags=tags, tagspaces=tagspaces, operation=operation, detailed=True).iteritems() if names] for tagspace, tagspace_names in sorted(tagged_names): if showheadings == 'true': out.write('<lh>%s tags</lh>' % tagspace) for name, tags in sorted(tagspace_names.iteritems()): if tagspace == 'wiki' and unicode(name).startswith('tags/'): continue tags = sorted(tags) taginfo = self._tag_details(taginfo, tags) href, link, title = engine.name_details(tagspace, name) htitle = wiki_to_oneliner(title, self.env) name_tags = ['<a href="%s" title="%s">%s</a>' % (taginfo[tag][0], taginfo[tag][1], tag) for tag in tags] if not name_tags: name_tags = '' else: name_tags = ' (' + ', '.join(sorted(name_tags)) + ')' out.write('<li>%s %s%s</li>\n' % (link, htitle, name_tags)) out.write('</ul>') return out.getvalue()
def render_tagcloud(self, req, smallest=10, biggest=20, tagspace=None, tagspaces=[]): """ Display a summary of all tags, with the font size reflecting the number of pages the tag applies to. Font size ranges from 10 to 22 pixels, but this can be overridden by the smallest=n and biggest=n macro parameters. By default, all tagspaces are displayed, but this can be overridden with tagspaces=(wiki, ticket) or tagspace=wiki.""" smallest = int(smallest) biggest = int(biggest) engine = TagEngine(self.env) # Get wiki tagspace if tagspace: tagspaces = [tagspace] else: tagspaces = tagspaces or engine.tagspaces cloud = {} for tag, names in engine.get_tags(tagspaces=tagspaces, detailed=True).iteritems(): cloud[tag] = len(names) tags = cloud.keys() # No tags? if not tags: return '' # by_count maps tag counts to an index in the set of counts by_count = list(set(cloud.values())) by_count.sort() by_count = dict([(c, float(i)) for i, c in enumerate(by_count)]) taginfo = self._tag_details({}, tags) tags.sort() rlen = float(biggest - smallest) tlen = float(len(by_count)) scale = 1.0 if tlen: scale = rlen / tlen out = StringIO() out.write('<ul class="tagcloud">\n') last = tags[-1] for tag in tags: if tag == last: cls = ' class="last"' else: cls = '' out.write( '<li%s><a rel="tag" title="%s" style="font-size: %ipx" href="%s">%s</a> <span class="tagcount">(%i)</span></li>\n' % (cls, taginfo[tag][1], smallest + int(by_count[cloud[tag]] * scale), taginfo[tag][0], tag, cloud[tag])) out.write('</ul>\n') return out.getvalue()
def render_listtagged(self, req, *tags, **kwargs): """ List tagged objects. Takes a list of tags to match against. The special tag '.' inserts the current Wiki page name. Optional keyword arguments are tagspace=wiki, tagspaces=(wiki, title, ...) and showheadings=true. By default displays the intersection of objects matching each tag. By passing operation=union this can be modified to display the union of objects matching each tag. """ if 'tagspace' in kwargs: tagspaces = [kwargs.get('tagspace', None)] else: tagspaces = kwargs.get('tagspaces', '') or \ list(TagEngine(self.env).tagspaces) showheadings = kwargs.get('showheadings', 'false') operation = kwargs.get('operation', 'intersection') if operation not in ('union', 'intersection'): raise TracError("Invalid tag set operation '%s'" % operation) engine = TagEngine(self.env) page_name = req.hdf.get('wiki.page_name') if page_name: tags = [tag == '.' and page_name or tag for tag in tags] taginfo = {} out = StringIO() out.write('<ul class="listtagged">') for tagspace, tagspace_names in sorted( engine.get_tagged_names(tags=tags, tagspaces=tagspaces, operation=operation, detailed=True).iteritems()): if showheadings == 'true': out.write('<lh>%s tags</lh>' % tagspace) for name, tags in sorted(tagspace_names.iteritems()): if tagspace == 'wiki' and unicode(name).startswith('tags/'): continue tags = sorted(tags) taginfo = self._tag_details(taginfo, tags) href, link, title = engine.name_details(tagspace, name) htitle = wiki_to_oneliner(title, self.env) name_tags = [ '<a href="%s" title="%s">%s</a>' % (taginfo[tag][0], taginfo[tag][1], tag) for tag in tags ] if not name_tags: name_tags = '' else: name_tags = ' (' + ', '.join(sorted(name_tags)) + ')' out.write('<li>%s %s%s</li>\n' % (link, htitle, name_tags)) out.write('</ul>') return out.getvalue()
def _update_tags(self, req, page): newtags = set([ t.strip() for t in _tag_split.split(req.args.get('tags')) if t.strip() ]) wikitags = TagEngine(self.env).tagspace.wiki oldtags = wikitags.get_tags([page.name]) if oldtags != newtags: wikitags.replace_tags(req, page.name, newtags)
def render_tagcloud(self, req, smallest=10, biggest=20, tagspace=None, tagspaces=[]): """ Display a summary of all tags, with the font size reflecting the number of pages the tag applies to. Font size ranges from 10 to 22 pixels, but this can be overridden by the smallest=n and biggest=n macro parameters. By default, all tagspaces are displayed, but this can be overridden with tagspaces=(wiki, ticket) or tagspace=wiki.""" smallest = int(smallest) biggest = int(biggest) engine = TagEngine(self.env) # Get wiki tagspace if tagspace: tagspaces = [tagspace] else: tagspaces = tagspaces or engine.tagspaces cloud = {} for tag, names in engine.get_tags(tagspaces=tagspaces, detailed=True).iteritems(): cloud[tag] = len(names) tags = cloud.keys() # No tags? if not tags: return '' # by_count maps tag counts to an index in the set of counts by_count = list(set(cloud.values())) by_count.sort() by_count = dict([(c, float(i)) for i, c in enumerate(by_count)]) taginfo = self._tag_details({}, tags) tags.sort() rlen = float(biggest - smallest) tlen = float(len(by_count)) scale = 1.0 if tlen: scale = rlen / tlen out = StringIO() out.write('<ul class="tagcloud">\n') last = tags[-1] for tag in tags: if tag == last: cls = ' class="last"' else: cls = '' out.write('<li%s><a rel="tag" title="%s" style="font-size: %ipx" href="%s">%s</a> <span class="tagcount">(%i)</span></li>\n' % ( cls, taginfo[tag][1], smallest + int(by_count[cloud[tag]] * scale), taginfo[tag][0], tag, cloud[tag])) out.write('</ul>\n') return out.getvalue()
def setUp(self): self.env = EnvironmentStub(default_data=True) self.env.path = '/' self.tag_engine = TagEngine(self.env) self.tag_engine.upgrade_environment(self.env.get_db_cnx()) # Insert some test tickets from trac.ticket.model import Ticket for id in (1, 2, 3): ticket = Ticket(self.env) ticket['summary'] = 'Test ticket %i' % id ticket['description'] = 'Test ticket %i description' % id ticket.insert()
def getDetails(self, req, hack): """ Fetch hack details. Returns dict with name, dependencies and description. """ from tractags.api import TagEngine wikitags = TagEngine(self.env).tagspace.wiki tags = wikitags.get_tags(hack) types = self.getTypes() hacks = wikitags.get_tagged_names(types) dependencies = hacks.intersection(tags) href, htmllink, description = wikitags.name_details(hack) return {"name": hack, "dependencies": tuple(dependencies), "description": description}
def render_listtagged(self, req, *tags, **kwargs): """ List tagged objects. Takes a list of tags to match against. The special tag '.' inserts the current Wiki page name. Optional keyword arguments are tagspace=wiki, tagspaces=(wiki, title, ...) and showheadings=true. By default displays the intersection of objects matching each tag. By passing operation=union this can be modified to display the union of objects matching each tag. """ if 'tagspace' in kwargs: tagspaces = [kwargs.get('tagspace', None)] else: tagspaces = kwargs.get('tagspaces', '') or \ list(TagEngine(self.env).tagspaces) showheadings = kwargs.get('showheadings', 'false') operation = kwargs.get('operation', 'intersection') if operation not in ('union', 'intersection'): raise TracError("Invalid tag set operation '%s'" % operation) engine = TagEngine(self.env) page_name = req.hdf.get('wiki.page_name') if page_name: tags = [tag == '.' and page_name or tag for tag in tags] taginfo = {} out = StringIO() out.write('<ul class="listtagged">') for tagspace, tagspace_names in sorted(engine.get_tagged_names(tags=tags, tagspaces=tagspaces, operation=operation, detailed=True).iteritems()): if showheadings == 'true': out.write('<lh>%s tags</lh>' % tagspace) for name, tags in sorted(tagspace_names.iteritems()): if tagspace == 'wiki' and unicode(name).startswith('tags/'): continue tags = sorted(tags) taginfo = self._tag_details(taginfo, tags) href, link, title = engine.name_details(tagspace, name) htitle = wiki_to_oneliner(title, self.env) name_tags = ['<a href="%s" title="%s">%s</a>' % (taginfo[tag][0], taginfo[tag][1], tag) for tag in tags] if not name_tags: name_tags = '' else: name_tags = ' (' + ', '.join(sorted(name_tags)) + ')' out.write('<li>%s %s%s</li>\n' % (link, htitle, name_tags)) out.write('</ul>') return out.getvalue()
def _wiki_view(self, req, stream): tags = self._page_tags(req) if not tags: return stream engine = TagEngine(self.env) add_stylesheet(req, 'tags/css/tractags.css') li = [] for tag in tags: href, title = engine.get_tag_link(tag) li.append(T.li(T.a(title=title, href=href)(tag), ' ')) insert = T.ul(class_='tags')(T.lh('Tags'), li) return stream | Transformer('//div[@class="buttons"]').before(insert)
def download_deleted(self, download): tags = TagEngine(self.env).tagspace.downloads # Prepare tag names. self._resolve_ids(download) tag_names = [download['author'], download['component'], download['version'], download['architecture'], download['platform'], download['type']] if download['tags']: tag_names.extend(download['tags'].split(' ')) # Add tags to download. self.log.debug(tag_names) tags.remove_tags(None, download['id'], list(sets.Set(tag_names)))
def getNames(self, req, tagname): """ Returns all pages with tagname """ engine = TagEngine(self.env) try: tagspaces = list() tagspaces.append(self.tagsystem.tagspace) tags = list() tags.append (tagname) names = engine.get_tagged_names(tags=tags,tagspaces=tagspaces) self.env.log.debug("getNames found %s for tagname %s"%(names, tagname)) return list(names[self.tagsystem.tagspace]) except Exception, e: self.env.log.debug('Error in getNames(%s): %s\n' % (tagname, str(e))) return None
def download_deleted(self, download): tags = TagEngine(self.env).tagspace.downloads # Prepare tag names. self._resolve_ids(download) tag_names = [ download['author'], download['component'], download['version'], download['architecture'], download['platform'], download['type'] ] if download['tags']: tag_names.extend(download['tags'].split(' ')) # Add tags to download. self.log.debug(tag_names) tags.remove_tags(None, download['id'], list(sets.Set(tag_names)))
def getDetails(self, req, hack): """ Fetch hack details. Returns dict with name, dependencies and description. """ from tractags.api import TagEngine wikitags = TagEngine(self.env).tagspace.wiki tags = wikitags.get_tags(hack) types = self.getTypes() hacks = wikitags.get_tagged_names(types) dependencies = hacks.intersection(tags) href, htmllink, description = wikitags.name_details(hack) return { 'name': hack, 'dependencies': tuple(dependencies), 'description': description }
def _page_tags(self, req): pagename = req.args.get('page', 'WikiStart') engine = TagEngine(self.env) wikitags = engine.tagspace.wiki current_tags = list(wikitags.get_tags([pagename])) current_tags.sort() return current_tags
def _do_save(self, req, db, page): # This method is overridden so the user doesn't get "Page not modified" # exceptions when updating tags but not wiki content. from tractags.api import TagEngine if 'tags' in req.args: newtags = set([t.strip() for t in _tag_split.split(req.args.get('tags')) if t.strip()]) wikitags = TagEngine(self.env).tagspace.wiki oldtags = wikitags.get_tags([page.name]) if oldtags != newtags: wikitags.replace_tags(req, page.name, newtags) # No changes, just redirect if req.args.get('text') == page.text: req.redirect(self.env.href.wiki(page.name)) return return WikiModule._do_save(self, req, db, page)
def getHacks(self, req, release, type): """ Fetch a list of hacks for Trac release, of type. """ from trac.versioncontrol.api import Node from tractags.api import TagEngine repo = self.env.get_repository(req.authname) wikitags = TagEngine(self.env).tagspace.wiki repo_rev = repo.get_youngest_rev() releases = wikitags.get_tagged_names([release]) types = wikitags.get_tagged_names([type]) for plugin in releases.intersection(types): if plugin.startswith('tags/'): continue path = '%s/%s' % (plugin.lower(), release) rev = 0 if repo.has_node(str(path), repo_rev): node = repo.get_node(path) rev = node.rev yield (plugin, rev)
def _prepare_wiki(self, req): from tractags.api import TagEngine page = req.path_info[6:] or 'WikiStart' engine = TagEngine(self.env) wikitags = engine.tagspace.wiki tags = list(wikitags.get_tags(page)) tags.sort() action = req.args.get('action', 'view') if action == 'edit': req.hdf['tags'] = req.args.get('tags', ', '.join(tags)) elif action == 'view': hdf_tags = [] for tag in tags: href, title = engine.get_tag_link(tag) hdf_tags.append({'name': tag, 'href': href, 'title': title}) req.hdf['tags'] = hdf_tags
def render_listtags(self, req, *tags, **kwargs): """ List all tags. ||'''Argument'''||'''Description'''|| ||`tagspace=<tagspace>`||Specify the tagspace the macro should operate on.|| ||`tagspaces=(<tagspace>,...)`||Specify a set of tagspaces the macro should operate on.|| ||`shownames=true|false`||Whether to show the objects that tags appear on ''(long)''.|| """ if tags: # Backwards compatibility return self.render_listtagged(req, *tags, **kwargs) page = self._current_page(req) engine = TagEngine(self.env) showpages = kwargs.get('showpages', None) or kwargs.get( 'shownames', 'false') if 'tagspace' in kwargs: tagspaces = [kwargs['tagspace']] else: tagspaces = kwargs.get('tagspaces', []) or \ list(TagEngine(self.env).tagspaces) out = StringIO() out.write('<ul class="listtags">\n') tag_details = {} for tag, names in sorted( engine.get_tags(tagspaces=tagspaces, detailed=True).iteritems()): href, title = engine.get_tag_link(tag) htitle = wiki_to_oneliner(title, self.env, req=req) out.write( '<li><a href="%s" title="%s">%s</a> %s <span class="tagcount">(%i)</span>' % (href, title, tag, htitle, len(names))) if showpages == 'true': out.write('\n') out.write(self.render_listtagged(req, tag, tagspaces=tagspaces)) out.write('</li>\n') out.write('</ul>\n') return out.getvalue()
def _tag_formatter(self, formatter, ns, target, label): if '?' in target: target, args = target.split('?')[0:2] args = '?' + args else: args = '' href, title = TagEngine(self.env).get_tag_link(target) return Markup('<a href="%s%s" title="%s">%s</a>' % (href, args, title, label))
def getHacks(self, req, release, type): """ Fetch a list of hacks for Trac release, of type. """ from trac.versioncontrol.api import Node from tractags.api import TagEngine repo = self.env.get_repository(req.authname) wikitags = TagEngine(self.env).tagspace.wiki repo_rev = repo.get_youngest_rev() releases = wikitags.get_tagged_names([release]) types = wikitags.get_tagged_names([type]) for plugin in releases.intersection(types): if plugin.startswith("tags/"): continue path = "%s/%s" % (plugin.lower(), release) rev = 0 if repo.has_node(str(path), repo_rev): node = repo.get_node(path) rev = node.rev yield (plugin, rev)
def _format_tagged(self, target, label): if '?' in target: target, args = target.split('?')[0:2] args = '?' + args else: args = '' href, title = TagEngine(self.env).get_tag_link(target, is_expression=True) return Markup('<a href="%s%s" title="%s">%s</a>' % (href, args, title, label))
def render_listtags(self, req, *tags, **kwargs): """ List tags. For backwards compatibility, can accept a list of tags. This will simply call ListTagged. Optional keyword arguments are tagspace=wiki, tagspaces=(wiki, ticket, ...) and shownames=true. """ if tags: # Backwards compatibility return self.render_listtagged(req, *tags, **kwargs) page = self._current_page(req) engine = TagEngine(self.env) showpages = kwargs.get('showpages', None) or kwargs.get( 'shownames', 'false') if 'tagspace' in kwargs: tagspaces = [kwargs['tagspace']] else: tagspaces = kwargs.get('tagspaces', []) or \ list(TagEngine(self.env).tagspaces) out = StringIO() out.write('<ul class="listtags">\n') tag_details = {} for tag, names in sorted( engine.get_tags(tagspaces=tagspaces, detailed=True).iteritems()): href, title = engine.get_tag_link(tag) htitle = wiki_to_oneliner(title, self.env) out.write( '<li><a href="%s" title="%s">%s</a> %s <span class="tagcount">(%i)</span>' % (href, title, tag, htitle, len(names))) if showpages == 'true': out.write('\n') out.write(self.render_listtagged(req, tag, tagspaces=tagspaces)) out.write('</li>\n') out.write('</ul>\n') return out.getvalue()
def render_listtags(self, req, *tags, **kwargs): """ List all tags. ||'''Argument'''||'''Description'''|| ||`tagspace=<tagspace>`||Specify the tagspace the macro should operate on.|| ||`tagspaces=(<tagspace>,...)`||Specify a set of tagspaces the macro should operate on.|| ||`shownames=true|false`||Whether to show the objects that tags appear on ''(long)''.|| """ if tags: # Backwards compatibility return self.render_listtagged(req, *tags, **kwargs) page = self._current_page(req) engine = TagEngine(self.env) showpages = kwargs.get('showpages', None) or kwargs.get('shownames', 'false') if 'tagspace' in kwargs: tagspaces = [kwargs['tagspace']] else: tagspaces = kwargs.get('tagspaces', []) or \ list(TagEngine(self.env).tagspaces) out = StringIO() out.write('<ul class="listtags">\n') tag_details = {} for tag, names in sorted(engine.get_tags(tagspaces=tagspaces, detailed=True).iteritems()): href, title = engine.get_tag_link(tag) htitle = wiki_to_oneliner(title, self.env) out.write('<li><a href="%s" title="%s">%s</a> %s <span class="tagcount">(%i)</span>' % (href, title, tag, htitle, len(names))) if showpages == 'true': out.write('\n') out.write(self.render_listtagged(req, tag, tagspaces=tagspaces)) out.write('</li>\n') out.write('</ul>\n') return out.getvalue()
def _initialize_tags(self, args, kwargs): """ Instantiate a TagEngine and get the tagged names Returns a list of all the pages tagged with the tags specified in args. Also return a reference to the TagEngine instance """ self.tags = TagEngine(self.env).tagspace.wiki tlist = args if not tlist: tlist = get_env_val('default_tag', config=self.env.config, section='blog', convert=list_val, default='blog') self.log.debug('tlist: %s' % str(tlist)) union = get_env_val('union', kwargs=kwargs, default=False, convert=bool_val) if union: blog = self.tags.get_tagged_names(tlist, operation='union') else: blog = self.tags.get_tagged_names(tlist, operation='intersection') return (blog, tlist)
def setUp(self): self.env = EnvironmentStub(default_data=True) from trac.log import logger_factory self.env.log = logger_factory(logtype='syslog', logfile=None, level='DEBUG', logid='Trac', format=None) self.env.path = '/' self.wiki_tag_rpc_engine = WikiTagRPCSystem(self.env) self.ticket_tag_rpc_engine = TicketTagRPCSystem(self.env) self.tag_engine = TagEngine(self.env) self.tag_engine.upgrade_environment(self.env.get_db_cnx()) self.xml_rpc_system = XMLRPCSystem(self.env) # Insert some test tickets from trac.ticket.model import Ticket for id in (1, 2, 3): ticket = Ticket(self.env) ticket['summary'] = 'Test ticket %i' % id ticket['description'] = 'Test ticket %i description' % id ticket.insert()
def setUp(self): self.env = EnvironmentStub(default_data=True) from trac.log import logger_factory self.env.log =logger_factory(logtype='syslog', logfile=None, level='DEBUG', logid='Trac', format=None) self.env.path = '/' self.wiki_tag_rpc_engine = WikiTagRPCSystem(self.env) self.ticket_tag_rpc_engine = TicketTagRPCSystem(self.env) self.tag_engine = TagEngine(self.env) self.tag_engine.upgrade_environment(self.env.get_db_cnx()) self.xml_rpc_system = XMLRPCSystem(self.env) # Insert some test tickets from trac.ticket.model import Ticket for id in (1, 2, 3): ticket = Ticket(self.env) ticket['summary'] = 'Test ticket %i' % id ticket['description'] = 'Test ticket %i description' % id ticket.insert()
def execute(hdf, template, env): out = StringIO() errors = [] authname = hdf.getValue("trac.authname", "anonymous") if not template: raise TracError("No template page supplied") if authname == "anonymous": errors.append('You need to <a href="%s">register</a> then <a href="%s">login</a> in order to create a new hack.' % (hdf.getValue("trac.href.registration", ""), hdf.getValue("trac.href.login", ""))) db = env.get_db_cnx() cursor = db.cursor() # Fetch meta-data from tags META_TAGS = set() from tractags.api import TagEngine wikitags = TagEngine(env).tagspace.wiki for tag in wikitags.get_tagged_names(['metatag']): META_TAGS.update(wikitags.get_tagged_names([tag])) TYPES = wikitags.get_tagged_names(['type']) RELEASES = wikitags.get_tagged_names(['release']) page_name = hdf.getValue('args.name', '') if not page_name.lower().endswith(hdf.getValue('args.type', '')): page_name += hdf.getValue('args.type', '').title() page_title = hdf.getValue('args.title', '') page_description = hdf.getValue('args.description', '') page_example = hdf.getValue('args.example', '') page_type = hdf.getValue('args.type', 'plugin') page_tags = get_branch_values(hdf, 'args.tags') page_releases = get_branch_values(hdf, 'args.releases') page_preview = hdf.getValue('args.previewhack', '') page_create = hdf.getValue('args.createhack', '') def write_tags(out, tags, checked = (), name = "tags", type="checkbox"): count = 0 for tag in sorted(tags): if tag.startswith('tags/'): continue (linktext,title,desc) = getInfo(db,tag) link = env.href.wiki(tag) check = "" if tag in checked: check = " checked" out.write('<input type="%s" name="%s" value="%s"%s/> <a href="%s" title="%s">%s</a> \n' % (type, name, tag, check, link, title, tag)) count += 1 if count % 8 == 0: out.write("<br/>\n") return count # Validation if page_preview or page_create: try: fetch_page(cursor, page_name) except: pass else: errors.append("Page name %s already exists" % page_name) if not re.match('^([A-Z][a-z]+){2,}$', page_name): errors.append('Invalid WikiName, only alpha characters are accepted and must be CamelCase') if not page_name: errors.append("No WikiName provided") if not page_title: errors.append("No page title provided") if not page_type: errors.append('No page type selected') if not page_description: errors.append("No description provided") if not page_example: errors.append("No example provided") if not page_releases: errors.append("No releases selected") if page_create and not errors: import subprocess repos_dir = env.config.get('trac', 'repository_dir') cursor.execute("SELECT name FROM component WHERE name=%s", (page_name,)) row = cursor.fetchone() if row: errors.append("Component '%s' already exists" % page_name) if subprocess.call(["svn", "ls", "%s/%s" % (SVN_LOCAL_PATH, page_name.lower())]) == 0: errors.append("Repository path '%s' already exists" % page_name.lower()) if not os.access(SVN_PERMISSIONS, os.W_OK): errors.append("Can't write to Subversion permissions file") lockfile = open("/var/tmp/newhack.lock", "w") try: rv = fcntl.flock(lockfile, fcntl.LOCK_EX | fcntl.LOCK_NB) if rv: errors.append('Failed to acquire lock, received error code %i' % rv) except IOError: errors.append('A hack is currently being created by another user. Try again later.') if not errors: try: # Insert component cursor.execute('INSERT INTO component (name, owner) VALUES (%s, %s)', (page_name, authname)) # Create page page = WikiPage(env, page_name, db = db) page.text = expand_vars(fetch_page(cursor, template), generate_vars(hdf)) out.write('Created wiki page.<br>\n') # Creating SVN paths paths = ['%s/%s' % (SVN_LOCAL_PATH, page_name.lower())] for release in page_releases: paths.append("%s/%s/%s" % (SVN_LOCAL_PATH, page_name.lower(), release)) output = os.popen('/usr/bin/op create-hack %s "New hack %s, created by %s" %s 2>&1' % (authname, page_name, authname, ' '.join(paths))).readlines() if output: raise Exception("Failed to create Subversion paths:\n%s" % ''.join(output)) out.write("Created SVN layout.<br>\n") # Add SVN permissions perms = open(SVN_PERMISSIONS, "a") perms.write("\n[/%s]\n%s = rw\n" % (page_name.lower(), authname)) out.write('Added SVN write permission.<br>\n') out.write('\nFinished.<p><h1>Hack Details</h1>\n') for release in page_releases: svnpath = "%s%s/%s" % (SVN_URL, page_name.lower(), release) out.write('The Subversion repository path for %s is <a href="%s">%s</a>.<br>\n' % (release, svnpath, svnpath)) out.write('The page for your hack is <a href="%s">%s</a>.<br>\n' % (env.href.wiki(page_name), page_name)) page.save(authname, 'New hack %s, created by %s' % (page_name, authname), None) db.commit() # Add tags env.log.debug(vars) tags = [page_type, authname] + page_tags + page_releases env.log.debug(tags) wikitags.replace_tags(None, page_name, tags) # Finish up rv = fcntl.flock(lockfile, fcntl.LOCK_UN) return out.getvalue() except Exception, e: # TODO Roll back changes to SVN_PERMISSIONS file db.rollback() rv = fcntl.flock(lockfile, fcntl.LOCK_UN) env.log.error(e, exc_info=True) raise TracError(str(e))
class TagsXmlRpcTestCase(unittest.TestCase): test_data = (('wiki', 'WikiStart', ('foo', 'bar')), ('wiki', 'SandBox', ('bar', 'war')), ('ticket', 1, ('war', 'death')), ('ticket', 2, ('death', 'destruction')), ('ticket', 3, ('foo', 'bar', 'destruction')) ) perm=Mock(assert_permission=lambda x: True,has_permission=lambda x: True) req = Mock(perm=perm, authname='anonymous') def _populate_tags(self,ts): for tagspace, target, tags in self.test_data: tagspace = ts.tagspace(tagspace) tagspace.add_tags(self.req, target, tags) yield tagspace, target, tags def setUp(self): self.env = EnvironmentStub(default_data=True) from trac.log import logger_factory self.env.log =logger_factory(logtype='syslog', logfile=None, level='DEBUG', logid='Trac', format=None) self.env.path = '/' self.wiki_tag_rpc_engine = WikiTagRPCSystem(self.env) self.ticket_tag_rpc_engine = TicketTagRPCSystem(self.env) self.tag_engine = TagEngine(self.env) self.tag_engine.upgrade_environment(self.env.get_db_cnx()) self.xml_rpc_system = XMLRPCSystem(self.env) # Insert some test tickets from trac.ticket.model import Ticket for id in (1, 2, 3): ticket = Ticket(self.env) ticket['summary'] = 'Test ticket %i' % id ticket['description'] = 'Test ticket %i description' % id ticket.insert() def test_insert(self): ts = self.tag_engine.tagspace for tagspace, target, tags in self._populate_tags(ts): found_tags = tagspace.get_tags([target]) self.assertEqual(found_tags, set(tags)) def test_wiki_tagspace(self): self.assertEqual(self.wiki_tag_rpc_engine.namespace, 'wiki') def test_ticket_tagspace(self): self.assertEqual(self.ticket_tag_rpc_engine.namespace, 'ticket') def test_wiki_xmlrpc_methods(self): self.failUnless(set(self.wiki_tag_rpc_engine.xmlrpc_methods()) != None, "No xmlrpc methods for wiki namespace") def test_ticket_xmlrpc_methods(self): self.failUnless(set(self.ticket_tag_rpc_engine.xmlrpc_methods()) != None, "No xmlrpc methods for ticket namespace") def test_wiki_xmlrpc_namespace(self): self.assertEqual(self.wiki_tag_rpc_engine.xmlrpc_namespace(), "tags.wiki") def test_ticket_xmlrpc_namespace(self): self.assertEqual(self.ticket_tag_rpc_engine.xmlrpc_namespace(), "tags.ticket") def test_wiki_get_name_tags (self): wiki_start_tags = self.wiki_tag_rpc_engine.getTags(self.req,"WikiStart") self.failUnless( wiki_start_tags!= None, "Can not find any tags for mock page WikiStart") def test_xmlrpc_listMethods(self): for method in self.xml_rpc_system.all_methods(self.req): namespace = method.namespace.replace('.', '_') namespaces = {} if namespace not in namespaces: namespaces[namespace] = { 'description' : wiki_to_oneliner(method.namespace_description, self.env), 'methods' : [], 'namespace' : method.namespace, } try: namespaces[namespace]['methods'].append((method.signature, wiki_to_oneliner(method.description, self.env), method.permission)) except Exception, e: self.fail('%s: %s\n' % (method.name, str(e)))
def wiki_page_deleted(self, page): # No point having tags on a non-existent page. self.env.log.debug("Removing all tags from 'wiki:%s'" % page.name) TagEngine(self.env).tagspace.wiki.remove_all_tags(None, page.name)
def screenshot_deleted(self, screenshot): # Add tags to screenshot. tags = TagEngine(self.env).tagspace.screenshots tag_names = self._get_tags(screenshot) tags.remove_tags(None, screenshot['id'], list(sets.Set(tag_names)))
def render_tagcloud(self, req, smallest=10, biggest=20, showcount=True, tagspace=None, mincount=1, tagspaces=[]): """ This macro displays a [http://en.wikipedia.org/wiki/Tag_cloud tag cloud] (weighted list) of all tags. ||'''Argument'''||'''Description'''|| ||`tagspace=<tagspace>`||Specify the tagspace the macro should operate on.|| ||`tagspaces=(<tagspace>,...)`||Specify a set of tagspaces the macro should operate on.|| ||`smallest=<n>`||The lower bound of the font size for the tag cloud.|| ||`biggest=<n>`||The upper bound of the font size for the tag cloud.|| ||`showcount=true|false`||Show the count of objects for each tag?|| ||`mincount=<n>`||Hide tags with a count less than `<n>`.|| """ smallest = int(smallest) biggest = int(biggest) mincount = int(mincount) engine = TagEngine(self.env) # Get wiki tagspace if tagspace: tagspaces = [tagspace] else: tagspaces = tagspaces or engine.tagspaces cloud = {} for tag, names in engine.get_tags(tagspaces=tagspaces, detailed=True).iteritems(): count = len(names) if count >= mincount: cloud[tag] = len(names) tags = cloud.keys() # No tags? if not tags: return '' # by_count maps tag counts to an index in the set of counts by_count = list(set(cloud.values())) by_count.sort() by_count = dict([(c, float(i)) for i, c in enumerate(by_count)]) taginfo = self._tag_details({}, tags) tags.sort() rlen = float(biggest - smallest) tlen = float(len(by_count)) scale = 1.0 if tlen: scale = rlen / tlen out = StringIO() out.write('<ul class="tagcloud">\n') last = tags[-1] for tag in tags: if tag == last: cls = ' class="last"' else: cls = '' if showcount != 'false': count = ' <span class="tagcount">(%i)</span>' % cloud[tag] else: count = '' out.write('<li%s><a rel="tag" title="%s" style="font-size: %ipx" href="%s">%s</a>%s</li>\n' % ( cls, taginfo[tag][1] + ' (%i)' % cloud[tag], smallest + int(by_count[cloud[tag]] * scale), taginfo[tag][0], tag, count)) out.write('</ul>\n') return out.getvalue()
def render_listtagged(self, req, *tags, **kwargs): """ List tagged objects. Optionally accepts a list of tags to match against. The special tag '''. (dot)''' inserts the current Wiki page name. `[[ListTagged(<tag>, ...)]]` ||'''Argument'''||'''Description'''|| ||`tagspace=<tagspace>`||Specify the tagspace the macro should operate on.|| ||`tagspaces=(<tagspace>,...)`||Specify a set of tagspaces the macro should operate on.|| ||`operation=intersection|union`||The set operation to perform on the discovered objects.|| ||`showheadings=true|false`||List objects under the tagspace they occur in.|| ||`expression=<expr>`||Match object tags against the given expression.|| The supported expression operators are: unary - (not); binary +, - and | (and, and not, or). All values in the expression are treated as tags. Any tag not in the same form as a Python variable must be quoted. eg. Match all objects tagged with ticket and workflow, and not tagged with wiki or closed. (ticket+workflow)-(wiki|closed) If an expression is provided operation is ignored. """ if 'tagspace' in kwargs: tagspaces = [kwargs.get('tagspace', None)] else: tagspaces = kwargs.get('tagspaces', '') or \ list(TagEngine(self.env).tagspaces) expression = kwargs.get('expression', None) showheadings = kwargs.get('showheadings', 'false') operation = kwargs.get('operation', 'intersection') if operation not in ('union', 'intersection'): raise TracError("Invalid tag set operation '%s'" % operation) engine = TagEngine(self.env) page_name = req.hdf.get('wiki.page_name') if page_name: tags = [tag == '.' and page_name or tag for tag in tags] tags = set(tags) taginfo = {} out = StringIO() out.write('<ul class="listtagged">\n') # If expression was passed as an argument, do a full walk, using the # expression as the predicate. Silently assumes that failed expressions # are normal tags. if expression: from tractags.expr import Expression try: expr = Expression(expression) except Exception, e: self.env.log.error("Invalid expression '%s'" % expression, exc_info=True) tags.update([x.strip() for x in re.split('[+,]', expression) if x.strip()]) expression = None else: self.env.log.debug(expr.ast) tagged_names = {} tags.update(expr.get_tags()) for tagspace, name, name_tags in engine.walk_tagged_names(tags=tags, tagspaces=tagspaces, predicate=lambda ts, n, t: expr(t)): tagged_names.setdefault(tagspace, {})[name] = name_tags tagged_names = [(tagspace, names) for tagspace, names in tagged_names.iteritems()]
def _do_actions(self, req, cursor, modes, component, version): for mode in modes: if mode == "get-file": req.perm.assert_permission("SCREENSHOTS_VIEW") # Get screenshot match = re.match(r"""^/screenshots/(\d+)/(small|medium|large)$""", req.path_info) if match: id = match.group(1) size = match.group(2) screenshot = self.api.get_screenshot(cursor, id) # Return screenshots image action. file = screenshot["%s_file" % (size,)] path = os.path.join(self.path, str(screenshot["id"]), file) self.log.debug("file: %s" % (file,)) self.log.debug("path: %s" % (path,)) type = mimetypes.guess_type(path)[0] req.send_file(path, type) elif mode == "add": req.perm.assert_permission("SCREENSHOTS_ADMIN") elif mode == "post-add": req.perm.assert_permission("SCREENSHOTS_ADMIN") # Get form values. new_name = Markup(req.args.get("name")) new_description = Markup(req.args.get("description")) new_author = req.authname file, filename = self._get_file_from_req(req) content = file.read() new_tags = req.args.get("tags") new_components = req.args.get("components") if not isinstance(new_components, list): new_components = [new_components] new_versions = req.args.get("versions") if not isinstance(new_versions, list): new_versions = [new_versions] # Check form values if not new_components or not new_versions: raise TracError("You must select at least one component" " and version.") # Check correct file type. reg = re.compile(r"^(.*)[.](.*)$") result = reg.match(filename) if result: if not result.group(2).lower() in self.ext.split(" "): raise TracError("Unsupported uploaded file type.") else: raise TracError("Unsupported uploaded file type.") # Prepare images filenames. large_filename = re.sub(reg, r"\1_large.\2", filename) medium_filename = re.sub(reg, r"\1_medium.\2", filename) small_filename = re.sub(reg, r"\1_small.\2", filename) # Add new screenshot. screenshot_time = int(time.time()) self.api.add_screenshot( cursor, new_name, new_description, screenshot_time, new_author, new_tags, large_filename, medium_filename, small_filename, ) # Get inserted screenshot. screenshot = self.api.get_screenshot_by_time(cursor, screenshot_time) self.id = screenshot["id"] # Add components and versions to screenshot. for new_component in new_components: self.api.add_component(cursor, screenshot["id"], new_component) for new_version in new_versions: self.api.add_version(cursor, screenshot["id"], new_version) # Create screenshot tags. if is_tags: tags = TagEngine(self.env).tagspace.screenshots tag_names = new_components tag_names.extend(new_versions) tag_names.extend([screenshot["name"], screenshot["author"]]) if screenshot["tags"]: tag_names.extend(screenshot["tags"].split(" ")) tags.replace_tags(req, screenshot["id"], tag_names) # Prepare file paths path = os.path.join(self.path, str(self.id)) large_filepath = os.path.join(path, large_filename) medium_filepath = os.path.join(path, medium_filename) small_filepath = os.path.join(path, small_filename) self.log.debug("large_filepath: %s" % (large_filepath,)) self.log.debug("medium_filepath: %s" % (medium_filepath,)) self.log.debug("small_filepath: %s" % (small_filepath,)) # Store uploaded image. try: os.mkdir(path) out_file = open(large_filepath, "w+") out_file.write(content) out_file.close() os.chdir(path) os.system('convert "%s" -resize 400!x300! "%s"' % (large_filename, medium_filename)) os.system('convert "%s" -resize 120!x90! "%s"' % (large_filename, small_filename)) except: raise TracError("Error storing file.") elif mode == "edit": req.perm.assert_permission("SCREENSHOTS_ADMIN") elif mode == "post-edit": req.perm.assert_permission("SCREENSHOTS_ADMIN") # Get form values. new_name = Markup(req.args.get("name")) new_description = Markup(req.args.get("description")) new_components = req.args.get("components") if not isinstance(new_components, list): new_components = [new_components] new_versions = req.args.get("versions") if not isinstance(new_versions, list): new_versions = [new_versions] new_tags = req.args.get("tags") # Check form values if not new_components or not new_versions: raise TracError("You must select at least one component" " and version.") # Get old screenshot screenshot = self.api.get_screenshot(cursor, self.id) # Update screenshot tags. if is_tags: tags = TagEngine(self.env).tagspace.screenshots tag_names = new_components tag_names.extend(new_versions) tag_names.extend([new_name, screenshot["author"]]) if new_tags: tag_names.extend(new_tags.split(" ")) tags.replace_tags(req, screenshot["id"], tag_names) # Edit screenshot. self.api.edit_screenshot( cursor, screenshot["id"], new_name, new_description, new_tags, new_components, new_versions ) elif mode == "delete": req.perm.assert_permission("SCREENSHOTS_ADMIN") # Get screenshots screenshots = self.api.get_screenshots(cursor, component["name"], version["name"]) index = self._get_screenshot_index(screenshots, self.id) or 0 screenshot = self.api.get_screenshot(cursor, self.id) # Delete screenshot. try: self.api.delete_screenshot(cursor, self.id) path = os.path.join(self.path, str(self.id)) os.remove(os.path.join(path, screenshot["large_file"])) os.remove(os.path.join(path, screenshot["medium_file"])) os.remove(os.path.join(path, screenshot["small_file"])) os.rmdir(path) except: pass # Delete screenshot tags. if is_tags: tags = TagEngine(self.env).tagspace.screenshots tag_names = screenshot["components"] tag_names.extend(screenshot["versions"]) tag_names.extend([screenshot["name"], screenshot["author"]]) if screenshot["tags"]: tag_names.extend(screenshot["tags"].split(" ")) tags.remove_tags(req, screenshot["id"], tag_names) # Set new screenshot id. if index > 1: self.id = screenshots[index - 1]["id"] else: self.id = screenshots[0]["id"] elif mode == "display": req.perm.assert_permission("SCREENSHOTS_VIEW") # Get screenshots of selected version and component. screenshots = self.api.get_screenshots(cursor, component["name"], version["name"]) index = self._get_screenshot_index(screenshots, self.id) or 0 # Prepare displayed screenshots. lenght = len(screenshots) previous = [] current = [] next = [] if lenght > 0: current.append(screenshots[index]) if (index + 1) < lenght: next.append(screenshots[index + 1]) else: next.append(no_screenshot) if (index + 2) < lenght: next.append(screenshots[index + 2]) else: next.append(no_screenshot) if (index - 1) > 0: previous.append(screenshots[index - 2]) else: previous.append(no_screenshot) if (index) > 0: previous.append(screenshots[index - 1]) else: previous.append(no_screenshot) # Fill HDF structure req.hdf["screenshots.index"] = index + 1 req.hdf["screenshots.count"] = len(screenshots) req.hdf["screenshots.previous"] = previous req.hdf["screenshots.current"] = current req.hdf["screenshots.next"] = next return "screenshots.cs", None elif mode == "add-display": req.perm.assert_permission("SCREENSHOTS_ADMIN") # Get screenshot screenshot = self.api.get_screenshot(cursor, self.id) self.log.debug("screenshot: %s" % (screenshot,)) # Fill HDF structure req.hdf["screenshots.current"] = [screenshot] return "screenshot-add.cs", None
def render_macro(self, req, name, content): from StringIO import StringIO from trac.wiki import wiki_to_html from trac.wiki.model import WikiPage from trac.util import Markup from tractags.api import TagEngine import re tagspace = TagEngine(self.env).tagspace.wiki out = StringIO() pages = tagspace.get_tagged_names(tags=["type"]) pages = sorted(pages) out.write('<form style="text-align: right; padding-top: 1em; margin-right: 5em;" method="get">') out.write('<span style="font-size: xx-small">') out.write("Show hacks for releases: ") releases = natsorted(tagspace.get_tagged_names(tags=["release"])) if "update_th_filter" in req.args: show_releases = req.args.get("release", ["0.12"]) if isinstance(show_releases, basestring): show_releases = [show_releases] req.session["th_release_filter"] = ",".join(show_releases) else: show_releases = req.session.get("th_release_filter", "0.12").split(",") for version in releases: checked = version in show_releases out.write( '<input type="checkbox" name="release" value="%s"%s>%s\n' % (version, checked and " checked" or "", version) ) out.write( '<input name="update_th_filter" type="submit" style="font-size: xx-small; padding: 0; border: solid 1px black" value="Update"/>' ) out.write("</span>") out.write("</form>") for i, pagename in enumerate(pages): page = WikiPage(self.env, pagename) if page.text: topmargin = "0em" if i < len(pages) - 1: bottommargin = "0em" else: bottommargin = "2em" out.write( '<fieldset style="padding: 1em; margin: %s 5em %s 5em; border: 1px solid #999;">\n' % (topmargin, bottommargin) ) body = page.text title = re.search("=+\s([^=]*)=+", body) if title: title = title.group(1).strip() body = re.sub("=+\s([^=]*)=+", "", body, 1) else: title = pagename body = re.sub("\\[\\[TagIt.*", "", body) out.write( '<legend style="color: #999;"><a href="%s">%s</a></legend>\n' % (self.env.href.wiki(pagename), title) ) body = wiki_to_html(body, self.env, req) # Dear God, the horror! for line in body.splitlines(): show = False for release in show_releases: self.env.log.debug(release) if ">%s</a>" % release in line: show = True break if show or not "<li>" in line: out.write(line) out.write("</fieldset>\n") return out.getvalue()
class TagsXmlRpcTestCase(unittest.TestCase): test_data = (('wiki', 'WikiStart', ('foo', 'bar')), ('wiki', 'SandBox', ('bar', 'war')), ('ticket', 1, ('war', 'death')), ('ticket', 2, ('death', 'destruction')), ('ticket', 3, ('foo', 'bar', 'destruction'))) perm = Mock(assert_permission=lambda x: True, has_permission=lambda x: True) req = Mock(perm=perm, authname='anonymous') def _populate_tags(self, ts): for tagspace, target, tags in self.test_data: tagspace = ts.tagspace(tagspace) tagspace.add_tags(self.req, target, tags) yield tagspace, target, tags def setUp(self): self.env = EnvironmentStub(default_data=True) from trac.log import logger_factory self.env.log = logger_factory(logtype='syslog', logfile=None, level='DEBUG', logid='Trac', format=None) self.env.path = '/' self.wiki_tag_rpc_engine = WikiTagRPCSystem(self.env) self.ticket_tag_rpc_engine = TicketTagRPCSystem(self.env) self.tag_engine = TagEngine(self.env) self.tag_engine.upgrade_environment(self.env.get_db_cnx()) self.xml_rpc_system = XMLRPCSystem(self.env) # Insert some test tickets from trac.ticket.model import Ticket for id in (1, 2, 3): ticket = Ticket(self.env) ticket['summary'] = 'Test ticket %i' % id ticket['description'] = 'Test ticket %i description' % id ticket.insert() def test_insert(self): ts = self.tag_engine.tagspace for tagspace, target, tags in self._populate_tags(ts): found_tags = tagspace.get_tags([target]) self.assertEqual(found_tags, set(tags)) def test_wiki_tagspace(self): self.assertEqual(self.wiki_tag_rpc_engine.namespace, 'wiki') def test_ticket_tagspace(self): self.assertEqual(self.ticket_tag_rpc_engine.namespace, 'ticket') def test_wiki_xmlrpc_methods(self): self.failUnless( set(self.wiki_tag_rpc_engine.xmlrpc_methods()) != None, "No xmlrpc methods for wiki namespace") def test_ticket_xmlrpc_methods(self): self.failUnless( set(self.ticket_tag_rpc_engine.xmlrpc_methods()) != None, "No xmlrpc methods for ticket namespace") def test_wiki_xmlrpc_namespace(self): self.assertEqual(self.wiki_tag_rpc_engine.xmlrpc_namespace(), "tags.wiki") def test_ticket_xmlrpc_namespace(self): self.assertEqual(self.ticket_tag_rpc_engine.xmlrpc_namespace(), "tags.ticket") def test_wiki_get_name_tags(self): wiki_start_tags = self.wiki_tag_rpc_engine.getTags( self.req, "WikiStart") self.failUnless(wiki_start_tags != None, "Can not find any tags for mock page WikiStart") def test_xmlrpc_listMethods(self): for method in self.xml_rpc_system.all_methods(self.req): namespace = method.namespace.replace('.', '_') namespaces = {} if namespace not in namespaces: namespaces[namespace] = { 'description': wiki_to_oneliner(method.namespace_description, self.env), 'methods': [], 'namespace': method.namespace, } try: namespaces[namespace]['methods'].append( (method.signature, wiki_to_oneliner(method.description, self.env), method.permission)) except Exception, e: self.fail('%s: %s\n' % (method.name, str(e)))
def wiki_page_deleted(self, page): # No point having tags on a non-existent page. self.env.log.debug("Removing all tags from 'wiki:%s'" % page.name) engine = TagEngine(self.env) engine.tagspace.wiki.remove_all_tags(None, page.name) engine.flush_link_cache(page)
def _new_blog_post(self, req): """ Generate a new blog post """ action = req.args.get('action', 'edit') pg_name_fmt = self.env.config.get('blog', 'page_format', '%Y/%m/%d/%H.%M') wikitext = req.args.get('text', '') blogtitle = req.args.get('blogtitle', '') pagename = req.args.get('pagename', pg_name_fmt) pagename = time.strftime(pagename) if '%@' in pagename and blogtitle: urltitle = re.sub(r'[^\w]+', '-', blogtitle).lower() pagename = pagename.replace('%@', urltitle) while '-' in pagename and len(pagename) > 60: pagename = '-'.join(pagename.split('-')[:-1]) pagename = pagename.strip('-') if '$U' in pagename: pagename = pagename.replace('$U', req.authname) comment = req.args.get('comment', '') readonly = int(req.args.has_key('readonly')) edit_rows = int(req.args.get('edite_rows', 20)) req_tags = req.args.get('tags', []) if req.method == 'POST': if action == 'edit': if req.args.has_key('cancel'): req.redirect(self.env.href.blog()) page = WikiPage(self.env, pagename, None) tags = TagEngine(self.env).tagspace.wiki if req.args.has_key('preview'): req.hdf['blog.action'] = 'preview' self._render_editor(req, page, self.env.get_db_cnx(), preview=True) else: titleline = ' '.join(["=", blogtitle, "=\n"]) if blogtitle: page.text = ''.join([titleline, wikitext]) else: page.text = wikitext page.readonly = readonly page.save(req.authname, comment, req.remote_addr) # taglist = [x.strip() for x in req_tags.split(',') if x] taglist = [t.strip() for t in _tag_split.split(req.args.get('tags')) if t.strip()] tags.add_tags(req, pagename, taglist) req.redirect(self.env.href.blog()) else: info = { 'title' : blogtitle, 'pagename': pagename, 'page_source': wikitext, 'comment': comment, 'readonly': readonly, 'edit_rows': edit_rows, 'scroll_bar_pos': req.args.get('scroll_bar_pos', '') } req.hdf['blog'] = info req.hdf['title'] = 'New Blog Entry' tlist = req.args.getlist('tag') if not tlist: tlist = [self.env.config.get('blog', 'default_tag', 'blog')] req.hdf['tags'] = ', '.join(tlist) pass
class TagApiTestCase(unittest.TestCase): test_data = (('wiki', 'WikiStart', ('foo', 'bar')), ('wiki', 'SandBox', ('bar', 'war')), ('ticket', 1, ('war', 'death')), ('ticket', 2, ('death', 'destruction')), ('ticket', 3, ('foo', 'bar', 'destruction')) ) req = Mock(perm=Mock(assert_permission=lambda x: True), authname='anonymous') def _populate_tags(self, ts): for tagspace, target, tags in self.test_data: tagspace = ts.tagspace(tagspace) tagspace.add_tags(self.req, target, tags) yield tagspace, target, tags def setUp(self): self.env = EnvironmentStub(default_data=True) self.env.path = '/' self.tag_engine = TagEngine(self.env) self.tag_engine.upgrade_environment(self.env.get_db_cnx()) # Insert some test tickets from trac.ticket.model import Ticket for id in (1, 2, 3): ticket = Ticket(self.env) ticket['summary'] = 'Test ticket %i' % id ticket['description'] = 'Test ticket %i description' % id ticket.insert() def test_tagspaces(self): tagspaces = set(self.tag_engine.tagspaces) self.assertEqual(tagspaces, set(('ticket', 'wiki'))) def test_insert(self): ts = self.tag_engine.tagspace for tagspace, target, tags in self._populate_tags(ts): found_tags = tagspace.get_tags([target]) self.assertEqual(found_tags, set(tags)) def test_remove(self): ts = self.tag_engine.tagspace for tagspace, target, tags in self._populate_tags(ts): target_tags = tagspace.get_name_tags(target) tag = iter(target_tags).next() tagspace.remove_tags(self.req, target, (tag,)) target_tags.discard(tag) self.assertEqual(tagspace.get_name_tags(target), target_tags) def test_remove_all(self): ts = self.tag_engine.tagspace for tagspace, target, tags in self._populate_tags(ts): tagspace.remove_all_tags(self.req, target) def test_replace(self): ts = self.tag_engine.tagspace test_set = set(('foozle', 'stick')) for tagspace, target, tags in self._populate_tags(ts): found_tags = tagspace.get_tags([target]) tagspace.replace_tags(self.req, target, test_set) self.assertEqual(test_set, tagspace.get_name_tags(target)) def test_add(self): ts = self.tag_engine.tagspace test_set = set(('foozle', 'stick')) for tagspace, target, tags in self._populate_tags(ts): found_tags = tagspace.get_tags([target]) tagspace.add_tags(self.req, target, test_set) self.assertEqual(test_set.union(found_tags), tagspace.get_name_tags(target)) def test_walk(self): engine = self.tag_engine compare_data = {} for tagspace, target, tags in self._populate_tags(engine.tagspace): compare_data.setdefault(tagspace.tagspace, {})[target] = set(tags) tag_data = {} for tagspace, name, tags in engine.walk_tagged_names(): tag_data.setdefault(tagspace, {})[name] = tags self.assertEqual(compare_data, tag_data) def test_get_tagged_union(self): ts = self.tag_engine.tagspace for tagspace, target, tags in self._populate_tags(ts): pass self.assertEqual(self.tag_engine.get_tagged_names(tags=('foo', 'bar'), operation='union'), {'wiki': set([u'WikiStart', u'SandBox']), 'ticket': set([3])}) def test_get_tagged_intersection(self): ts = self.tag_engine.tagspace for tagspace, target, tags in self._populate_tags(ts): pass self.assertEqual(self.tag_engine.get_tagged_names(tags=('foo', 'bar'), operation='intersection'), {'wiki': set(['WikiStart']), 'ticket': set([3])}) def test_get_tags_union(self): ts = self.tag_engine.tagspace for tagspace, target, tags in self._populate_tags(ts): pass self.assertEqual(self.tag_engine.get_tags(names=('WikiStart', 1), operation='union'), set(['death', 'bar', 'war', 'foo'])) def test_get_tags_intersection(self): ts = self.tag_engine.tagspace for tagspace, target, tags in self._populate_tags(ts): pass self.assertEqual(self.tag_engine.get_tags(names=('WikiStart', 3), operation='intersection'), set(['bar', 'foo'])) def test_tagspace_get_tagged_union(self): ts = self.tag_engine.tagspace for tagspace, target, tags in self._populate_tags(ts): pass self.assertEqual(self.tag_engine.tagspace.wiki.get_tagged_names(tags=('foo', 'bar'), operation='union'), set([u'WikiStart', u'SandBox'])) def test_tagspace_get_tagged_intersection(self): ts = self.tag_engine.tagspace for tagspace, target, tags in self._populate_tags(ts): pass self.assertEqual(self.tag_engine.tagspace.wiki.get_tagged_names(tags=('bar',), operation='intersection'), set([u'WikiStart', u'SandBox'])) def test_tagspace_get_tags_union(self): ts = self.tag_engine.tagspace for tagspace, target, tags in self._populate_tags(ts): pass self.assertEqual(self.tag_engine.tagspace.wiki.get_tags(names=('WikiStart', 'SandBox'), operation='union'), set(['bar', 'war', 'foo'])) def test_tagspace_get_tags_intersection(self): ts = self.tag_engine.tagspace for tagspace, target, tags in self._populate_tags(ts): pass self.assertEqual(self.tag_engine.tagspace.wiki.get_tags(names=('WikiStart', 'SandBox'), operation='intersection'), set(['bar'])) # Detailed def test_detailed_get_tagged_union(self): ts = self.tag_engine.tagspace for tagspace, target, tags in self._populate_tags(ts): pass self.assertEqual(self.tag_engine.get_tagged_names(tags=('foo', 'bar'), operation='union', detailed=True), {'wiki': {'WikiStart': set(('foo', 'bar')), 'SandBox': set(('bar', 'war'))}, 'ticket': {3: set(('foo', 'bar', 'destruction'))}}) def test_detailed_get_tagged_intersection(self): ts = self.tag_engine.tagspace for tagspace, target, tags in self._populate_tags(ts): pass self.assertEqual(self.tag_engine.get_tagged_names(tags=('foo', 'bar'), operation='intersection', detailed=True), {'wiki': {'WikiStart': set(('foo', 'bar'))}, 'ticket': {3: set(('foo', 'bar', 'destruction'))}}) def test_detailed_get_tags_union(self): ts = self.tag_engine.tagspace for tagspace, target, tags in self._populate_tags(ts): pass self.assertEqual(self.tag_engine.get_tags(names=('WikiStart', 1), operation='union', detailed=True), {'death': set([('ticket', 1)]), 'bar': set([('wiki', 'WikiStart')]), 'war': set([('ticket', 1)]), 'foo': set([('wiki', 'WikiStart')])}) def test_detailed_get_tags_intersection(self): ts = self.tag_engine.tagspace for tagspace, target, tags in self._populate_tags(ts): pass self.assertEqual(self.tag_engine.get_tags(names=('WikiStart', 3, 'SandBox'), operation='intersection', detailed=True), {'bar': set([('wiki', 'SandBox'), ('wiki', 'WikiStart'), ('ticket', 3)])}) def test_detailed_tagspace_get_tagged_union(self): ts = self.tag_engine.tagspace for tagspace, target, tags in self._populate_tags(ts): pass self.assertEqual(self.tag_engine.tagspace.wiki.get_tagged_names(tags=('foo', 'bar'), operation='union', detailed=True), {'WikiStart': set(['foo', 'bar']), 'SandBox': set(['bar', 'war'])}) def test_detailed_tagspace_get_tagged_intersection(self): ts = self.tag_engine.tagspace for tagspace, target, tags in self._populate_tags(ts): pass self.assertEqual(self.tag_engine.tagspace.wiki.get_tagged_names(tags=('bar',), operation='intersection', detailed=True), {'WikiStart': set(['foo', 'bar']), 'SandBox': set(['bar', 'war'])}) def test_detailed_tagspace_get_tags_union(self): ts = self.tag_engine.tagspace for tagspace, target, tags in self._populate_tags(ts): pass self.assertEqual(self.tag_engine.tagspace.wiki.get_tags(names=('WikiStart', 'SandBox'), operation='union', detailed=True), {'foo': set(['WikiStart']), 'bar': set(['WikiStart', 'SandBox']), 'war': set(['SandBox'])}) def test_detailed_tagspace_get_tags_intersection(self): ts = self.tag_engine.tagspace for tagspace, target, tags in self._populate_tags(ts): pass self.assertEqual(self.tag_engine.tagspace.wiki.get_tags(names=('WikiStart', 'SandBox'), operation='intersection', detailed=True), {'bar': set(['WikiStart', 'SandBox'])})
def _tag_formatter(self, formatter, ns, target, label): href, title = TagEngine(self.env).get_tag_link(target) return Markup('<a href="%s" title="%s">%s</a>' % (href, title, label))
def render_listtagged(self, req, *tags, **kwargs): """ List tagged objects. Optionally accepts a list of tags to match against. The special tag '''. (dot)''' inserts the current Wiki page name. `[[ListTagged(<tag>, ...)]]` ||'''Argument'''||'''Description'''|| ||`tagspace=<tagspace>`||Specify the tagspace the macro should operate on.|| ||`tagspaces=(<tagspace>,...)`||Specify a set of tagspaces the macro should operate on.|| ||`operation=intersection|union`||The set operation to perform on the discovered objects.|| ||`showheadings=true|false`||List objects under the tagspace they occur in.|| ||`expression=<expr>`||Match object tags against the given expression.|| The supported expression operators are: unary - (not); binary +, - and | (and, and not, or). All values in the expression are treated as tags. Any tag not in the same form as a Python variable must be quoted. eg. Match all objects tagged with ticket and workflow, and not tagged with wiki or closed. (ticket+workflow)-(wiki|closed) If an expression is provided operation is ignored. """ if 'tagspace' in kwargs: tagspaces = [kwargs.get('tagspace', None)] else: tagspaces = kwargs.get('tagspaces', '') or \ list(TagEngine(self.env).tagspaces) expression = kwargs.get('expression', None) showheadings = kwargs.get('showheadings', 'false') operation = kwargs.get('operation', 'intersection') if operation not in ('union', 'intersection'): raise TracError("Invalid tag set operation '%s'" % operation) engine = TagEngine(self.env) page_name = self._current_page(req) if page_name: tags = [tag == '.' and page_name or tag for tag in tags] tags = set(tags) taginfo = {} out = StringIO() out.write('<ul class="listtagged">\n') # If expression was passed as an argument, do a full walk, using the # expression as the predicate. Silently assumes that failed expressions # are normal tags. if expression: from tractags.expr import Expression try: expr = Expression(expression) self.env.log.debug(repr(expr)) except Exception, e: self.env.log.error("Invalid expression '%s'" % expression, exc_info=True) tags.update([ x.strip() for x in re.split('[+,]', expression) if x.strip() ]) expression = None else: self.env.log.debug(expr.ast) tagged_names = {} tags.update(expr.get_tags()) for tagspace, name, name_tags in engine.walk_tagged_names( tags=tags, tagspaces=tagspaces, predicate=lambda ts, n, t: expr(t)): tagged_names.setdefault(tagspace, {})[name] = name_tags tagged_names = [ (tagspace, names) for tagspace, names in tagged_names.iteritems() ]
def wiki_page_changed(self, page, version, t, comment, author, ipnr): TagEngine(self.env).flush_link_cache(page)
def _tag_details(self, links, tags): """ Extract dictionary of tag:(href, title) for all tags. """ for tag in tags: if tag not in links: links[tag] = TagEngine(self.env).get_tag_link(tag) return links
def wiki_page_version_deleted(self, page): # Wiki tags are not versioned. If they were, we'd delete them here. TagEngine(self.env).flush_link_cache(page)
def render_tagcloud(self, req, smallest=10, biggest=20, showcount=True, tagspace=None, mincount=1, tagspaces=[], **kwargs): """ This macro displays a [http://en.wikipedia.org/wiki/Tag_cloud tag cloud] (weighted list) of all tags. ||'''Argument'''||'''Description'''|| ||`tagspace=<tagspace>`||Specify the tagspace the macro should operate on.|| ||`tagspaces=(<tagspace>,...)`||Specify a set of tagspaces the macro should operate on.|| ||`smallest=<n>`||The lower bound of the font size for the tag cloud.|| ||`biggest=<n>`||The upper bound of the font size for the tag cloud.|| ||`showcount=true|false`||Show the count of objects for each tag?|| ||`mincount=<n>`||Hide tags with a count less than `<n>`.|| """ smallest = int(smallest) biggest = int(biggest) mincount = int(mincount) engine = TagEngine(self.env) # Get wiki tagspace if tagspace: tagspaces = [tagspace] else: tagspaces = tagspaces or engine.tagspaces cloud = {} for tag, names in engine.get_tags(tagspaces=tagspaces, detailed=True).iteritems(): count = len(names) if count >= mincount: cloud[tag] = len(names) tags = cloud.keys() # No tags? if not tags: return '' # by_count maps tag counts to an index in the set of counts by_count = list(set(cloud.values())) by_count.sort() by_count = dict([(c, float(i)) for i, c in enumerate(by_count)]) taginfo = self._tag_details({}, tags) tags.sort() rlen = float(biggest - smallest) tlen = float(len(by_count)) scale = 1.0 if tlen: scale = rlen / tlen out = StringIO() out.write('<ul class="tagcloud">\n') last = tags[-1] for tag in tags: if tag == last: cls = ' class="last"' else: cls = '' if showcount != 'false': count = ' <span class="tagcount">(%i)</span>' % cloud[tag] else: count = '' out.write( '<li%s><a rel="tag" title="%s" style="font-size: %ipx" href="%s">%s</a>%s</li>\n' % (cls, taginfo[tag][1] + ' (%i)' % cloud[tag], smallest + int(by_count[cloud[tag]] * scale), taginfo[tag][0], tag, count)) out.write('</ul>\n') return out.getvalue()
def _generate_blog(self, req, *args, **kwargs): """Extract the blog pages and fill the HDF. *args is a list of tags to use to limit the blog scope **kwargs are any aditional keyword arguments that are needed """ tallies = {} tags = TagEngine(self.env).tagspace.wiki try: union = kwargs['union'] except KeyError: union = False # Formatting read_post = "[wiki:%s Read Post]" entries = {} if not len(args): tlist = [self.env.config.get('blog', 'default_tag', 'blog')] else: tlist = args if union: blog = tags.get_tagged_names(tlist, operation='union') else: blog = tags.get_tagged_names(tlist, operation='intersection') macropage = req.args.get('page', None) poststart, postend, default_times = self._get_time_range(req, **kwargs) mark_updated = self._choose_value('mark_updated', req, kwargs, convert=bool_val) if not mark_updated and (not isinstance(mark_updated, bool)): mark_updated = bool_val(self.env.config.get('blog', 'mark_updated', True)) macro_bl = self.env.config.get('blog', 'macro_blacklist', '').split(',') macro_bl = [name.strip() for name in macro_bl if name.strip()] macro_bl.append('BlogShow') # Get the email addresses of all known users and validate the "poster" # BlogShow optional argument at the same time (avoids looping the user # list twice). is_poster = None limit_poster = self._choose_value('poster', req, kwargs, convert=None) email_map = {} for username, name, email in self.env.get_known_users(): if email: email_map[username] = email if limit_poster != None: if username == limit_poster: is_poster = username num_posts = self._choose_value('num_posts', req, kwargs, convert=int) if num_posts and default_times: poststart = sys.maxint postend = 0 for blog_entry in blog: if blog_entry == macropage: continue try: page = WikiPage(self.env, version=1, name=blog_entry) version, post_time, author, comment, ipnr = page.get_history( ).next() # if we're limiting by poster, do so now so that the calendar # only shows the number of entries the specific poster made. if is_poster != None: if is_poster != author: continue self._add_to_tallies(tallies, post_time, blog_entry) page = WikiPage(self.env, name=blog_entry) version, modified, author, comment, ipnr = page.get_history( ).next() except: self.log.debug("Error loading wiki page %s" % blog_entry, exc_info=True) continue if poststart >= post_time >= postend: time_format = self.env.config.get('blog', 'date_format') \ or '%x %X' timeStr = format_datetime(post_time, format=time_format) fulltext = page.text # remove comments in blog view: del_comments = re.compile('==== Comment.*\Z', re.DOTALL) fulltext = del_comments.sub('', fulltext) # remove the [[AddComment...]] tag, otherwise it would appeare # more than one and crew up the blog view: del_addcomment = re.compile('\[\[AddComment.*\Z', re.DOTALL) fulltext = del_addcomment.sub('', fulltext) # limit length of preview: post_size = self._choose_value('post_size', req, kwargs, int) if not post_size and (not isinstance(post_size, int)): post_size = int(self.env.config.get('blog', 'post_size', 1024)) text = self._trim_page(fulltext, blog_entry, post_size) pagetags = [x for x in tags.get_name_tags(blog_entry) if x not in tlist] tagtags = [] for i, t in enumerate(pagetags[:3]): d = { 'link' : t, 'name' : t, 'last' : i == (len(pagetags[:3]) - 1), } tagtags.append(d) continue # extract title from text: match = _title_split_match(fulltext) if match: title = match.group(1) fulltext = match.group(2) else: title = blog_entry html_text = wiki_to_html(fulltext, self.env, req) rss_text = Markup.escape(to_unicode(html_text)) data = { 'name' : blog_entry, 'title' : title, 'href' : self.env.href.wiki(blog_entry), 'wiki_link' : wiki_to_oneliner(read_post % blog_entry, self.env), 'time' : timeStr, 'date' : http_date(post_time), # 'date' : http_date(page.time), 'author' : author, 'wiki_text' : wiki_to_nofloat_html(text, self.env, req, macro_blacklist=macro_bl), 'rss_text' : rss_text, 'comment' : wiki_to_oneliner(comment, self.env), 'tags' : { 'present' : len(pagetags), 'tags' : tagtags, 'more' : len(pagetags) > 3 or 0, }, } if author: # For RSS, author must be an email address if author.find('@') != -1: data['author.email'] = author elif email_map.has_key(author): data['author.email'] = email_map[author] if (modified != post_time) and mark_updated: data['modified'] = 1 mod_str = format_datetime(modified, format=time_format) data['mod_time'] = mod_str entries[post_time] = data continue tlist = entries.keys() tlist.sort() tlist.reverse() if num_posts and (num_posts <= len(tlist)): tlist = tlist[:num_posts] if tlist: entries[tlist[-1]]['last'] = 1 req.hdf['blog.entries'] = [entries[x] for x in tlist] bloglink = self.env.config.get('blog', 'new_blog_link', 'New Blog Post') req.hdf['blog.newblog'] = bloglink hidecal = self._choose_value('hidecal', req, kwargs) if not hidecal: self._generate_calendar(req, tallies) req.hdf['blog.hidecal'] = hidecal # Insert /wiki/BlogHeader into /blog. If the page does not exist, # this'll be a no-op blog_header = WikiPage(self.env, name='BlogHeader').text req.hdf['blog.header'] = Mimeview(self.env).render(req, 'text/x-trac-wiki', blog_header)