Example #1
0
def Linkify(_mr, autolink_regex_match, _component_ref_artifacts):
    """Examine a textual reference and replace it with a hyperlink or not.

  This is a callback for use with the autolink feature.  The function
  parameters are standard for this type of callback.

  Args:
    _mr: unused information parsed from the HTTP request.
    autolink_regex_match: regex match for the textual reference.
    _component_ref_artifacts: unused result of call to GetReferencedIssues.

  Returns:
    A list of TextRuns with tag=a for all matched ftp, http, https and mailto
    links converted into HTML hyperlinks.
  """
    hyperlink = autolink_regex_match.group(0)

    trailing = ''
    for begin, end in _LINK_TRAILING_CHARS:
        if hyperlink.endswith(end):
            if not begin or hyperlink[:-len(end)].find(begin) == -1:
                trailing = end + trailing
                hyperlink = hyperlink[:-len(end)]

    tag_match = _CLOSING_TAG_RE.search(hyperlink)
    if tag_match:
        trailing = hyperlink[tag_match.start(0):] + trailing
        hyperlink = hyperlink[:tag_match.start(0)]

    href = hyperlink
    if not href.lower().startswith(('http', 'ftp', 'mailto')):
        # We use http because redirects for https are not all set up.
        href = 'http://' + href

    if (not validate.IsValidURL(href)
            and not (href.startswith('mailto')
                     and validate.IsValidEmail(href[7:]))):
        return [template_helpers.TextRun(autolink_regex_match.group(0))]

    result = [template_helpers.TextRun(hyperlink, tag='a', href=href)]
    if trailing:
        result.append(template_helpers.TextRun(trailing))

    return result
Example #2
0
    def _ApplySubstFunctionToRuns(self, text_runs, regex, subst_fun, mr,
                                  component_ref_artifacts):
        """Apply autolink regex and substitution function to each text run.

    Args:
      text_runs: list of TextRun objects with parts of the original comment.
      regex: Regular expression for detecting textual references to artifacts.
      subst_fun: function to return autolink markup, or original text.
      mr: common info parsed from the user HTTP request.
      component_ref_artifacts: already-looked-up destination artifacts to use
        when computing substitution text.

    Returns:
      A new list with more and smaller runs, some of which may have tag
      and link attributes set.
    """
        result_runs = []
        for run in text_runs:
            content = run.content
            if run.tag:
                # This chunk has already been substituted, don't allow nested
                # autolinking to mess up our output.
                result_runs.append(run)
            else:
                pos = 0
                for match in regex.finditer(content):
                    if match.start() > pos:
                        result_runs.append(
                            template_helpers.TextRun(
                                content[pos:match.start()]))
                    replacement_runs = subst_fun(mr, match,
                                                 component_ref_artifacts)
                    result_runs.extend(replacement_runs)
                    pos = match.end()

                if run.content[
                        pos:]:  # Keep any text that came after the last match
                    result_runs.append(
                        template_helpers.TextRun(run.content[pos:]))

        # TODO(jrobbins): ideally we would merge consecutive plain text runs
        # so that regexes can match across those run boundaries.

        return result_runs
Example #3
0
 def testMarkupAutolinks_TooBig(self):
     """If the issue has too much text, we just do regex-based autolinking."""
     all_ref_artifacts = self.aa.GetAllReferencedArtifacts(
         None, self.comments, max_total_length=10)
     result = self.aa.MarkupAutolinks(
         None, [template_helpers.TextRun(self.comment1)], all_ref_artifacts)
     self.assertEqual(5, len(result))
     self.assertEqual('Feel free to contact me at ', result[0].content)
     # The test autolink handlers in this file do not link email addresses.
     self.assertEqual('a AT other.com', result[1].content)
     self.assertIsNone(result[1].href)
Example #4
0
    def testMarkupAutolinks(self):
        all_ref_artifacts = self.aa.GetAllReferencedArtifacts(
            None, self.comments)
        result = self.aa.MarkupAutolinks(
            None, [template_helpers.TextRun(self.comment1)], all_ref_artifacts)
        self.assertEqual('Feel free to contact me at ', result[0].content)
        self.assertEqual('a AT other.com', result[1].content)
        self.assertEqual(', or ', result[2].content)
        self.assertEqual('*****@*****.**', result[3].content)
        self.assertEqual('mailto:[email protected]', result[3].href)
        self.assertEqual(', or [email protected].', result[4].content)

        result = self.aa.MarkupAutolinks(
            None, [template_helpers.TextRun(self.comment2)], all_ref_artifacts)
        self.assertEqual('no matches in this comment', result[0].content)

        result = self.aa.MarkupAutolinks(
            None, [template_helpers.TextRun(self.comment3)], all_ref_artifacts)
        self.assertEqual('just matches with no ref: ', result[0].content)
        self.assertEqual('a AT other.com', result[1].content)
        self.assertEqual(', [email protected]', result[2].content)
Example #5
0
def _Linkify(autolink_regex_match, shorthand=False):
    """Examine a textual reference and replace it with a hyperlink or not.

  This is a callback for use with the autolink feature.

  Args:
    autolink_regex_match: regex match for the textual reference.
    shorthand: Set to True to allow shorthand links without "http".

  Returns:
    A list of TextRuns with tag=a for all matched ftp, http, https and mailto
    links converted into HTML hyperlinks.
  """
    hyperlink = autolink_regex_match.group(0)

    trailing = ''
    for begin, end in _LINK_TRAILING_CHARS:
        if hyperlink.endswith(end):
            if not begin or hyperlink[:-len(end)].find(begin) == -1:
                trailing = end + trailing
                hyperlink = hyperlink[:-len(end)]

    tag_match = _CLOSING_TAG_RE.search(hyperlink)
    if tag_match:
        trailing = hyperlink[tag_match.start(0):] + trailing
        hyperlink = hyperlink[:tag_match.start(0)]

    href = hyperlink
    if shorthand and not href.lower().startswith('http'):
        # We use http because redirects for https are not all set up.
        href = 'http://' + href

    if (not validate.IsValidURL(href) and not validate.IsValidEmail(href)):
        return [template_helpers.TextRun(autolink_regex_match.group(0))]

    result = [template_helpers.TextRun(hyperlink, tag='a', href=href)]
    if trailing:
        result.append(template_helpers.TextRun(trailing))

    return result
Example #6
0
def _ReplaceIssueRef(autolink_regex_match, component_ref_artifacts,
                     single_issue_regex, default_project_name):
    """Examine a textual reference and replace it with an autolink or not.

  Args:
    autolink_regex_match: regex match for the textual reference.
    component_ref_artifacts: result of earlier call to GetReferencedIssues.
    single_issue_regex: regular expression to parse individual issue references
        out of a multi-issue-reference phrase.  E.g., "issues 12 and 34".
    default_project_name: project name to use when not specified.

  Returns:
    A list of IssueRefRuns and TextRuns to replace the textual
    reference.  If there is an issue to autolink to, we return an HTML
    hyperlink.  Otherwise, we the run will have the original plain
    text.
  """
    open_dict, closed_dict = {}, {}
    if component_ref_artifacts:
        open_dict, closed_dict = component_ref_artifacts
    original = autolink_regex_match.group(0)
    logging.info('called ReplaceIssueRef on %r', original)
    result_runs = []
    pos = 0
    for submatch in single_issue_regex.finditer(original):
        if submatch.start() >= pos:
            if original[pos:submatch.start()]:
                result_runs.append(
                    template_helpers.TextRun(original[pos:submatch.start()]))
            replacement_run = _ReplaceSingleIssueRef(submatch, open_dict,
                                                     closed_dict,
                                                     default_project_name)
            result_runs.append(replacement_run)
            pos = submatch.end()

    if original[pos:]:
        result_runs.append(template_helpers.TextRun(original[pos:]))

    return result_runs
Example #7
0
def _ReplaceSingleIssueRef(submatch, open_dict, closed_dict,
                           default_project_name):
    """Replace one issue reference with a link, or the original text."""
    content = submatch.group(0)
    project_name = submatch.group('project_name')
    if project_name:
        project_name = project_name.lstrip().rstrip(':#')
    else:
        # We need project_name for the URL, even if it is not in the text.
        project_name = default_project_name

    local_id = int(submatch.group('local_id'))
    issue_key = _IssueProjectKey(project_name, local_id)
    if issue_key in open_dict:
        return IssueRefRun(open_dict[issue_key], False, project_name, content)
    elif issue_key in closed_dict:
        return IssueRefRun(closed_dict[issue_key], True, project_name, content)
    else:  # Don't link to non-existent issues.
        return template_helpers.TextRun(content)
Example #8
0
    def __init__(self,
                 pb,
                 mr,
                 prefetched_issues,
                 users_by_id,
                 prefetched_projects,
                 prefetched_configs,
                 autolink=None,
                 all_ref_artifacts=None,
                 ending=None,
                 highlight=None):
        """Constructs an ActivityView out of an Activity protocol buffer.

    Args:
      pb: an IssueComment or Activity protocol buffer.
      mr: HTTP request info, used by the artifact autolink.
      prefetched_issues: dictionary of the issues for the comments being shown.
      users_by_id: dict {user_id: UserView} for all relevant users.
      prefetched_projects: dict {project_id: project} including all the projects
          that we might need.
      prefetched_configs: dict {project_id: config} for those projects.
      autolink: Autolink instance.
      all_ref_artifacts: list of all artifacts in the activity stream.
      ending: ending type for activity titles, 'in_project' or 'by_user'
      highlight: what to highlight in the middle column on user updates pages
          i.e. 'project', 'user', or None
    """
        template_helpers.PBProxy.__init__(self, pb)

        activity_type = 'ProjectIssueUpdate'  # TODO(jrobbins): more types

        self.comment = None
        self.issue = None
        self.field_changed = None
        self.multiple_fields_changed = ezt.boolean(False)
        self.project = None
        self.user = None
        self.timestamp = time.time(
        )  # Bogus value makes bad ones highly visible.

        if isinstance(pb, tracker_pb2.IssueComment):
            self.timestamp = pb.timestamp
            issue = prefetched_issues[pb.issue_id]
            if self.timestamp == issue.opened_timestamp:
                issue_change_id = None  # This comment is the description.
            else:
                issue_change_id = pb.timestamp  # instead of seq num.

            self.comment = tracker_views.IssueCommentView(
                mr.project_name, pb, users_by_id, autolink, all_ref_artifacts,
                mr, issue)

            # TODO(jrobbins): pass effective_ids of the commenter so that he/she
            # can be identified as a project member or not.
            config = prefetched_configs[issue.project_id]
            self.issue = tracker_views.IssueView(issue, users_by_id, config)
            self.user = self.comment.creator
            project = prefetched_projects[issue.project_id]
            self.project_name = project.project_name
            self.project = project_views.ProjectView(project)

        else:
            logging.warn('unknown activity object %r', pb)

        nested_page_data = {
            'activity_type': activity_type,
            'issue_change_id': issue_change_id,
            'comment': self.comment,
            'issue': self.issue,
            'project': self.project,
            'user': self.user,
            'timestamp': self.timestamp,
            'ending_type': ending,
        }

        self.escaped_title = self._TITLE_TEMPLATE.GetResponse(
            nested_page_data).strip()
        self.escaped_body = self._BODY_TEMPLATE.GetResponse(
            nested_page_data).strip()

        if autolink is not None and all_ref_artifacts is not None:
            # TODO(jrobbins): actually parse the comment text.  Actually render runs.
            runs = autolink.MarkupAutolinks(
                mr, [template_helpers.TextRun(self.escaped_body)],
                all_ref_artifacts)
            self.escaped_body = ''.join(run.content for run in runs)

        self.date_bucket, self.date_relative = timestr.GetHumanScaleDate(
            self.timestamp)
        time_tuple = time.localtime(self.timestamp)
        self.date_tooltip = time.asctime(time_tuple)

        # We always highlight the user for starring activities
        if activity_type.startswith('UserStar'):
            self.highlight = 'user'
        else:
            self.highlight = highlight
Example #9
0
def _ChunkToRun(chunk):
    """Convert a substring of the user's comment to a TextRun object."""
    if chunk.startswith('<b>') and chunk.endswith('</b>'):
        return template_helpers.TextRun(chunk[3:-4], tag='b')
    else:
        return template_helpers.TextRun(chunk)
Example #10
0
 def MakeHyperLink(_mr, match, _comp_ref_artifacts):
     domain = match.group(0)
     return [
         template_helpers.TextRun(tag='a', href=domain, content=domain)
     ]