Commits

ejucovy committed 16f7242

remove code from gh-pages branch

Comments (0)

Files changed (9)

LICENSE.txt

-Copyright (C) 2011-2012 Progressive Change Campaign Committee
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions
-are met:
-
- 1. Redistributions of source code must retain the above copyright
-    notice, this list of conditions and the following disclaimer.
- 2. Redistributions in binary form must reproduce the above copyright
-    notice, this list of conditions and the following disclaimer in
-    the documentation and/or other materials provided with the
-    distribution.
- 3. The name of the author may not be used to endorse or promote
-    products derived from this software without specific prior
-    written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
-OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
-DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
-GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
-IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
-OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
-IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-Portions of this code are copyright (C) 2011-2012 by Michael Bayer,
-and distributed under the terms of the MIT license.
-
-This is the MIT license: http://www.opensource.org/licenses/mit-license.php
-
-Copyright (C) 2011-2012 by Michael Bayer.
-
-Permission is hereby granted, free of charge, to any person obtaining a copy of this
-software and associated documentation files (the "Software"), to deal in the Software
-without restriction, including without limitation the rights to use, copy, modify, merge,
-publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
-to whom the Software is furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all copies or
-substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
-INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
-PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
-FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
-OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
-DEALINGS IN THE SOFTWARE.

README.txt

-Adds user picture icons (avatars) to Trac.
-
-Credit goes to Michael Bayer for the idea -- and most of the code -- in https://bitbucket.org/zzzeek/tracvatar
-
-The avatar engine is configurable, and two are provided in this
-package: a GravatarProvider that looks up the user's email address at
-Gravatar, and a UserManagerProvider that uses internally hosted images
-provided by the Trac UserManager Plugin if it is installed: http://trac-hacks.org/wiki/UserManagerPlugin
-
-The approach of the plugin is to filter specific Trac views, gather
-all the authors found in the "data" hash being passed to Genshi, then
-using Genshi filters to insert additional avatar nodes with the proper
-img tags. 
-
-Currently supported views are:
-
- * Timeline
- * Ticket details: reporter, owner, comments, comment diffs
- * Attachment views (on tickets, wiki pages, etc)
- * Source control views (directory listings, file contents, changesets)
- * Report and custom query views
- * Wiki history, diffs and individual versions
- * Search results
-
-This is, more or less, all the places where users show up in a
-standard Trac instance.  If you find any other places where icons
-should also be inserted, whether in a standard Trac installation or in
-a view provided by your favorite plugin, please submit an issue or a
-patch.
-
-Patches implementing additional avatar engines are also welcome.
-
-== Installation ==
-
-Install the plugin in your favorite way (python setup.py develop,
-uploading an egg, etc) and then enable its components in trac.ini like
-so::
-
-  [components]
-  userpictures.* = enabled
-
-You should then choose your preferred avatar engine.  For Gravatar::
-
-  [userpictures]
-  pictures_provider = UserPicturesGravatarProvider
-
-For UserManager, ensure that the UserManager plugin is installed, and
-then::
-
-  [userpictures]
-  pictures_provider = UserPicturesUserManagerProvider
-
-If you do not explicitly select either engine, a default provider is
-used which displays a blank silhouette for every user.
-
-There are a number of optional "size" settings for each view; these
-are set to sensible defaults that are designed to look good with a
-standard Trac install and the stylesheet provided by this plugin, but
-look at the source in userpictures/__init__.py (and the CSS in
-userpictures/htdocs/userpictures.css) if you really want to change the
-way the icons are displayed. 

setup.py

-from setuptools import find_packages, setup
-
-version='0.1'
-
-try:
-    long_description = open("README.txt").read()
-except:
-    long_description = ''
-
-setup(name='trac-UserPictures',
-      version=version,
-      description="Adds user pictures to Trac",
-      long_description=long_description,
-      author='Ethan Jucovy',
-      author_email='ejucovy@gmail.com',
-      url='http://trac-hacks.org/wiki/UserPicturesPlugin',
-      keywords='trac plugin',
-      license="BSD",
-      packages=find_packages(exclude=['ez_setup', 'examples', 'tests*']),
-      include_package_data=True,
-      package_data={ 'userpictures': ['templates/*', 'htdocs/*'] },
-      zip_safe=False,
-      entry_points = """
-      [trac.plugins]
-      userpictures = userpictures
-      """,
-      )
-

userpictures/__init__.py

-from genshi.filters.transform import Transformer
-from genshi.builder import tag
-import itertools
-from pkg_resources import resource_filename
-
-from trac.config import *
-from trac.core import *
-from trac.web.api import ITemplateStreamFilter
-from trac.web.chrome import ITemplateProvider, add_stylesheet
-
-class IUserPicturesProvider(Interface):
-    def get_src(req, username, size):
-        """
-        Return the path to an image for this user, either locally or on the web
-        """
-
-class DefaultUserPicturesProvider(Component):
-    implements(IUserPicturesProvider)
-
-    def get_src(self, req, username, size):
-        return req.href.chrome('userpictures/default-portrait.gif')
-
-from userpictures.providers import *
-
-class _render_event(object):
-    def __init__(self, event, base_render, generate_avatar):
-        self.event = event
-        self.base_render = base_render
-        self.generate_avatar = generate_avatar
-
-    def __call__(self, field, context):
-        orig = self.base_render(field, context)
-        if field != 'description':
-            return orig
-        if not self.event.get('author'):
-            return orig
-        author = self.event['author']
-
-        return tag.div(self.generate_avatar(author), orig)
-    ## style="padding-top: 0.2em; padding-right: 1em; margin-left: -5.8em; vertical-align: text-top"),
-
-class UserPicturesModule(Component):
-    implements(ITemplateStreamFilter, ITemplateProvider)
-
-    pictures_provider = ExtensionOption('userpictures', 'pictures_provider',
-                                        IUserPicturesProvider,
-                                        'DefaultUserPicturesProvider')
-
-    ticket_comment_diff_size = Option("userpictures", "ticket_comment_diff_size", default="30")
-    ticket_reporter_size = Option("userpictures", "ticket_reporter_size", default="60")
-    ticket_owner_size = Option("userpictures", "ticket_owner_size", default="30")
-    ticket_comment_size = Option("userpictures", "ticket_comment_size", default="40")
-    timeline_size = Option("userpictures", "timeline_size", default="30")
-    report_size = Option("userpictures", "report_size", default="20")
-    browser_changeset_size = Option("userpictures", "browser_changeset_size", default="30")
-    browser_filesource_size = Option("userpictures", "browser_filesource_size", default="40")
-    browser_lineitem_size = Option("userpictures", "browser_lineitem_size", default="20")
-    search_results_size = Option("userpictures", "search_results_size", default="20")
-    wiki_diff_size = Option("userpictures", "wiki_diff_size", default="30")
-    wiki_history_lineitem_size = Option("userpictures", "wiki_history_lineitem_size", default="20")
-    wiki_view_size = Option("userpictures", "wiki_view_size", default="40")
-    attachment_view_size = Option("userpictures", "attachment_view_size", default="40")
-    attachment_lineitem_size = Option("userpictures", "attachment_lineitem_size", default="20")
-
-    ## ITemplateProvider methods
-
-    def get_htdocs_dirs(self):
-        yield 'userpictures', resource_filename(__name__, 'htdocs')
-
-    def get_templates_dirs(self):
-        return []    
-
-    ## ITemplateStreamFilter methods
-
-    def filter_stream(self, req, method, filename, stream, data):
-        filter_ = []
-        if req.path_info.startswith("/ticket"):
-            filter_.extend(self._ticket_filter(req, data))
-        elif req.path_info.startswith("/timeline"):
-            filter_.extend(self._timeline_filter(req, data))
-        elif req.path_info.startswith("/browser") or req.path_info.startswith("/changeset"):
-            filter_.extend(self._browser_filter(req, data))
-        elif req.path_info.startswith("/log"):
-            filter_.extend(self._log_filter(req, data))
-        elif req.path_info.startswith("/search"):
-            filter_.extend(self._search_filter(req, data))
-        elif req.path_info.startswith("/report") or req.path_info.startswith("/query"):
-            filter_.extend(self._report_filter(req, data))
-        elif req.path_info.startswith("/wiki"):
-            filter_.extend(self._wiki_filter(req, data))
-        elif req.path_info.startswith("/attachment"):
-            filter_.extend(self._attachment_filter(req, data))
-        
-        if 'attachments' in data and data.get('attachments', {}).get('attachments'):
-            filter_.extend(self._page_attachments_filter(req, data))
-
-        for f in filter_:
-            if f is not None:
-                stream |= f
-
-        add_stylesheet(req, 'userpictures/userpictures.css')
-        return stream
-
-    def _generate_avatar(self, req, author, class_, size):
-        href = self.pictures_provider.get_src(req, author, size)
-        return tag.img(src=href, class_='userpictures_avatar %s' % class_,
-                       width=size, height=size).generate()
-
-    def _ticket_filter(self, req, data):
-        filter_ = []
-        if "action=comment-diff" in req.query_string:
-            filter_.extend(self._ticket_comment_diff_filter(req, data))
-        else:
-            filter_.extend(self._ticket_reporter_filter(req, data))
-            filter_.extend(self._ticket_owner_filter(req, data))
-            filter_.extend(self._ticket_comment_filter(req, data))
-        return filter_
-
-    def _ticket_comment_diff_filter(self, req, data):
-        author = data['change']['author']
-
-        return [lambda stream: Transformer('//dd[@class="author"]'
-                                           ).prepend(self._generate_avatar(
-                    req, author, 
-                    "ticket-comment-diff", self.ticket_comment_diff_size)
-                                                     )(stream)]
-
-    def _ticket_reporter_filter(self, req, data):
-        if 'ticket' not in data:
-            return []
-        author = data['ticket'].values['reporter']
-
-        return [lambda stream: Transformer('//div[@id="ticket"]'
-                                           ).prepend(self._generate_avatar(
-                    req, author,
-                    'ticket-reporter', self.ticket_reporter_size)
-                                                     )(stream)]
-    def _ticket_owner_filter(self, req, data):
-        if 'ticket' not in data:
-            return []
-        author = data['ticket'].values['owner']
-
-        return [lambda stream: Transformer('//td[@headers="h_owner"]'
-                                           ).prepend(self._generate_avatar(
-                    req, author,
-                    'ticket-owner', self.ticket_owner_size)
-                                                     )(stream)]
-        
-    def _ticket_comment_filter(self, req, data):
-        if 'changes' not in data:
-            return []
-
-        apply_authors = []
-        for change in data['changes']:
-            author = change['author']
-            apply_authors.insert(0, author)
-
-        def find_change(stream):
-            stream = iter(stream)
-            author = apply_authors.pop()
-            tag = self._generate_avatar(req, author,
-                                        'ticket-comment', self.ticket_comment_size)
-            return itertools.chain([next(stream)], tag, stream)
-
-        return [Transformer('//div[@id="changelog"]/div[@class="change"]/h3[@class="change"]'
-                            ).filter(find_change)]
-
-    def _timeline_filter(self, req, data):
-        if 'events' not in data:
-            return []
-
-        # Instead of using a Genshi filter here,
-        # we'll reach into the guts of the context
-        # and manipulate the `render` function provided in there.
-        # This is likely to break one day since this is not a public API!
-        for event in data['events']:
-            base_render = event['render']
-            event['render'] = _render_event(
-                event, base_render, 
-                lambda author: self._generate_avatar(req, author, 
-                                                     'timeline',
-                                                     self.timeline_size))
-            
-        return []
-
-    def _browser_filter(self, req, data):
-        filter_ = []
-        filter_.extend(self._browser_changeset_filter(req, data))
-        filter_.extend(self._browser_lineitem_filter(req, data))
-        return filter_
-
-    def _browser_changeset_filter(self, req, data):
-        author = None
-        if (data.get('file') or {}).get('changeset'):
-            author = data['file']['changeset'].author
-        elif 'changeset' in data:
-            author = data['changeset'].author
-        if author is None:
-            return []
-
-        return [lambda stream: Transformer('//table[@id="info"]//th'
-                                           ).prepend(self._generate_avatar(
-                    req, author,
-                    "browser-filesource", self.browser_filesource_size)
-                                                     )(stream),
-                lambda stream: Transformer('//dd[@class="author"]'
-                                           ).prepend(self._generate_avatar(
-                    req, author,
-                    "browser-changeset", self.browser_changeset_size)
-                                                     )(stream),
-                ]
-
-    def _browser_lineitem_filter(self, req, data):
-        def find_change(stream):
-            author = ''.join(stream_part[1] for stream_part in stream if stream_part[0] == 'TEXT').strip()
-            tag = self._generate_avatar(req, author,
-                                        'browser-lineitem', self.browser_lineitem_size)
-            return itertools.chain([stream[0]], tag, stream[1:])
-
-        return [Transformer('//td[@class="author"]').filter(find_change)]
-
-    def _log_filter(self, req, data):
-        if 'changes' not in data:
-            return []
-
-        return self._browser_lineitem_render_filter(req, data)
-
-    def _search_filter(self, req, data):
-        if 'results' not in data:
-            return []
-
-        ## The stream contains this stupid "By ethan" instead of just "ethan"
-        ## so we'll rely on the ordering of the data instead, 
-        ## and file a ticket with Trac core eventually
-        results_iter = iter(data['results'])
-        def find_change(stream):
-            try:
-                author = results_iter.next()['author']
-            except StopIteration:
-                author = ''.join(stream_part[1] for stream_part in stream if stream_part[0] == 'TEXT').strip() ## As a fallback, we may as well, but this should never happen...
-            tag = self._generate_avatar(req, author,
-                                        'search-results', 
-                                        self.search_results_size)
-            return itertools.chain([stream[0]], tag, stream[1:])
-
-        return [Transformer('//span[@class="author"]').filter(find_change)]
-
-    def _report_filter(self, req, data):
-        if 'tickets' not in data and 'row_groups' not in data:
-            return []
-
-        if 'tickets' in data:
-            class_ = 'query'
-        elif 'row_groups' in data:
-            class_ = 'report'
-
-        def find_change(stream):
-            author = ''.join(stream_part[1] for stream_part in stream if stream_part[0] == 'TEXT').strip()
-            tag = self._generate_avatar(req, author,
-                                        class_, self.report_size)
-            return itertools.chain([stream[0]], tag, stream[1:])
-
-        return [Transformer('//table[@class="listing tickets"]/tbody/tr/td[@class="owner"]|//table[@class="listing tickets"]/tbody/tr/td[@class="reporter"]').filter(find_change)]
-
-    def _wiki_filter(self, req, data):
-        if "action=diff" in req.query_string:
-            return self._wiki_diff_filter(req, data)
-        elif "action=history" in req.query_string:
-            return self._wiki_history_lineitem_filter(req, data)
-        elif "version" in req.query_string:
-            if 'page' not in data:
-                return []
-            author = data['page'].author
-            return [lambda stream: Transformer('//table[@id="info"]//th'
-                                               ).prepend(
-                    self._generate_avatar(
-                        req, author,
-                        "wiki-view", self.wiki_view_size)
-                    )(stream)]
-        return []
-
-    def _wiki_diff_filter(self, req, data):
-        author = data['change']['author']
-
-        return [lambda stream: Transformer('//dd[@class="author"]'
-                                           ).prepend(self._generate_avatar(
-                    req, author, 
-                    "wiki-diff", self.wiki_diff_size)
-                                                     )(stream)]
-    
-    def _wiki_history_lineitem_filter(self, req, data):
-        def find_change(stream):
-            author = ''.join(stream_part[1] for stream_part in stream if stream_part[0] == 'TEXT').strip()
-            tag = self._generate_avatar(req, author,
-                                        'wiki-history-lineitem', self.wiki_history_lineitem_size)
-            return itertools.chain([stream[0]], tag, stream[1:])
-
-        return [Transformer('//td[@class="author"]').filter(find_change)]
-
-    def _attachment_filter(self, req, data):
-        if not data.get('attachment'):
-            return []
-        author = data['attachment'].author
-        if not author:
-            return []
-        return [Transformer('//table[@id="info"]//th'
-                            ).prepend(
-                self._generate_avatar(
-                    req, author,
-                    "attachment-view", self.attachment_view_size)
-                )]
-
-    def _page_attachments_filter(self, req, data):
-        def find_change(stream):
-            author = ''.join(stream_part[1] for stream_part in stream if stream_part[0] == 'TEXT').strip()
-            tag = self._generate_avatar(req, author,
-                                        'attachment-lineitem', self.attachment_lineitem_size)
-            return itertools.chain([stream[0]], tag, stream[1:])
-
-        return [Transformer('//div[@id="attachments"]/div/ul/li/em|//div[@id="attachments"]/div[@class="attachments"]/dl[@class="attachments"]/dt/em').filter(find_change)]
-        

userpictures/htdocs/default-portrait.gif

Removed
Old image

userpictures/htdocs/userpictures.css

-img.userpictures_avatar {
-  box-shadow: 2px 2px 4px #444;
-}
-
-img.userpictures_avatar.timeline {
-  margin-top: 0.2em;
-  margin-right: 1em;
-  margin-left: -5.8em;
-  vertical-align: text-top;
-}
-
-img.userpictures_avatar.ticket-reporter {
-  float: left;
-  margin: 0 10px 10px 0px;
-}
-
-img.userpictures_avatar.ticket-comment {
-  margin: 0 5px 5px 0;
-}
-
-img.userpictures_avatar.ticket-owner {
-  margin-top: 0.2em;
-  margin-right: 1em;
-  vertical-align: middle;
-}
-
-img.userpictures_avatar.browser-lineitem,
-img.userpictures_avatar.wiki-history-lineitem {
-  margin: 0 5px 0 0;
-}
-img.userpictures_avatar.attachment-lineitem {
-  margin: 0 5px 5px 0;
-  vertical-align: middle;
-}
-
-img.userpictures_avatar.browser-changeset,
-img.userpictures_avatar.ticket-comment-diff,
-img.userpictures_avatar.wiki-diff {
-  margin: 3px 10px 5px 0px;
-  vertical-align: middle;
-}
-
-img.userpictures_avatar.browser-filesource,
-img.userpictures_avatar.wiki-view {
-  margin: 3px 10px 0px 0px;
-  vertical-align: top;
-}
-
-img.userpictures_avatar.attachment-view {
-  margin: 3px 10px 3px 0px;
-  vertical-align: top;
-}
-
-img.userpictures_avatar.search-results {
-  margin-right: 5px;
-  margin-top: 3px;
-  vertical-align: middle;
-}
-
-img.userpictures_avatar.report {
-  margin-right: 5px;
-}

userpictures/providers/__init__.py

-from userpictures.providers.gravatar import UserPicturesGravatarProvider
-from userpictures.providers.usermanager import UserPicturesUserManagerProvider
-    

userpictures/providers/gravatar.py

-import hashlib
-import re
-
-from trac.config import *
-from trac.core import *
-
-from userpictures import IUserPicturesProvider
-
-class UserPicturesGravatarProvider(Component):
-    implements(IUserPicturesProvider)
-
-    # from trac source
-    _long_author_re = re.compile(r'.*<([^@]+)@([^@]+)>\s*|([^@]+)@([^@]+)')
-
-    @property
-    def email_map(self):
-        if hasattr(self, '_email_map'):
-            return self._email_map
-        _email_map = {}
-        for username, name, email in self.env.get_known_users():
-            _email_map[username] = email
-        setattr(self, '_email_map', _email_map)
-        return self._email_map
-
-    def get_src(self, req, username, size):
-        email = ''
-        if '@' not in username:
-            if username != 'anonymous':
-                email = self.email_map.get(username) or ''
-        else:
-            author_info = self._long_author_re.match(username)
-            if author_info:
-                if author_info.group(1):
-                    email = '%s@%s' % author_info.group(1, 2)
-                elif author_info.group(3):
-                    email = '%s@%s' % author_info.group(3, 4)
-
-        email_hash = hashlib.md5(email).hexdigest()
-        if req.base_url.startswith("https://"):
-            href = "https://gravatar.com/avatar/" + email_hash
-        else:
-            href = "http://www.gravatar.com/avatar/" + email_hash
-        href += "?size=%s" % size
-        return href
-

userpictures/providers/usermanager.py

-from trac.config import *
-from trac.core import *
-
-try:
-    from tracusermanager.api import UserManager
-except ImportError:
-    class UserManager(object):
-        def __init__(self, env):
-            self.env = env
-        def get_user(self, username):
-            self.env.log.warning("Trying to use UserPicturesUserManagerProvider, but UserManager plugin is not installed!")
-            return None
-
-from userpictures import IUserPicturesProvider
-
-class UserPicturesUserManagerProvider(Component):
-    implements(IUserPicturesProvider)
-
-    def get_src(self, req, username, size):
-        user_manager = UserManager(self.env)
-        user = user_manager.get_user(username)
-        if not user or not user['picture_href']:
-            return req.href.chrome('userpictures/default-portrait.gif')
-        return user['picture_href']
-
-