import re from tractags.api import TagEngine from StringIO import StringIO from trac.core import * from trac.perm import IPermissionRequestor from trac.web.main import IRequestHandler from trac.web.chrome import ITemplateProvider, INavigationContributor, add_stylesheet from trac.web.api import ITemplateStreamFilter from trac.wiki.api import IWikiPageManipulator from trac.util.html import Markup, escape from trac.util.compat import set from trac.wiki.web_ui import WikiModule from tractags.expr import Expression from genshi.builder import tag as T from genshi.filters.transform import Transformer _tag_split = re.compile('[,\s]+') class TagsUserInterface(Component): implements(ITemplateStreamFilter, IWikiPageManipulator, IPermissionRequestor) # Internal methods 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 _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 _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 _wiki_edit(self, req, stream): insert = T.div(class_='field')( T.label( 'Tag under: (', T.a('view all tags', href=req.href.tags()), ')', T.br(), T.input(id='tags', type='text', name='tags', size='30', value=req.args.get('tags', ' '.join(self._page_tags(req)))), ) ) return stream | Transformer('//div[@id="changeinfo1"]').append(insert) # IPermissionRequestor methods def get_permission_actions(self): return ['TAGS_VIEW', 'TAGS_MODIFY'] # ITemplateStreamFilter methods def filter_stream(self, req, method, filename, stream, data): if filename == 'wiki_view.html' and 'TAGS_VIEW' in req.perm: return self._wiki_view(req, stream) elif filename == 'wiki_edit.html' and 'TAGS_MODIFY' in req.perm: return self._wiki_edit(req, stream) return stream # IWikiPageManipulator methods def prepare_wiki_page(self, req, page, fields): pass def validate_wiki_page(self, req, page): if req and 'TAGS_MODIFY' in req.perm and req.path_info.startswith('/wiki') \ and 'save' in req.args: self._update_tags(req, page) return [] class TagsModule(Component): """ Serve a /tags namespace. Top-level displays tag cloud, sub-levels display output of ListTagged(tag). The following configuration options are supported: [tags] # Use a tag list or cloud for the main index index = cloud|list # The keyword arguments to pass to the TagCloud or ListTags macros that # is being used for the index. index.args = ... # Keyword arguments to pass to the listing for each tag under the # /tags/ URL space. listing.args = ... """ implements(IRequestHandler, INavigationContributor, ITemplateProvider) # This method is never really called by anything.... Remove? # 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 # ITemplateProvider methods def get_templates_dirs(self): """ Return the absolute path of the directory containing the provided ClearSilver templates. """ from pkg_resources import resource_filename return [resource_filename(__name__, 'templates')] def get_htdocs_dirs(self): """Return the absolute path of a directory containing additional static resources (such as images, style sheets, etc). """ from pkg_resources import resource_filename return [('tags', resource_filename(__name__, 'htdocs'))] # INavigationContributor methods def get_active_navigation_item(self, req): if 'TAGS_VIEW' in req.perm: return 'tags' def get_navigation_items(self, req): from trac.web.chrome import Chrome if 'TAGS_VIEW' in req.perm: yield ('mainnav', 'tags', Markup('Tags', req.href.tags())) # IRequestHandler methods def match_request(self, req): return 'TAGS_VIEW' in req.perm and req.path_info.startswith('/tags') def process_request(self, req): from tractags.macros import TagMacros from tractags.parseargs import parseargs from trac.web.chrome import add_stylesheet req.perm.require('TAGS_VIEW') add_stylesheet(req, 'tags/css/tractags.css') data = {} def update_from_req(args): for k in req.args.keys(): args[k] = unicode(req.args.get(k)) if not req.args.has_key('e') and re.match('^/tags/?$', req.path_info): index = self.env.config.get('tags', 'index', 'cloud') index_kwargs = {'smallest': 10, 'biggest': 30} _, config_kwargs = parseargs(self.env.config.get('tags', 'index.args', '')) index_kwargs.update(config_kwargs) update_from_req(index_kwargs) if index == 'cloud': data['tag_body'] = Markup( TagMacros(self.env).render_tagcloud(req, **index_kwargs)) elif index == 'list': data['tag_body'] = Markup( TagMacros(self.env).render_listtagged(req, **index_kwargs)) else: raise TracError("Invalid index style '%s'" % index) else: _, args = parseargs(self.env.config.get('tags', 'listing.args', '')) if req.args.has_key('e'): expr = req.args.get('e') else: expr = req.path_info[6:] data['tag_title'] = Markup('Objects matching the expression %s' % escape(expr)) data['tag_expression'] = expr try: Expression(expr) except Exception, e: data['tag_expression_error'] = unicode(e).replace(' (line 1)', '') args['expression'] = expr tags = [] update_from_req(args) data['tag_body'] = Markup( TagMacros(self.env).render_listtagged(req, *tags, **args)) return 'tags.html', data, None