code:
# -*- coding: utf-8 -*-

"""
 (c) 2014-2015 - Copyright Red Hat Inc

 Authors:
   Pierre-Yves Chibon <pingou@pingoured.fr>

pagure notifications.
"""

import datetime
import hashlib
import json
import urlparse
import re
import smtplib
import time
import warnings

import flask
import requests
import six
import pagure

from email.mime.text import MIMEText

REPLY_MSG = 'To reply, visit the link below'
if pagure.APP.config['EVENTSOURCE_SOURCE']:
    REPLY_MSG += ' or just reply to this email'

def fedmsg_publish(*args, **kwargs):  # pragma: no cover
    ''' Try to publish a message on the fedmsg bus. '''
    # We catch Exception if we want :-p
    # pylint: disable=W0703
    # Ignore message about fedmsg import
    # pylint: disable=F0401
    kwargs['modname'] = 'pagure'
    try:
        import fedmsg
        fedmsg.publish(*args, **kwargs)
    except Exception as err:
        warnings.warn(str(err))

def log(project, topic, msg, redis=None):
    ''' This is the place where we send notifications to user about actions
    occuring in pagure.
    '''
    # Send fedmsg notification (if fedmsg is there and set-up)
    fedmsg_publish(topic, msg)

    if redis:
        redis.publish(
            'pagure.hook',
            json.dumps({
                'project': project.fullname,
                'topic': topic,
                'msg': msg,
            }))

def _add_mentioned_users(emails, comment):
    ''' Check the comment to see if an user is mentioned in it and if
    so add this user to the list of people to notify.
    '''
    MENTION_RE = r'@(\w+)'
    for username in re.findall(MENTION_RE, comment):
        user = pagure.lib.search_user(pagure.SESSION, username=username)
        if user:
            emails.add(user.default_email)
    return emails

def _clean_emails(emails, user):
    ''' Remove the email of the user doing the action if it is in the list.

    This avoids receiving emails about action you do.
    '''
    # Remove the user doing the action from the list of person to email
    if user and user.emails:
        for email in user.emails:
            if email.email in emails:
                emails.remove(email.email)
    return emails

def _get_emails_for_issue(issue):
    ''' Return the list of emails to send notification to when notifying
    about the specified issue.
    '''
    emails = set()
    # Add project creator/owner
    if issue.project.user.default_email:
        emails.add(issue.project.user.default_email)

    # Add project maintainers
    for user in issue.project.users:
        if user.default_email:
            emails.add(user.default_email)

    # Add people in groups with commits access to the project:
    for group in issue.project.groups:
        if group.creator.default_email:
            emails.add(group.creator.default_email)
        for user in group.users:
            if user.default_email:
                emails.add(user.default_email)

    # Add people that commented on the ticket
    for comment in issue.comments:
        if comment.user.default_email:
            emails.add(comment.user.default_email)

    # Add the person that opened the issue
    if issue.user.default_email:
        emails.add(issue.user.default_email)

    # Add the person assigned to the ticket
    if issue.assignee and issue.assignee.default_email:
        emails.add(issue.assignee.default_email)

    # Add the person watching this project, if the issue is public
    if issue.isa == 'issue' and not issue.private:
        for watcher in issue.project.watchers:
            emails.add(watcher.user.default_email)

    # Remove the person list in unwatch
    for unwatcher in issue.project.unwatchers:
        if unwatcher.user.default_email in emails:
            emails.remove(unwatcher.user.default_email)

    # Drop the email used by pagure when sending
    emails = _clean_emails(
        emails, pagure.APP.config.get(pagure.APP.config.get(
            'FROM_EMAIL', 'pagure@fedoraproject.org'))
    )

    return emails

def _build_url(*args):
    ''' Build a URL from a given list of arguments. '''
    items = []
    for idx, arg in enumerate(args):
        arg = str(arg)
        if arg.startswith('/'):
            arg = arg[1:]
        if arg.endswith('/') and not idx + 1 == len(args):
            arg = arg[:-1]
        items.append(arg)

    return '/'.join(items)

def send_email(text, subject, to_mail,
               mail_id=None, in_reply_to=None,
               project_name=None):  # pragma: no cover
    ''' Send an email with the specified information.

    :arg text: the content of the email to send
    :arg subject: the subject of the email
    :arg to_mail: a string representing a list of recipient separated by a
        coma
    :kwarg mail_id: if defined, the header `mail-id` is set with this value
    :kwarg in_reply_to: if defined, the header `In-Reply-To` is set with
        this value
    :kwarg project_name: if defined, the name of the project

    '''
    if not to_mail:
        return

    if not pagure.APP.config.get('EMAIL_SEND', True):
        print '******EMAIL******'
        print 'To: %s' % to_mail
        print 'Subject: %s' % subject
        print 'in_reply_to: %s' % in_reply_to
        print 'mail_id: %s' % mail_id
        print 'Contents:'
        print text.encode('utf-8')
        print '*****/EMAIL******'
        return

    if project_name is not None:
        subject_tag = project_name
    else:
        subject_tag = 'Pagure'

    if pagure.APP.config['SMTP_SSL']:
        smtp = smtplib.SMTP_SSL(pagure.APP.config['SMTP_SERVER'], pagure.APP.config['SMTP_PORT'])
    else:
        smtp = smtplib.SMTP(pagure.APP.config['SMTP_SERVER'], pagure.APP.config['SMTP_PORT'])

    for mailto in to_mail.split(','):
        msg = MIMEText(text.encode('utf-8'), 'plain', 'utf-8')
        msg['Subject'] = '[%s] %s' % (subject_tag, subject)
        from_email = pagure.APP.config.get('FROM_EMAIL', 'pagure@fedoraproject.org')
        msg['From'] = from_email

        if mail_id:
            msg['mail-id'] = mail_id
            msg['Message-Id'] = '<%s>' % mail_id

        if in_reply_to:
            msg['In-Reply-To'] = '<%s>' % in_reply_to

        msg['X-pagure'] = pagure.APP.config['APP_URL']
        if project_name is not None:
            msg['X-pagure-project'] = project_name

        # Send the message via our own SMTP server, but don't include the
        # envelope header.
        msg['To'] = mailto
        salt = pagure.APP.config.get('SALT_EMAIL')
        mhash = hashlib.sha512('<%s>%s%s' % (mail_id, salt, mailto))
        msg['Reply-To'] = 'reply+%s@%s' % (
            mhash.hexdigest(),
            pagure.APP.config['DOMAIN_EMAIL_NOTIFICATIONS'])
        try:
            if pagure.APP.config['SMTP_USERNAME'] and pagure.APP.config['SMTP_PASSWORD']:
                smtp.login(pagure.APP.config['SMTP_USERNAME'], pagure.APP.config['SMTP_PASSWORD'])

            smtp.sendmail(
                from_email,
                [mailto],
                msg.as_string())
        except smtplib.SMTPException as err:
            pagure.LOG.exception(err)
    smtp.quit()
    return msg

def notify_new_comment(comment, user=None):
    ''' Notify the people following an issue that a new comment was added
    to the issue.
    '''

    text = u"""
%s added a new comment to an issue you are following:
``
%s
``

%s
%s
""" % (comment.user.user,
       comment.comment,
       REPLY_MSG,
       _build_url(
           pagure.APP.config['APP_URL'],
           comment.issue.project.name,
           'issue',
           comment.issue.id))
    mail_to = _get_emails_for_issue(comment.issue)
    if comment.user and comment.user.default_email:
        mail_to.add(comment.user.default_email)

    mail_to = _add_mentioned_users(mail_to, comment.comment)
    mail_to = _clean_emails(mail_to, user)

    send_email(
        text,
        'Issue #%s `%s`' % (comment.issue.id, comment.issue.title),
        ','.join(mail_to),
        mail_id=comment.mail_id,
        in_reply_to=comment.issue.mail_id,
        project_name=comment.issue.project.name,
    )

def notify_new_issue(issue, user=None):
    ''' Notify the people following a project that a new issue was added
    to it.
    '''
    text = u"""
%s reported a new issue against the project: `%s` that you are following:
``
%s
``

%s
%s
""" % (issue.user.user,
       issue.project.name,
       issue.content,
       REPLY_MSG,
       _build_url(
           pagure.APP.config['APP_URL'],
           issue.project.name,
           'issue',
           issue.id))
    mail_to = _get_emails_for_issue(issue)
    mail_to = _add_mentioned_users(mail_to, issue.content)
    mail_to = _clean_emails(mail_to, user)

    send_email(
        text,
        'Issue #%s `%s`' % (issue.id, issue.title),
        ','.join(mail_to),
        mail_id=issue.mail_id,
        project_name=issue.project.name,
    )

def notify_assigned_issue(issue, new_assignee, user):
    ''' Notify the people following an issue that the assignee changed.
    '''
    action = 'reset'
    if new_assignee:
        action = 'assigned to `%s`' % new_assignee.user
    text = u"""
The issue: `%s` of project: `%s` has been %s by %s.

%s
""" % (issue.title,
       issue.project.name,
       action,
       user.username,
       _build_url(
           pagure.APP.config['APP_URL'],
           issue.project.name,
           'issue',
           issue.id))
    mail_to = _get_emails_for_issue(issue)
    if new_assignee and new_assignee.default_email:
        mail_to.add(new_assignee.default_email)

    mail_to = _clean_emails(mail_to, user)

    uid = time.mktime(datetime.datetime.now().timetuple())
    send_email(
        text,
        'Issue #%s `%s`' % (issue.id, issue.title),
        ','.join(mail_to),
        mail_id='%s/assigned/%s' % (issue.mail_id, uid),
        in_reply_to=issue.mail_id,
        project_name=issue.project.name,
    )

def notify_assigned_request(request, new_assignee, user):
    ''' Notify the people following a pull-request that the assignee changed.
    '''
    action = 'reset'
    if new_assignee:
        action = 'assigned to `%s`' % new_assignee.user
    text = u"""
The pull-request: `%s` of project: `%s` has been %s by %s.

%s
""" % (request.title,
       request.project.name,
       action,
       user.username,
       _build_url(
           pagure.APP.config['APP_URL'],
           request.project.name,
           'pull-request',
           request.id))
    mail_to = _get_emails_for_issue(request)
    if new_assignee and new_assignee.default_email:
        mail_to.add(new_assignee.default_email)

    mail_to = _clean_emails(mail_to, user)

    uid = time.mktime(datetime.datetime.now().timetuple())
    send_email(
        text,
        'PR #%s `%s`' % (request.id, request.title),
        ','.join(mail_to),
        mail_id='%s/assigned/%s' % (request.mail_id, uid),
        in_reply_to=request.mail_id,
        project_name=request.project.name,
    )

def notify_new_pull_request(request):
    ''' Notify the people following a project that a new pull-request was
    added to it.
    '''
    text = u"""
%s opened a new pull-request against the project: `%s` that you are following:
``
%s
``

%s
%s
""" % (request.user.user,
       request.project.name,
       request.title,
       REPLY_MSG,
       _build_url(
           pagure.APP.config['APP_URL'],
           request.project.name,
           'pull-request',
           request.id))
    mail_to = _get_emails_for_issue(request)

    send_email(
        text,
        'PR #%s `%s`' % (request.id, request.title),
        ','.join(mail_to),
        mail_id=request.mail_id,
        project_name=request.project.name,
    )

def notify_merge_pull_request(request, user):
    ''' Notify the people following a project that a pull-request was merged
    in it.
    '''
    text = u"""
%s merged a pull-request against the project: `%s` that you are following.

Merged pull-request:

``
%s
``

%s
""" % (user.username,
       request.project.name,
       request.title,
       _build_url(
           pagure.APP.config['APP_URL'],
           request.project.name,
           'pull-request',
           request.id))
    mail_to = _get_emails_for_issue(request)

    uid = time.mktime(datetime.datetime.now().timetuple())
    send_email(
        text,
        'PR #%s `%s`' % (request.id, request.title),
        ','.join(mail_to),
        mail_id='%s/close/%s' % (request.mail_id, uid),
        in_reply_to=request.mail_id,
        project_name=request.project.name,
    )

def notify_cancelled_pull_request(request, user):
    ''' Notify the people following a project that a pull-request was
    cancelled in it.
    '''
    text = u"""
%s canceled a pull-request against the project: `%s` that you are following.

Cancelled pull-request:

``
%s
``

%s
""" % (user.username,
       request.project.name,
       request.title,
       _build_url(
           pagure.APP.config['APP_URL'],
           request.project.name,
           'pull-request',
           request.id))
    mail_to = _get_emails_for_issue(request)

    uid = time.mktime(datetime.datetime.now().timetuple())
    send_email(
        text,
        'PR #%s `%s`' % (request.id, request.title),
        ','.join(mail_to),
        mail_id='%s/close/%s' % (request.mail_id, uid),
        in_reply_to=request.mail_id,
        project_name=request.project.name,
    )

def notify_pull_request_comment(comment, user):
    ''' Notify the people following a pull-request that a new comment was
    added to it.
    '''
    text = u"""
%s commented on the pull-request: `%s` that you are following:
``
%s
``

%s
%s
""" % (comment.user.user,
       comment.pull_request.title,
       comment.comment,
       REPLY_MSG,
       _build_url(
           pagure.APP.config['APP_URL'],
           comment.pull_request.project.name,
           'pull-request',
           comment.pull_request.id))
    mail_to = _get_emails_for_issue(comment.pull_request)
    mail_to = _add_mentioned_users(mail_to, comment.comment)
    mail_to = _clean_emails(mail_to, user)

    send_email(
        text,
        'PR #%s `%s`' % (comment.pull_request.id, comment.pull_request.title),
        ','.join(mail_to),
        mail_id=comment.mail_id,
        in_reply_to=comment.pull_request.mail_id,
        project_name=comment.pull_request.project.name,
    )

def notify_new_email(email, user):
    ''' Ask the user to confirm to the email belong to them.
    '''

    root_url = pagure.APP.config.get('APP_URL', flask.request.url_root)

    url = urlparse.urljoin(
        root_url or flask.request.url_root,
        flask.url_for('confirm_email', token=email.token),
    )

    text = u"""Dear %(username)s,

You have registered a new email on pagure at %(root_url)s.

To finish your validate this registration, please click on the following
link or copy/paste it in your browser, this link will remain valid only 2 days:
  %(url)s

The email will not be activated until you finish this step.

Sincerely,
Your pagure admin.
""" % ({'username': user.username, 'url': url, 'root_url': root_url})

    send_email(
        text,
        'Confirm new email',
        email.email,
    )


code:
# -*- coding: utf-8 -*-
import os
import flask
from sqlalchemy.orm import scoped_session, sessionmaker
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy import create_engine

from pagure.hooks import jenkins_hook
import pagure.lib
from pagure.lib import model
from pagure import APP, SESSION
import pagure.exceptions

import json
import logging

import requests
import jenkins

APP.logger.setLevel(logging.INFO)

PAGURE_URL = '{base}api/0/{repo}/pull-request/{pr}/flag'
JENKINS_TRIGGER_URL = '{base}job/{project}/buildWithParameters'

def process_pr(logger, cfg, pr_id, repo, branch):
    ''' Process the pull-request, it POST the
    data to the Jenkins URL to trigger build.
    '''
    if cfg.active:
        post_data(logger,
                  JENKINS_TRIGGER_URL.format(
                      base=cfg.jenkins_url, project=cfg.jenkins_name),
                  {'token': cfg.jenkins_token,
                   'cause': pr_id,
                   'REPO': repo,
                   'BRANCH': branch})
    else:
        raise pagure.exceptions.HookInactiveException(cfg.pagure_name)

def process_build(logger, cfg, build_id):
    '''  Gets the build info from jenkins
    and flags that particular pull-request.
    '''
    if cfg.active:
        jenk = jenkins.Jenkins(cfg.jenkins_url)
        build_info = jenk.get_build_info(cfg.jenkins_name, build_id)
        result = build_info['result']
        url = build_info['url']

        pr_id = None

        for action in build_info['actions']:
            for cause in action.get('causes', []):
                try:
                    pr_id = int(cause['note'])
                except (KeyError, ValueError):
                    continue

        if not pr_id:
            logger.info('Not a PR check')
            return

        # Comment in Pagure
        logger.info('Updating %s PR %d: %s', cfg.pagure_name, pr_id, result)
        try:
            pagure_ci_flag(logger,
                        username=cfg.display_name,
                        repo=cfg.pagure_name,
                        requestid=pr_id,
                        result=result,
                        url=url)

        except KeyError as exc:
            logger.warning('Unknown build status', exc_info=exc)
    else:
        raise pagure.exceptions.HookInactiveException(cfg.pagure_name)

def post_data(logger, *args, **kwargs):
    ''' POST data to jenkins and reports a response. '''
    resp = requests.post(*args, **kwargs)
    logger.debug('Received response status %s', resp.status_code)
    if resp.status_code < 200 or resp.status_code >= 300:
        logger.error('Network request failed: %d: %s',
                     resp.status_code, resp.text)

def pagure_ci_flag(logger, repo, username, url, result, requestid):
    ''' Flag the pull-request in pagure. '''

    comment, percent = {
        'SUCCESS': ('Build successful', 100),
        'FAILURE': ('Build failed', 0),
    }[result]

    repo = pagure.lib.get_project(SESSION, repo, user=None)
    output = {}

    if repo is None:
        raise pagure.exceptions.FileNotFoundException('Repo not found')

    request = pagure.lib.search_pull_requests(
        SESSION, project_id=repo.id, requestid=requestid)

    if not request:
        raise pagure.exceptions.FileNotFoundException('Request not found')

    try:
        message = pagure.lib.add_pull_request_flag(
            SESSION,
            request=request,
            username=username,
            percent=percent,
            comment=comment,
            url=url,
            uid=None,
            user=repo.user.username,
            requestfolder=APP.config['REQUESTS_FOLDER'],
        )
        SESSION.commit()
        logger.debug('Received response status: %s', message)
        output['message'] = message

    except SQLAlchemyError as err:  # pragma: no cover
        logger.exception(err)
        SESSION.rollback()

repo_name: bill/auger

license: mit


code:
# -*- coding: utf-8 -*-

"""
 (c) 2015 - Copyright Red Hat Inc

 Authors:
   Pierre-Yves Chibon <pingou@pingoured.fr>

"""

import pygit2

import pagure
import pagure.exceptions

def get_pygit2_version():
    ''' Return pygit2 version as a tuple of integers.
    This is needed for correct version comparison.
    '''
    return tuple([int(i) for i in pygit2.__version__.split('.')])

class PagureRepo(pygit2.Repository):
    """ An utility class allowing to go around pygit2's inability to be
    stable.

    """

    @staticmethod
    def push(remote, refname):
        """ Push the given reference to the specified remote. """
        pygit2_version = get_pygit2_version()
        if pygit2_version >= (0, 22):
            remote.push([refname])
        else:
            remote.push(refname)

    def pull(self, remote_name='origin', branch='master', force=False):
        ''' pull changes for the specified remote (defaults to origin).

        Code from MichaelBoselowitz at:
        https://github.com/MichaelBoselowitz/pygit2-examples/blob/
            68e889e50a592d30ab4105a2e7b9f28fac7324c8/examples.py#L58
        licensed under the MIT license.
        '''

        for remote in self.remotes:
            if remote.name == remote_name:
                remote.fetch()
                remote_master_id = self.lookup_reference(
                    'refs/remotes/origin/%s' % branch).target

                if force:
                    repo_branch = self.lookup_reference(
                        'refs/heads/%s' % branch)
                    repo_branch.set_target(remote_master_id)

                merge_result, _ = self.merge_analysis(remote_master_id)
                # Up to date, do nothing
                if merge_result & pygit2.GIT_MERGE_ANALYSIS_UP_TO_DATE:
                    return
                # We can just fastforward
                elif merge_result & pygit2.GIT_MERGE_ANALYSIS_FASTFORWARD:
                    self.checkout_tree(self.get(remote_master_id))
                    master_ref = self.lookup_reference(
                        'refs/heads/%s' % branch)
                    master_ref.set_target(remote_master_id)
                    self.head.set_target(remote_master_id)
                elif merge_result & pygit2.GIT_MERGE_ANALYSIS_NORMAL:
                    raise pagure.exceptions.GitConflictsException(
                        'Pulling remote changes leads to a conflict')
                else:
                    pagure.LOG.debug(
                        'Un-expected merge result: %s' % (
                        pygit2.GIT_MERGE_ANALYSIS_NORMAL))
                    raise AssertionError('Unknown merge analysis result')


code:
# -*- coding: utf-8 -*-

"""
 (c) 2014 - Copyright Red Hat Inc

 Authors:
   Pierre-Yves Chibon <pingou@pingoured.fr>

"""

# # pylint cannot import flask extension correctly
# pylint: disable=E0611,F0401
# # The forms here don't have specific methods, they just inherit them.
# pylint: disable=R0903
# # We apparently use old style super in our __init__
# pylint: disable=E1002
# # Couple of our forms do not even have __init__
# pylint: disable=W0232

from flask.ext import wtf
import wtforms

from pagure.forms import ConfirmationForm

def same_password(form, field):
    ''' Check if the data in the field is the same as in the password field.
    '''
    if field.data != form.password.data:
        raise wtforms.validators.ValidationError('Both password fields should be equal')

class LostPasswordForm(wtf.Form):
    """ Form to ask for a password change. """
    username = wtforms.TextField(
        'username  *',
        [wtforms.validators.Required()]
    )

class ResetPasswordForm(wtf.Form):
    """ Form to reset one's password in the local database. """
    password = wtforms.PasswordField(
        'Password  *',
        [wtforms.validators.Required()]
    )
    confirm_password = wtforms.PasswordField(
        'Confirm password  *',
        [wtforms.validators.Required(), same_password]
    )

class LoginForm(wtf.Form):
    """ Form to login via the local database. """
    username = wtforms.TextField(
        'username  *',
        [wtforms.validators.Required()]
    )
    password = wtforms.PasswordField(
        'Password  *',
        [wtforms.validators.Required()]
    )

class NewUserForm(wtf.Form):
    """ Form to add a new user to the local database. """
    user = wtforms.TextField(
        'username  *',
        [wtforms.validators.Required()]
    )
    fullname = wtforms.TextField(
        'Full name',
        [wtforms.validators.Optional()]
    )
    email_address = wtforms.TextField(
        'Email address  *',
        [wtforms.validators.Required(), wtforms.validators.Email()]
    )
    password = wtforms.PasswordField(
        'Password  *',
        [wtforms.validators.Required()]
    )
    confirm_password = wtforms.PasswordField(
        'Confirm password  *',
        [wtforms.validators.Required(), same_password]
    )

class ChangePasswordForm(wtf.Form):
    """ Form to reset one's password in the local database. """
    old_password = wtforms.PasswordField(
        'Old Password  *',
        [wtforms.validators.Required()]
    )
    password = wtforms.PasswordField(
        'Password  *',
        [wtforms.validators.Required()]
    )
    confirm_password = wtforms.PasswordField(
        'Confirm password  *',
        [wtforms.validators.Required(), same_password]
    )


code:
# -*- coding: utf-8 -*-
#
# Copyright © 2014  Red Hat, Inc.
#
# This copyrighted material is made available to anyone wishing to use,
# modify, copy, or redistribute it subject to the terms and conditions
# of the GNU General Public License v.2, or (at your option) any later
# version.  This program is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY expressed or implied, including the
# implied warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR
# PURPOSE.  See the GNU General Public License for more details.  You
# should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# Any Red Hat trademarks that are incorporated in the source
# code or documentation are not subject to the GNU General Public
# License and may only be used or replicated with the express permission
# of Red Hat, Inc.
#

'''
Mail handler for logging.
'''
import logging
import logging.handlers

import inspect
import os
import socket
import traceback

import flask

psutil = None
try:
    import psutil
except (OSError, ImportError):  # pragma: no cover
    # We run into issues when trying to import psutil from inside mod_wsgi on
    # rhel7.  If we hit that here, then just fail quietly.
    # https://github.com/jmflinuxtx/kerneltest-harness/pull/17#issuecomment-48007837
    pass

class ContextInjector(logging.Filter):  # pragma: no cover
    """ Logging filter that adds context to log records.

    Filters are typically used to "filter" log records.  They declare a filter
    method that can return True or False.  Only records with 'True' will
    actually be logged.

    Here, we somewhat abuse the concept of a filter.  We always return true,
    but we use the opportunity to hang important contextual information on the
    log record to later be used by the logging Formatter.  We don't normally
    want to see all this stuff in normal log records, but we *do* want to see
    it when we are emailed error messages.  Seeing an error, but not knowing
    which host it comes from, is not that useful.

    http://docs.python.org/2/howto/logging-cookbook.html#filters-contextual

    This code has been originally written by Ralph Bean for the fedmsg
    project:
        https://github.com/fedora-infra/fedmsg/
    and can be found at:
        https://infrastructure.fedoraproject.org/cgit/ansible.git/tree/roles/fedmsg/base/templates/logging.py.j2

    """

    def filter(self, record):
        """ Set up additional information on the record object. """
        current_process = ContextInjector.get_current_process()
        current_hostname = socket.gethostname()

        record.host = current_hostname
        record.proc = current_process
        record.pid = '-'
        if not isinstance(current_process, str):
            record.pid = current_process.pid
            # Be compatible with python-psutil 1.0 and 2.0, 3.0
            proc_name = current_process.name
            if callable(proc_name):
                proc_name = proc_name()
            record.proc_name = proc_name
            # Be compatible with python-psutil 1.0 and 2.0, 3.0
            cmd_line = current_process.cmdline
            if callable(cmd_line):
                cmd_line = cmd_line()
            record.command_line = " ".join(cmd_line)

        record.callstack = self.format_callstack()

        record.url = '-'
        record.args = '-'
        record.form = '-'
        record.username = '-'
        try:
            record.url = flask.request.url
        except RuntimeError:
            pass
        try:
            record.args = flask.request.args
        except RuntimeError:
            pass
        try:
            record.form = dict(flask.request.form)
            if 'csrf_token' in record.form:
                record.form['csrf_token'] = 'Was present, is cleaned up'
        except RuntimeError:
            pass
        try:
            record.username = flask.g.fas_user.username
        except:
            pass

        return True

    @staticmethod
    def format_callstack():
        """ Format the callstack to find out the stack trace. """
        ind = 0
        for ind, frame in enumerate(f[0] for f in inspect.stack()):
            if '__name__' not in frame.f_globals:
                continue
            modname = frame.f_globals['__name__'].split('.')[0]
            if modname != "logging":
                break

        def _format_frame(frame):
            """ Format the frame. """
            return '  File "%s", line %i in %s\n    %s' % (frame)

        stack = traceback.extract_stack()
        stack = stack[:-ind]
        return "\n".join([_format_frame(frame) for frame in stack])

    @staticmethod
    def get_current_process():
        """ Return the current process (PID). """
        mypid = os.getpid()

        if not psutil:
            return "Could not import psutil for %r" % mypid

        for proc in psutil.process_iter():
            if proc.pid == mypid:
                return proc

        # This should be impossible.
        raise ValueError("Could not find process %r" % mypid)

MSG_FORMAT = """Process Details
---------------
host:     %(host)s
PID:      %(pid)s
name:     %(proc_name)s
command:  %(command_line)s

Message type:       %(levelname)s
Location:           %(pathname)s:%(lineno)d
Module:             %(module)s
Function:           %(funcName)s
Time:               %(asctime)s

URL:    %(url)s
args:   %(args)s
form:   %(form)s
user:   %(username)s

Message:
--------

%(message)s

Callstack that lead to the logging statement
--------------------------------------------
%(callstack)s
"""

def get_mail_handler(smtp_server, mail_admin):
    """ Set up the handler sending emails for big exception
    """
    mail_handler = logging.handlers.SMTPHandler(
        smtp_server,
        'nobody@fedoraproject.org',
        mail_admin,
        'Pagure error')
    mail_handler.setFormatter(logging.Formatter(MSG_FORMAT))
    mail_handler.setLevel(logging.ERROR)
    mail_handler.addFilter(ContextInjector())
    return mail_handler


code:
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
# USA.

""" Pagure-flavored Markdown

Author: Ralph Bean <rbean@redhat.com>
        Pierre-Yves Chibon <pingou@pingoured.fr>
"""

import re

import flask

import markdown.inlinepatterns
import markdown.util

import pagure
import pagure.lib

MENTION_RE = r'@(\w+)'
EXPLICIT_FORK_ISSUE_RE = r'(\w+)/(\w+)#([0-9]+)'
EXPLICIT_MAIN_ISSUE_RE = r'[^|\w](?<!\/)(\w+)#([0-9]+)'
IMPLICIT_ISSUE_RE = r'[^|\w](?<!\w)#([0-9]+)'
IMPLICIT_PR_RE = r'[^|\w](?<!\w)PR#([0-9]+)'

class MentionPattern(markdown.inlinepatterns.Pattern):
    """ @user pattern class. """

    def handleMatch(self, m):
        """ When the pattern matches, update the text. """
        name = markdown.util.AtomicString(m.group(2))
        text = ' @%s' % name
        user = pagure.lib.search_user(pagure.SESSION, username=name)
        if not user:
            return text

        element = markdown.util.etree.Element("a")
        url = flask.url_for('view_user', username=name)
        element.set('href', url)
        element.text = text
        return element

class ExplicitForkIssuePattern(markdown.inlinepatterns.Pattern):
    """ Explicit fork issue pattern. """

    def handleMatch(self, m):
        """ When the pattern matches, update the text. """
        user = markdown.util.AtomicString(m.group(2))
        repo = markdown.util.AtomicString(m.group(3))
        idx = markdown.util.AtomicString(m.group(4))
        text = '%s/%s#%s' % (user, repo, idx)

        issue = _issue_exists(user, repo, idx)
        if not issue:
            return text

        return _obj_anchor_tag(user, repo, issue, text)

class ExplicitMainIssuePattern(markdown.inlinepatterns.Pattern):
    """ Explicit issue pattern (for non-fork project). """

    def handleMatch(self, m):
        """ When the pattern matches, update the text. """
        repo = markdown.util.AtomicString(m.group(2))
        idx = markdown.util.AtomicString(m.group(3))
        text = ' %s#%s' % (repo, idx)

        issue = _issue_exists(None, repo, idx)
        if not issue:
            return text

        return _obj_anchor_tag(None, repo, issue, text)

class ImplicitIssuePattern(markdown.inlinepatterns.Pattern):
    """ Implicit issue pattern. """

    def handleMatch(self, m):
        """ When the pattern matches, update the text. """
        idx = markdown.util.AtomicString(m.group(2))
        text = ' #%s' % idx

        try:
            root = flask.request.url_root
            url = flask.request.url
        except RuntimeError:
            return text
        repo = user = None

        if flask.request.args.get('user'):
            user = flask.request.args.get('user')
        if flask.request.args.get('repo'):
            repo = flask.request.args.get('repo')

        if not user and not repo:
            if 'fork/' in url:
                user, repo = url.split('fork/')[1].split('/', 2)[:2]
            else:
                repo = url.split(root)[1].split('/', 1)[0]

        issue = _issue_exists(user, repo, idx)
        if issue:
            return _obj_anchor_tag(user, repo, issue, text)

        request = _pr_exists(user, repo, idx)
        if request:
            return _obj_anchor_tag(user, repo, request, text)

        return text

class ImplicitPRPattern(markdown.inlinepatterns.Pattern):
    """ Implicit pull-request pattern. """

    def handleMatch(self, m):
        """ When the pattern matches, update the text. """
        idx = markdown.util.AtomicString(m.group(2))
        text = ' PR#%s' % idx

        try:
            root = flask.request.url_root
            url = flask.request.url
        except RuntimeError:
            return text
        repo = user = None

        if flask.request.args.get('user'):
            user = flask.request.args.get('user')
        if flask.request.args.get('repo'):
            repo = flask.request.args.get('repo')

        if not user and not repo:
            if 'fork/' in url:
                user, repo = url.split('fork/')[1].split('/', 2)[:2]
            else:
                repo = url.split(root)[1].split('/', 1)[0]

        issue = _issue_exists(user, repo, idx)
        if issue:
            return _obj_anchor_tag(user, repo, issue, text)

        request = _pr_exists(user, repo, idx)
        if request:
            return _obj_anchor_tag(user, repo, request, text)

        return text

class PagureExtension(markdown.extensions.Extension):

    def extendMarkdown(self, md, md_globals):
        # First, make it so that bare links get automatically linkified.
        markdown.inlinepatterns.AUTOLINK_RE = '(%s)' % '|'.join([
            r'<(?:f|ht)tps?://[^>]*>',
            r'\b(?:f|ht)tps?://[^)<>\s]+[^.,)<>\s]',
        ])

        md.inlinePatterns['mention'] = MentionPattern(MENTION_RE)
        if pagure.APP.config.get('ENABLE_TICKETS', True):
            md.inlinePatterns['implicit_pr'] = \
                ImplicitPRPattern(IMPLICIT_PR_RE)
            md.inlinePatterns['explicit_fork_issue'] = \
                ExplicitForkIssuePattern(EXPLICIT_FORK_ISSUE_RE)
            md.inlinePatterns['explicit_main_issue'] = \
                ExplicitMainIssuePattern(EXPLICIT_MAIN_ISSUE_RE)
            md.inlinePatterns['implicit_issue'] = \
                ImplicitIssuePattern(IMPLICIT_ISSUE_RE)

        md.registerExtension(self)

def makeExtension(*arg, **kwargs):
    return PagureExtension(**kwargs)

def _issue_exists(user, repo, idx):
    """ Utility method checking if a given issue exists. """
    repo_obj = pagure.lib.get_project(
        pagure.SESSION, name=repo, user=user)
    if not repo_obj:
        return False

    issue_obj = pagure.lib.search_issues(
        pagure.SESSION, repo=repo_obj, issueid=idx)
    if not issue_obj:
        return False

    return issue_obj

def _pr_exists(user, repo, idx):
    """ Utility method checking if a given PR exists. """
    repo_obj = pagure.lib.get_project(
        pagure.SESSION, name=repo, user=user)
    if not repo_obj:
        return False

    pr_obj = pagure.lib.search_pull_requests(
        pagure.SESSION, project_id=repo_obj.id, requestid=idx)
    if not pr_obj:
        return False

    return pr_obj

def _obj_anchor_tag(user, repo, obj, text):
    """ Utility method generating the link to an issue or a PR. """
    if obj.isa == 'issue':
        url = flask.url_for(
            'view_issue', username=user, repo=repo, issueid=obj.id)
    else:
        url = flask.url_for(
            'request_pull', username=user, repo=repo, requestid=obj.id)

    element = markdown.util.etree.Element("a")
    element.set('href', url)
    element.set('title', obj.title)
    element.text = text
    return element


code:
# -*- coding: utf-8 -*-
#
# Copyright © 2014  Red Hat, Inc.
#
# This copyrighted material is made available to anyone wishing to use,
# modify, copy, or redistribute it subject to the terms and conditions
# of the GNU General Public License v.2, or (at your option) any later
# version.  This program is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY expressed or implied, including the
# implied warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR
# PURPOSE.  See the GNU General Public License for more details.  You
# should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# Any Red Hat trademarks that are incorporated in the source
# code or documentation are not subject to the GNU General Public
# License and may only be used or replicated with the express permission
# of Red Hat, Inc.
#

'''
Makes pagure an application behind a reverse proxy and thus ensure the
redirects are using ``https``.

Source: http://flask.pocoo.org/snippets/35/ by Peter Hansen
'''

class ReverseProxied(object):  # pragma: no cover
    '''Wrap the application in this middleware and configure the
    front-end server to add these headers, to let you quietly bind
    this to a URL other than / and to an HTTP scheme that is
    different than what is used locally.

    In nginx:
    location /myprefix {
        proxy_pass http://192.168.0.1:5001;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Scheme $scheme;
        proxy_set_header X-Script-Name /myprefix;
        }
    In apache:
        RequestHeader set X-Forwarded-Scheme https early
        RequestHeader set X-Scheme https early
        RequestHeader add X-Script-Name /myprefix early
        RequestHeader set X-Forwarded-Proto https early

    :param app: the WSGI application
    '''
    def __init__(self, app):
        self.app = app

    def __call__(self, environ, start_response):
        script_name = environ.get('HTTP_X_SCRIPT_NAME', '')
        if script_name:
            environ['SCRIPT_NAME'] = script_name
            path_info = environ['PATH_INFO']
            if path_info.startswith(script_name):
                environ['PATH_INFO'] = path_info[len(script_name):]

        server = environ.get('HTTP_X_FORWARDED_HOST', '')
        if server:
            environ['HTTP_HOST'] = server

        scheme = environ.get('HTTP_X_SCHEME', '')
        if scheme:
            environ['wsgi.url_scheme'] = scheme

        return self.app(environ, start_response)


code: .atwho-view{position:absolute;top:0;left:0;display:none;margin-top:18px;background:#fff;color:#000;border:1px solid #DDD;border-radius:3px;box-shadow:0 0 5px rgba(0,0,0,.1);min-width:120px;z-index:11110!important}.atwho-view .atwho-header{padding:5px;margin:5px;cursor:pointer;border-bottom:solid 1px #eaeff1;color:#6f8092;font-size:11px;font-weight:700}.atwho-view .atwho-header .small{color:#6f8092;float:right;padding-top:2px;margin-right:-5px;font-size:12px;font-weight:400}.atwho-view .atwho-header:hover{cursor:default}.atwho-view .cur{background:#36F;color:#fff}.atwho-view .cur small{color:#fff}.atwho-view strong{color:#36F}.atwho-view .cur strong{color:#fff;font:700}.atwho-view ul{list-style:none;padding:0;margin:auto;max-height:200px;overflow-y:auto}.atwho-view ul li{display:block;padding:5px 10px;border-bottom:1px solid #DDD;cursor:pointer}.atwho-view small{font-size:smaller;color:#777;font-weight:400}


code:
/* BASICS */

.CodeMirror {
  /* Set height, width, borders, and global font properties here */
  font-family: monospace;
  height: 300px;
  color: black;
}

/* PADDING */

.CodeMirror-lines {
  padding: 4px 0; /* Vertical padding around content */
}
.CodeMirror pre {
  padding: 0 4px; /* Horizontal padding of content */
}

.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
  background-color: white; /* The little square between H and V scrollbars */
}

/* GUTTER */

.CodeMirror-gutters {
  border-right: 1px solid #ddd;
  background-color: #f7f7f7;
  white-space: nowrap;
}
.CodeMirror-linenumbers {}
.CodeMirror-linenumber {
  padding: 0 3px 0 5px;
  min-width: 20px;
  text-align: right;
  color: #999;
  white-space: nowrap;
}

.CodeMirror-guttermarker { color: black; }
.CodeMirror-guttermarker-subtle { color: #999; }

/* CURSOR */

.CodeMirror-cursor {
  border-left: 1px solid black;
  border-right: none;
  width: 0;
}
/* Shown when moving in bi-directional text */
.CodeMirror div.CodeMirror-secondarycursor {
  border-left: 1px solid silver;
}
.cm-fat-cursor .CodeMirror-cursor {
  width: auto;
  border: 0;
  background: #7e7;
}
.cm-fat-cursor div.CodeMirror-cursors {
  z-index: 1;
}

.cm-animate-fat-cursor {
  width: auto;
  border: 0;
  -webkit-animation: blink 1.06s steps(1) infinite;
  -moz-animation: blink 1.06s steps(1) infinite;
  animation: blink 1.06s steps(1) infinite;
  background-color: #7e7;
}
@-moz-keyframes blink {
  0% {}
  50% { background-color: transparent; }
  100% {}
}
@-webkit-keyframes blink {
  0% {}
  50% { background-color: transparent; }
  100% {}
}
@keyframes blink {
  0% {}
  50% { background-color: transparent; }
  100% {}
}

/* Can style cursor different in overwrite (non-insert) mode */
.CodeMirror-overwrite .CodeMirror-cursor {}

.cm-tab { display: inline-block; text-decoration: inherit; }

.CodeMirror-ruler {
  border-left: 1px solid #ccc;
  position: absolute;
}

/* DEFAULT THEME */

.cm-s-default .cm-header {color: blue;}
.cm-s-default .cm-quote {color: #090;}
.cm-negative {color: #d44;}
.cm-positive {color: #292;}
.cm-header, .cm-strong {font-weight: bold;}
.cm-em {font-style: italic;}
.cm-link {text-decoration: underline;}
.cm-strikethrough {text-decoration: line-through;}

.cm-s-default .cm-keyword {color: #708;}
.cm-s-default .cm-atom {color: #219;}
.cm-s-default .cm-number {color: #164;}
.cm-s-default .cm-def {color: #00f;}
.cm-s-default .cm-variable,
.cm-s-default .cm-punctuation,
.cm-s-default .cm-property,
.cm-s-default .cm-operator {}
.cm-s-default .cm-variable-2 {color: #05a;}
.cm-s-default .cm-variable-3 {color: #085;}
.cm-s-default .cm-comment {color: #a50;}
.cm-s-default .cm-string {color: #a11;}
.cm-s-default .cm-string-2 {color: #f50;}
.cm-s-default .cm-meta {color: #555;}
.cm-s-default .cm-qualifier {color: #555;}
.cm-s-default .cm-builtin {color: #30a;}
.cm-s-default .cm-bracket {color: #997;}
.cm-s-default .cm-tag {color: #170;}
.cm-s-default .cm-attribute {color: #00c;}
.cm-s-default .cm-hr {color: #999;}
.cm-s-default .cm-link {color: #00c;}

.cm-s-default .cm-error {color: #f00;}
.cm-invalidchar {color: #f00;}

.CodeMirror-composing { border-bottom: 2px solid; }

/* Default styles for common addons */

div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
.CodeMirror-activeline-background {background: #e8f2ff;}

/* STOP */

/* The rest of this file contains styles related to the mechanics of
   the editor. You probably shouldn't touch them. */

.CodeMirror {
  position: relative;
  overflow: hidden;
  background: white;
}

.CodeMirror-scroll {
  overflow: scroll !important; /* Things will break if this is overridden */
  /* 30px is the magic margin used to hide the element's real scrollbars */
  /* See overflow: hidden in .CodeMirror */
  margin-bottom: -30px; margin-right: -30px;
  padding-bottom: 30px;
  height: 100%;
  outline: none; /* Prevent dragging from highlighting the element */
  position: relative;
}
.CodeMirror-sizer {
  position: relative;
  border-right: 30px solid transparent;
}

/* The fake, visible scrollbars. Used to force redraw during scrolling
   before actuall scrolling happens, thus preventing shaking and
   flickering artifacts. */
.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
  position: absolute;
  z-index: 6;
  display: none;
}
.CodeMirror-vscrollbar {
  right: 0; top: 0;
  overflow-x: hidden;
  overflow-y: scroll;
}
.CodeMirror-hscrollbar {
  bottom: 0; left: 0;
  overflow-y: hidden;
  overflow-x: scroll;
}
.CodeMirror-scrollbar-filler {
  right: 0; bottom: 0;
}
.CodeMirror-gutter-filler {
  left: 0; bottom: 0;
}

.CodeMirror-gutters {
  position: absolute; left: 0; top: 0;
  z-index: 3;
}
.CodeMirror-gutter {
  white-space: normal;
  height: 100%;
  display: inline-block;
  margin-bottom: -30px;
  /* Hack to make IE7 behave */
  *zoom:1;
  *display:inline;
}
.CodeMirror-gutter-wrapper {
  position: absolute;
  z-index: 4;
  background: none !important;
  border: none !important;
}
.CodeMirror-gutter-background {
  position: absolute;
  top: 0; bottom: 0;
  z-index: 4;
}
.CodeMirror-gutter-elt {
  position: absolute;
  cursor: default;
  z-index: 4;
}
.CodeMirror-gutter-wrapper {
  -webkit-user-select: none;
  -moz-user-select: none;
  user-select: none;
}

.CodeMirror-lines {
  cursor: text;
  min-height: 1px; /* prevents collapsing before first draw */
}
.CodeMirror pre {
  /* Reset some styles that the rest of the page might have set */
  -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
  border-width: 0;
  background: transparent;
  font-family: inherit;
  font-size: inherit;
  margin: 0;
  white-space: pre;
  word-wrap: normal;
  line-height: inherit;
  color: inherit;
  z-index: 2;
  position: relative;
  overflow: visible;
  -webkit-tap-highlight-color: transparent;
}
.CodeMirror-wrap pre {
  word-wrap: break-word;
  white-space: pre-wrap;
  word-break: normal;
}

.CodeMirror-linebackground {
  position: absolute;
  left: 0; right: 0; top: 0; bottom: 0;
  z-index: 0;
}

.CodeMirror-linewidget {
  position: relative;
  z-index: 2;
  overflow: auto;
}

.CodeMirror-widget {}

.CodeMirror-code {
  outline: none;
}

/* Force content-box sizing for the elements where we expect it */
.CodeMirror-scroll,
.CodeMirror-sizer,
.CodeMirror-gutter,
.CodeMirror-gutters,
.CodeMirror-linenumber {
  -moz-box-sizing: content-box;
  box-sizing: content-box;
}

.CodeMirror-measure {
  position: absolute;
  width: 100%;
  height: 0;
  overflow: hidden;
  visibility: hidden;
}

.CodeMirror-cursor { position: absolute; }
.CodeMirror-measure pre { position: static; }

div.CodeMirror-cursors {
  visibility: hidden;
  position: relative;
  z-index: 3;
}
div.CodeMirror-dragcursors {
  visibility: visible;
}

.CodeMirror-focused div.CodeMirror-cursors {
  visibility: visible;
}

.CodeMirror-selected { background: #d9d9d9; }
.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
.CodeMirror-crosshair { cursor: crosshair; }
.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }

.cm-searching {
  background: #ffa;
  background: rgba(255, 255, 0, .4);
}

/* IE7 hack to prevent it from returning funny offsetTops on the spans */
.CodeMirror span { *vertical-align: text-bottom; }

/* Used to force a border model for a node */
.cm-force-border { padding-right: .1px; }

@media print {
  /* Hide the cursor when printing */
  .CodeMirror div.CodeMirror-cursors {
    visibility: hidden;
  }
}

/* See issue #2901 */
.cm-tab-wrap-hack:after { content: ''; }

/* Help users use markselection to safely style text background */
span.CodeMirror-selectedtext { background: none; }


code:
/*
Solarized theme for code-mirror
http://ethanschoonover.com/solarized
*/

/*
Solarized color pallet
http://ethanschoonover.com/solarized/img/solarized-palette.png
*/

.solarized.base03 { color: #002b36; }
.solarized.base02 { color: #073642; }
.solarized.base01 { color: #586e75; }
.solarized.base00 { color: #657b83; }
.solarized.base0 { color: #839496; }
.solarized.base1 { color: #93a1a1; }
.solarized.base2 { color: #eee8d5; }
.solarized.base3  { color: #fdf6e3; }
.solarized.solar-yellow  { color: #b58900; }
.solarized.solar-orange  { color: #cb4b16; }
.solarized.solar-red { color: #dc322f; }
.solarized.solar-magenta { color: #d33682; }
.solarized.solar-violet  { color: #6c71c4; }
.solarized.solar-blue { color: #268bd2; }
.solarized.solar-cyan { color: #2aa198; }
.solarized.solar-green { color: #859900; }

/* Color scheme for code-mirror */

.cm-s-solarized {
  line-height: 1.45em;
  color-profile: sRGB;
  rendering-intent: auto;
}
.cm-s-solarized.cm-s-dark {
  color: #839496;
  background-color:  #002b36;
  text-shadow: #002b36 0 1px;
}
.cm-s-solarized.cm-s-light {
  background-color: #fdf6e3;
  color: #657b83;
  text-shadow: #eee8d5 0 1px;
}

.cm-s-solarized .CodeMirror-widget {
  text-shadow: none;
}

.cm-s-solarized .cm-header { color: #586e75; }
.cm-s-solarized .cm-quote { color: #93a1a1; }

.cm-s-solarized .cm-keyword { color: #cb4b16; }
.cm-s-solarized .cm-atom { color: #d33682; }
.cm-s-solarized .cm-number { color: #d33682; }
.cm-s-solarized .cm-def { color: #2aa198; }

.cm-s-solarized .cm-variable { color: #839496; }
.cm-s-solarized .cm-variable-2 { color: #b58900; }
.cm-s-solarized .cm-variable-3 { color: #6c71c4; }

.cm-s-solarized .cm-property { color: #2aa198; }
.cm-s-solarized .cm-operator { color: #6c71c4; }

.cm-s-solarized .cm-comment { color: #586e75; font-style:italic; }

.cm-s-solarized .cm-string { color: #859900; }
.cm-s-solarized .cm-string-2 { color: #b58900; }

.cm-s-solarized .cm-meta { color: #859900; }
.cm-s-solarized .cm-qualifier { color: #b58900; }
.cm-s-solarized .cm-builtin { color: #d33682; }
.cm-s-solarized .cm-bracket { color: #cb4b16; }
.cm-s-solarized .CodeMirror-matchingbracket { color: #859900; }
.cm-s-solarized .CodeMirror-nonmatchingbracket { color: #dc322f; }
.cm-s-solarized .cm-tag { color: #93a1a1; }
.cm-s-solarized .cm-attribute { color: #2aa198; }
.cm-s-solarized .cm-hr {
  color: transparent;
  border-top: 1px solid #586e75;
  display: block;
}
.cm-s-solarized .cm-link { color: #93a1a1; cursor: pointer; }
.cm-s-solarized .cm-special { color: #6c71c4; }
.cm-s-solarized .cm-em {
  color: #999;
  text-decoration: underline;
  text-decoration-style: dotted;
}
.cm-s-solarized .cm-strong { color: #eee; }
.cm-s-solarized .cm-error,
.cm-s-solarized .cm-invalidchar {
  color: #586e75;
  border-bottom: 1px dotted #dc322f;
}

.cm-s-solarized.cm-s-dark div.CodeMirror-selected { background: #073642; }
.cm-s-solarized.cm-s-dark.CodeMirror ::selection { background: rgba(7, 54, 66, 0.99); }
.cm-s-solarized.cm-s-dark .CodeMirror-line::-moz-selection, .cm-s-dark .CodeMirror-line > span::-moz-selection, .cm-s-dark .CodeMirror-line > span > span::-moz-selection { background: rgba(7, 54, 66, 0.99); }

.cm-s-solarized.cm-s-light div.CodeMirror-selected { background: #eee8d5; }
.cm-s-solarized.cm-s-light .CodeMirror-line::selection, .cm-s-light .CodeMirror-line > span::selection, .cm-s-light .CodeMirror-line > span > span::selection { background: #eee8d5; }
.cm-s-solarized.cm-s-light .CodeMirror-line::-moz-selection, .cm-s-ligh .CodeMirror-line > span::-moz-selection, .cm-s-ligh .CodeMirror-line > span > span::-moz-selection { background: #eee8d5; }

/* Editor styling */

/* Little shadow on the view-port of the buffer view */
.cm-s-solarized.CodeMirror {
  -moz-box-shadow: inset 7px 0 12px -6px #000;
  -webkit-box-shadow: inset 7px 0 12px -6px #000;
  box-shadow: inset 7px 0 12px -6px #000;
}

/* Gutter border and some shadow from it  */
.cm-s-solarized .CodeMirror-gutters {
  border-right: 1px solid;
}

/* Gutter colors and line number styling based of color scheme (dark / light) */

/* Dark */
.cm-s-solarized.cm-s-dark .CodeMirror-gutters {
  background-color:  #002b36;
  border-color: #00232c;
}

.cm-s-solarized.cm-s-dark .CodeMirror-linenumber {
  text-shadow: #021014 0 -1px;
}

/* Light */
.cm-s-solarized.cm-s-light .CodeMirror-gutters {
  background-color: #fdf6e3;
  border-color: #eee8d5;
}

/* Common */
.cm-s-solarized .CodeMirror-linenumber {
  color: #586e75;
  padding: 0 5px;
}
.cm-s-solarized .CodeMirror-guttermarker-subtle { color: #586e75; }
.cm-s-solarized.cm-s-dark .CodeMirror-guttermarker { color: #ddd; }
.cm-s-solarized.cm-s-light .CodeMirror-guttermarker { color: #cb4b16; }

.cm-s-solarized .CodeMirror-gutter .CodeMirror-gutter-text {
  color: #586e75;
}

.cm-s-solarized .CodeMirror-cursor { border-left: 1px solid #819090; }

/*
Active line. Negative margin compensates left padding of the text in the
view-port
*/
.cm-s-solarized.cm-s-dark .CodeMirror-activeline-background {
  background: rgba(255, 255, 255, 0.10);
}
.cm-s-solarized.cm-s-light .CodeMirror-activeline-background {
  background: rgba(0, 0, 0, 0.10);
}


code:
/* open-sans-300 - latin_latin-ext */
@font-face {
  font-family: 'Open Sans';
  font-style: normal;
  font-weight: 300;
  src: url('../fonts/open-sans-v13-latin_latin-ext-300.eot'); /* IE9 Compat Modes */
  src: local('Open Sans Light'), local('OpenSans-Light'),
       url('../fonts/open-sans-v13-latin_latin-ext-300.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
       url('../fonts/open-sans-v13-latin_latin-ext-300.woff2') format('woff2'), /* Super Modern Browsers */
       url('../fonts/open-sans-v13-latin_latin-ext-300.woff') format('woff'), /* Modern Browsers */
       url('../fonts/open-sans-v13-latin_latin-ext-300.ttf') format('truetype'), /* Safari, Android, iOS */
       url('../fonts/open-sans-v13-latin_latin-ext-300.svg#OpenSans') format('svg'); /* Legacy iOS */
}

/* open-sans-300italic - latin_latin-ext */
@font-face {
  font-family: 'Open Sans';
  font-style: italic;
  font-weight: 300;
  src: url('../fonts/open-sans-v13-latin_latin-ext-300italic.eot'); /* IE9 Compat Modes */
  src: local('Open Sans Light Italic'), local('OpenSansLight-Italic'),
       url('../fonts/open-sans-v13-latin_latin-ext-300italic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
       url('../fonts/open-sans-v13-latin_latin-ext-300italic.woff2') format('woff2'), /* Super Modern Browsers */
       url('../fonts/open-sans-v13-latin_latin-ext-300italic.woff') format('woff'), /* Modern Browsers */
       url('../fonts/open-sans-v13-latin_latin-ext-300italic.ttf') format('truetype'), /* Safari, Android, iOS */
       url('../fonts/open-sans-v13-latin_latin-ext-300italic.svg#OpenSans') format('svg'); /* Legacy iOS */
}

/* open-sans-regular - latin_latin-ext */
@font-face {
  font-family: 'Open Sans';
  font-style: normal;
  font-weight: 400;
  src: url('../fonts/open-sans-v13-latin_latin-ext-regular.eot'); /* IE9 Compat Modes */
  src: local('Open Sans'), local('OpenSans'),
       url('../fonts/open-sans-v13-latin_latin-ext-regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
       url('../fonts/open-sans-v13-latin_latin-ext-regular.woff2') format('woff2'), /* Super Modern Browsers */
       url('../fonts/open-sans-v13-latin_latin-ext-regular.woff') format('woff'), /* Modern Browsers */
       url('../fonts/open-sans-v13-latin_latin-ext-regular.ttf') format('truetype'), /* Safari, Android, iOS */
       url('../fonts/open-sans-v13-latin_latin-ext-regular.svg#OpenSans') format('svg'); /* Legacy iOS */
}

/* open-sans-italic - latin_latin-ext */
@font-face {
  font-family: 'Open Sans';
  font-style: italic;
  font-weight: 400;
  src: url('../fonts/open-sans-v13-latin_latin-ext-italic.eot'); /* IE9 Compat Modes */
  src: local('Open Sans Italic'), local('OpenSans-Italic'),
       url('../fonts/open-sans-v13-latin_latin-ext-italic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
       url('../fonts/open-sans-v13-latin_latin-ext-italic.woff2') format('woff2'), /* Super Modern Browsers */
       url('../fonts/open-sans-v13-latin_latin-ext-italic.woff') format('woff'), /* Modern Browsers */
       url('../fonts/open-sans-v13-latin_latin-ext-italic.ttf') format('truetype'), /* Safari, Android, iOS */
       url('../fonts/open-sans-v13-latin_latin-ext-italic.svg#OpenSans') format('svg'); /* Legacy iOS */
}

/* open-sans-700 - latin_latin-ext */
@font-face {
  font-family: 'Open Sans';
  font-style: normal;
  font-weight: 700;
  src: url('../fonts/open-sans-v13-latin_latin-ext-700.eot'); /* IE9 Compat Modes */
  src: local('Open Sans Bold'), local('OpenSans-Bold'),
       url('../fonts/open-sans-v13-latin_latin-ext-700.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
       url('../fonts/open-sans-v13-latin_latin-ext-700.woff2') format('woff2'), /* Super Modern Browsers */
       url('../fonts/open-sans-v13-latin_latin-ext-700.woff') format('woff'), /* Modern Browsers */
       url('../fonts/open-sans-v13-latin_latin-ext-700.ttf') format('truetype'), /* Safari, Android, iOS */
       url('../fonts/open-sans-v13-latin_latin-ext-700.svg#OpenSans') format('svg'); /* Legacy iOS */
}

/* open-sans-700italic - latin_latin-ext */
@font-face {
  font-family: 'Open Sans';
  font-style: italic;
  font-weight: 700;
  src: url('../fonts/open-sans-v13-latin_latin-ext-700italic.eot'); /* IE9 Compat Modes */
  src: local('Open Sans Bold Italic'), local('OpenSans-BoldItalic'),
       url('../fonts/open-sans-v13-latin_latin-ext-700italic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
       url('../fonts/open-sans-v13-latin_latin-ext-700italic.woff2') format('woff2'), /* Super Modern Browsers */
       url('../fonts/open-sans-v13-latin_latin-ext-700italic.woff') format('woff'), /* Modern Browsers */
       url('../fonts/open-sans-v13-latin_latin-ext-700italic.ttf') format('truetype'), /* Safari, Android, iOS */
       url('../fonts/open-sans-v13-latin_latin-ext-700italic.svg#OpenSans') format('svg'); /* Legacy iOS */
}


code:
clean_entry= function(text, element) {
  var _out = []
  var _data = $.trim(text).split(',');
  var y=0;
  for (var j=0; j<_data.length; j++){
    if ($.trim(_data[j]) == element) {
      continue;
    }
    _out[y] = $.trim(_data[j]);
    y+=1;
  }
  return _out;
}

add_tags = function(data, _issues_url) {
  console.log('adding ' + data.added_tags);

  var $select = $('#tag').selectize();
  var selectize = $select[0].selectize;

  var field = $('#taglist');
  for (i=0; i<data.added_tags.length; i++ ){
    tag = data.added_tags[i]
    var html = '\n ' + tag + ' ';
    field.append(html);
    selectize.createItem(tag);
  }
}

remove_tags = function(data, _issues_url) {
  console.log('removing ' + data.removed_tags);

  var $select = $('#tag').selectize();
  var selectize = $select[0].selectize;

  for (var i=0; i<data.removed_tags.length; i++ ){
    tag = data.removed_tags[i]
    selectize.removeItem(tag);
    $('#tag-' + tag).remove();
  }
}

assigne_issue = function(data, _issues_url) {
  console.log('assigning ' + data.assigned.name);

  var $select = $('#assignee').selectize();
  var selectize = $select[0].selectize;
  selectize.settings.create = true;
  selectize.createItem(data.assigned.name);
  selectize.settings.create = false;
  $('#assignee').val(data.assigned.name);

  var field = $('#assignee_plain');
  var _url = '\n'
        + data.assigned.name + '';
  field.html(_url);
}

unassigne_issue = function(data) {
  console.log('un-assigning ');

  var $select = $('#assignee').selectize();
  var selectize = $select[0].selectize;
  selectize.setValue(null);

  var field = $('#assignee_plain');
  field.html('unassigned');
}

add_deps = function(data, issue_uid, _issue_url) {
  console.log('adding ' + data.added_dependency);

  var dep = data.added_dependency;
  if (data.issue_uid == issue_uid){
    if (data.type == "children"){
      var $select = $('#blocks').selectize();
      var field = $('#blocklist');
      var _id = 'block';
    } else {
      var $select = $('#depends').selectize();
      var field = $('#dependlist');
      var _id = 'depend';
    }
  }

  var selectize = $select[0].selectize;
  selectize.settings.create = true;
  selectize.items.push(String(dep));
  selectize.createItem(String(dep));
  selectize.settings.create = false;

  var input_field = $('#' + _id + 's');
  input_field.val(selectize.items.join(','));

  var html = '\n#' + dep + '';

  field.append(html);
}

remove_deps = function(data, issue_uid, _issue_url) {
  console.log('Removing ' + data.removed_dependency);
  if (data.issue_uid == issue_uid){
    if (data.type == "children"){
      var $select = $('#depends').selectize();
      var _id = 'depend';
    } else {
      var $select = $('#blocks').selectize();
      var _id = 'block';
    }
  }

  var selectize = $select[0].selectize;

  var dep = data.removed_dependency;
  $('#' + _id + '-' + dep).remove();
  selectize.removeItem(dep);
}

add_comment = function(data, username) {
  console.log('Adding comment ' + data.comment_added);
  var field = $('#comments');
  var _data = '
 \

 \
     \
     ' + data.comment_user + '\
       \
       \
        seconds ago \
       \

\

\

 \

 \
           \
          \
      ' + emojione.toImage(data.comment_added) + '\

 \

 \

 \
         \

 \
         \
             \
        ';
    if ( data.comment_user == username) {
          _data += ' \
             \
           \
          <button class="btn btn-secondary btn-sm" \
            title="Remove comment" \
            name="drop_comment" value="' + data.comment_id + '" type="submit"  \
            onclick="return confirm(\'Do you really want to remove this comment?\');" \
            > \
          </button>';
    }
    _data += ' \

 \

 \

';

  field.html(field.html() + _data);
}

update_comment = function(data) {
  console.log('Updating comment ' + data.comment_id);
  var field = $('#comment-' + data.comment_id).parent();
  var edited = field.find('.text-muted');
  if (edited.length == 0) {
    $(field.find('aside')).before(
        'Edited a just now by '
        + data.comment_editor + '');
  } else {
    edited.html('Edited a just now by ' + data.comment_editor)
  }
  field.find('.comment_body').html(data.comment_updated);
  field.find('.issue_actions').show();
  field.find('.issue_comment').show();
}

update_issue = function(data) {
  console.log('Adjusting issue ' + data.fields);
  for (i=0; i<data.fields.length; i++){
    var _f = data.fields[i];
    if (_f == 'status') {
      var field = $('#status').find('span');
      field.html(data.issue.status);
    } else if (_f == 'title'){
      var field = $('#issuetitle');
      field.html(data.issue.title);
    } else if (_f == 'content'){
      var field = $('#comment-0').parent().find('.comment_body');
      field.html('
' + data.issue.content + '
');
    }
  }
}

private_issue = function(data, _api_issue_url, issue_uid) {
  if (data.comment_id){
    var _url = _api_issue_url.replace('-123456789', issue_uid)
      + '/comment/' + data.comment_id;
    $.get( _url )
      .done(function(data) {
        add_comment({
          comment_added: data.comment,
          comment_id: data.id,
          comment_user: data.user.name,
          comment_date: data.comment_date,
          avatar_url: data.avatar_url,
        });
      })
  } else if (data.fields) {
    var _url = _api_issue_url.replace('-1', issue_uid) + '?comments=0';
    $.get( _url )
      .done(function(ndata) {
        update_issue({
          fields: data.fields,
          issue: {
            status: ndata.status,
            title: ndata.title,
            content: ndata.content,
          }
        });
      })
  }
}

private_issue_update = function(data, _api_issue_url, issue_uid) {
  var _url = _api_issue_url.replace('-1', issue_uid)
    + '/comment/' + data.comment_id;
  $.get( _url )
    .done(function(data) {
      update_comment({
        comment_updated: data.comment,
        comment_id: data.id,
        comment_user: data.user.name,
        comment_editor: data.comment_date,
      });
    })
}

process_event = function(
      data, issue_uid, _issue_url, _issues_url, _api_issue_url, username)
{
  console.log(data);
  var category = null;
  var originalTitle = document.title;
  if (data.issue == 'private'){
    console.log('private issue');
    private_issue(data, _api_issue_url, issue_uid)
  }
  else if (data.comment_updated == 'private'){
    console.log('private issue updated');
    private_issue_update(data, _api_issue_url, issue_uid)
  }
  else if (data.added_tags){
    add_tags(data, _issues_url);
    category = 'Tag added';
  }
  else if (data.removed_tags){
    remove_tags(data, _issues_url);
    category = 'Tag removed';
  }
  else if (data.assigned){
    assigne_issue(data, _issues_url);
    category = 'Issue assigned';
  }
  else if (data.unassigned){
    unassigne_issue(data);
    category = 'Issue un-assigned';
  }
  else if (data.added_dependency){
    add_deps(data, issue_uid, _issue_url);
    category = 'Dependency added';
  }
  else if (data.removed_dependency){
    remove_deps(data, issue_uid, _issue_url);
    category = 'Dependency removed';
  }
  else if (data.comment_added){
    add_comment(data, username);
    category = 'Comment added';
  }
  else if (data.comment_updated){
    update_comment(data);
    category = 'Comment updated';
  }
  else if (data.fields){
    update_issue(data);
    category = 'Issue edited';
  } else {
    console.log('Unknown data');
  }

  if (category && !document.hasFocus()) {
    var int = setInterval(function(){
      var title = document.title;
      document.title = (title === originalTitle) ? category : originalTitle;
    }, 750);

    $(window).focus(function () {
      clearInterval(int);
      document.title = originalTitle;
    });
  }
}