OLD | NEW |
(Empty) | |
| 1 # Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is govered by a BSD-style |
| 3 # license that can be found in the LICENSE file or at |
| 4 # https://developers.google.com/open-source/licenses/bsd |
| 5 |
| 6 """Business objects for Monorail's framework. |
| 7 |
| 8 These are classes and functions that operate on the objects that |
| 9 users care about in Monorail but that are not part of just one specific |
| 10 component: e.g., projects, users, and labels. |
| 11 """ |
| 12 |
| 13 import logging |
| 14 import re |
| 15 import string |
| 16 |
| 17 import settings |
| 18 from framework import framework_constants |
| 19 |
| 20 |
| 21 # Pattern to match a valid project name. Users of this pattern MUST use |
| 22 # the re.VERBOSE flag or the whitespace and comments we be considered |
| 23 # significant and the pattern will not work. See "re" module documentation. |
| 24 _RE_PROJECT_NAME_PATTERN_VERBOSE = r""" |
| 25 (?=[-a-z0-9]*[a-z][-a-z0-9]*) # Lookahead to make sure there is at least |
| 26 # one letter in the whole name. |
| 27 [a-z0-9] # Start with a letter or digit. |
| 28 [-a-z0-9]* # Follow with any number of valid characters. |
| 29 [a-z0-9] # End with a letter or digit. |
| 30 """ |
| 31 |
| 32 |
| 33 # Compiled regexp to match the project name and nothing more before or after. |
| 34 RE_PROJECT_NAME = re.compile( |
| 35 '^%s$' % _RE_PROJECT_NAME_PATTERN_VERBOSE, re.VERBOSE) |
| 36 |
| 37 |
| 38 def IsValidProjectName(s): |
| 39 """Return true if the given string is a valid project name.""" |
| 40 return (RE_PROJECT_NAME.match(s) and |
| 41 len(s) <= framework_constants.MAX_PROJECT_NAME_LENGTH) |
| 42 |
| 43 |
| 44 def UserOwnsProject(project, effective_ids): |
| 45 """Return True if any of the effective_ids is a project owner.""" |
| 46 return not effective_ids.isdisjoint(project.owner_ids or set()) |
| 47 |
| 48 |
| 49 def UserIsInProject(project, effective_ids): |
| 50 """Return True if any of the effective_ids is a project member. |
| 51 |
| 52 Args: |
| 53 project: Project PB for the current project. |
| 54 effective_ids: set of int user IDs for the current user (including all |
| 55 user groups). This will be an empty set for anonymous users. |
| 56 |
| 57 Returns: |
| 58 True if the user has any direct or indirect role in the project. The value |
| 59 will actually be a set(), but it will have an ID in it if the user is in |
| 60 the project, or it will be an empty set which is considered False. |
| 61 """ |
| 62 return (UserOwnsProject(project, effective_ids) or |
| 63 not effective_ids.isdisjoint(project.committer_ids or set()) or |
| 64 not effective_ids.isdisjoint(project.contributor_ids or set())) |
| 65 |
| 66 |
| 67 def AllProjectMembers(project): |
| 68 """Return a list of user IDs of all members in the given project.""" |
| 69 return project.owner_ids + project.committer_ids + project.contributor_ids |
| 70 |
| 71 |
| 72 def IsPriviledgedDomainUser(email): |
| 73 """Return True if the user's account is from a priviledged domain.""" |
| 74 if email and '@' in email: |
| 75 _, user_domain = email.split('@', 1) |
| 76 return user_domain in settings.priviledged_user_domains |
| 77 |
| 78 return False |
| 79 |
| 80 |
| 81 |
| 82 # String translation table to catch a common typos in label names. |
| 83 _CANONICALIZATION_TRANSLATION_TABLE = { |
| 84 ord(delete_u_char): None |
| 85 for delete_u_char in u'!"#$%&\'()*+,/:;<>?@[\\]^`{|}~\t\n\x0b\x0c\r ' |
| 86 } |
| 87 _CANONICALIZATION_TRANSLATION_TABLE.update({ord(u'='): ord(u'-')}) |
| 88 |
| 89 |
| 90 def CanonicalizeLabel(user_input): |
| 91 """Canonicalize a given label or status value. |
| 92 |
| 93 When the user enters a string that represents a label or an enum, |
| 94 convert it a canonical form that makes it more likely to match |
| 95 existing values. |
| 96 |
| 97 Args: |
| 98 user_input: string that the user typed for a label. |
| 99 |
| 100 Returns: |
| 101 Canonical form of that label as a unicode string. |
| 102 """ |
| 103 if user_input is None: |
| 104 return user_input |
| 105 |
| 106 if not isinstance(user_input, unicode): |
| 107 user_input = user_input.decode('utf-8') |
| 108 |
| 109 canon_str = user_input.translate(_CANONICALIZATION_TRANSLATION_TABLE) |
| 110 return canon_str |
| 111 |
| 112 |
| 113 def MergeLabels(labels_list, labels_add, labels_remove, excl_prefixes): |
| 114 """Update a list of labels with the given add and remove label lists. |
| 115 |
| 116 Args: |
| 117 labels_list: list of current labels. |
| 118 labels_add: labels that the user wants to add. |
| 119 labels_remove: labels that the user wants to remove. |
| 120 excl_prefixes: prefixes that can have only one value, e.g., Priority. |
| 121 |
| 122 Returns: |
| 123 (merged_labels, update_labels_add, update_labels_remove): |
| 124 A new list of labels with the given labels added and removed, and |
| 125 any exclusive label prefixes taken into account. Then two |
| 126 lists of update strings to explain the changes that were actually |
| 127 made. |
| 128 """ |
| 129 old_lower_labels = [lab.lower() for lab in labels_list] |
| 130 labels_add = [lab for lab in labels_add |
| 131 if lab.lower() not in old_lower_labels] |
| 132 labels_remove = [lab for lab in labels_remove |
| 133 if lab.lower() in old_lower_labels] |
| 134 labels_remove_lower = [lab.lower() for lab in labels_remove] |
| 135 config_excl = [lab.lower() for lab in excl_prefixes] |
| 136 |
| 137 # "Old minus exclusive" is the set of old label values minus any |
| 138 # that are implictly removed by newly set exclusive labels. |
| 139 excl_add = [] # E.g., there can be only one "Priority-*" label |
| 140 for lab in labels_add: |
| 141 prefix = lab.split('-')[0].lower() |
| 142 if prefix in config_excl: |
| 143 excl_add.append('%s-' % prefix) |
| 144 old_minus_excl = [] |
| 145 for lab in labels_list: |
| 146 for prefix_dash in excl_add: |
| 147 if lab.lower().startswith(prefix_dash): |
| 148 # Note: don't add -lab to update_labels_remove, it is implicit. |
| 149 break |
| 150 else: |
| 151 old_minus_excl.append(lab) |
| 152 |
| 153 merged_labels = [lab for lab in old_minus_excl + labels_add |
| 154 if lab.lower() not in labels_remove_lower] |
| 155 |
| 156 return merged_labels, labels_add, labels_remove |
OLD | NEW |