ikarienator@chromium.org | faf3fdf | 2013-09-20 02:11:48 +0000 | [diff] [blame] | 1 | # Copyright 2013 The Chromium Authors. All rights reserved. |
| 2 | # Use of this source code is governed by a BSD-style license that can be |
| 3 | # found in the LICENSE file. |
ikarienator@chromium.org | faf3fdf | 2013-09-20 02:11:48 +0000 | [diff] [blame] | 4 | """Interactive tool for finding reviewers/owners for a change.""" |
| 5 | |
Raul Tambre | 80ee78e | 2019-05-06 22:41:05 +0000 | [diff] [blame] | 6 | from __future__ import print_function |
| 7 | |
ikarienator@chromium.org | faf3fdf | 2013-09-20 02:11:48 +0000 | [diff] [blame] | 8 | import os |
| 9 | import copy |
ikarienator@chromium.org | faf3fdf | 2013-09-20 02:11:48 +0000 | [diff] [blame] | 10 | |
Edward Lesmes | ae3586b | 2020-03-23 21:21:14 +0000 | [diff] [blame] | 11 | import gclient_utils |
| 12 | |
| 13 | |
ikarienator@chromium.org | faf3fdf | 2013-09-20 02:11:48 +0000 | [diff] [blame] | 14 | def first(iterable): |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame^] | 15 | for element in iterable: |
| 16 | return element |
ikarienator@chromium.org | faf3fdf | 2013-09-20 02:11:48 +0000 | [diff] [blame] | 17 | |
| 18 | |
| 19 | class OwnersFinder(object): |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame^] | 20 | COLOR_LINK = '\033[4m' |
| 21 | COLOR_BOLD = '\033[1;32m' |
| 22 | COLOR_GREY = '\033[0;37m' |
| 23 | COLOR_RESET = '\033[0m' |
ikarienator@chromium.org | faf3fdf | 2013-09-20 02:11:48 +0000 | [diff] [blame] | 24 | |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame^] | 25 | indentation = 0 |
ikarienator@chromium.org | faf3fdf | 2013-09-20 02:11:48 +0000 | [diff] [blame] | 26 | |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame^] | 27 | def __init__(self, |
| 28 | files, |
| 29 | author, |
| 30 | reviewers, |
| 31 | owners_client, |
| 32 | email_postfix='@chromium.org', |
| 33 | disable_color=False, |
| 34 | ignore_author=False): |
| 35 | self.email_postfix = email_postfix |
ikarienator@chromium.org | faf3fdf | 2013-09-20 02:11:48 +0000 | [diff] [blame] | 36 | |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame^] | 37 | if os.name == 'nt' or disable_color: |
| 38 | self.COLOR_LINK = '' |
| 39 | self.COLOR_BOLD = '' |
| 40 | self.COLOR_GREY = '' |
| 41 | self.COLOR_RESET = '' |
ikarienator@chromium.org | faf3fdf | 2013-09-20 02:11:48 +0000 | [diff] [blame] | 42 | |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame^] | 43 | self.author = author |
ikarienator@chromium.org | faf3fdf | 2013-09-20 02:11:48 +0000 | [diff] [blame] | 44 | |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame^] | 45 | filtered_files = files |
ikarienator@chromium.org | faf3fdf | 2013-09-20 02:11:48 +0000 | [diff] [blame] | 46 | |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame^] | 47 | reviewers = list(reviewers) |
| 48 | if author and not ignore_author: |
| 49 | reviewers.append(author) |
Edward Lemur | 707d70b | 2018-02-07 00:50:14 +0100 | [diff] [blame] | 50 | |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame^] | 51 | # Eliminate files that existing reviewers can review. |
| 52 | self.owners_client = owners_client |
| 53 | approval_status = self.owners_client.GetFilesApprovalStatus( |
| 54 | filtered_files, reviewers, []) |
| 55 | filtered_files = [ |
| 56 | f for f in filtered_files |
| 57 | if approval_status[f] != self.owners_client.APPROVED |
| 58 | ] |
ikarienator@chromium.org | faf3fdf | 2013-09-20 02:11:48 +0000 | [diff] [blame] | 59 | |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame^] | 60 | # If some files are eliminated. |
| 61 | if len(filtered_files) != len(files): |
| 62 | files = filtered_files |
ikarienator@chromium.org | faf3fdf | 2013-09-20 02:11:48 +0000 | [diff] [blame] | 63 | |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame^] | 64 | self.files_to_owners = self.owners_client.BatchListOwners(files) |
ikarienator@chromium.org | faf3fdf | 2013-09-20 02:11:48 +0000 | [diff] [blame] | 65 | |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame^] | 66 | self.owners_to_files = {} |
| 67 | self._map_owners_to_files() |
ikarienator@chromium.org | faf3fdf | 2013-09-20 02:11:48 +0000 | [diff] [blame] | 68 | |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame^] | 69 | self.original_files_to_owners = copy.deepcopy(self.files_to_owners) |
ikarienator@chromium.org | faf3fdf | 2013-09-20 02:11:48 +0000 | [diff] [blame] | 70 | |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame^] | 71 | # This is the queue that will be shown in the interactive questions. |
| 72 | # It is initially sorted by the score in descending order. In the |
| 73 | # interactive questions a user can choose to "defer" its decision, then |
| 74 | # the owner will be put to the end of the queue and shown later. |
| 75 | self.owners_queue = [] |
ikarienator@chromium.org | faf3fdf | 2013-09-20 02:11:48 +0000 | [diff] [blame] | 76 | |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame^] | 77 | self.unreviewed_files = set() |
| 78 | self.reviewed_by = {} |
| 79 | self.selected_owners = set() |
| 80 | self.deselected_owners = set() |
| 81 | self.reset() |
ikarienator@chromium.org | faf3fdf | 2013-09-20 02:11:48 +0000 | [diff] [blame] | 82 | |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame^] | 83 | def run(self): |
| 84 | self.reset() |
| 85 | while self.owners_queue and self.unreviewed_files: |
| 86 | owner = self.owners_queue[0] |
ikarienator@chromium.org | faf3fdf | 2013-09-20 02:11:48 +0000 | [diff] [blame] | 87 | |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame^] | 88 | if (owner in self.selected_owners) or (owner |
| 89 | in self.deselected_owners): |
| 90 | continue |
ikarienator@chromium.org | faf3fdf | 2013-09-20 02:11:48 +0000 | [diff] [blame] | 91 | |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame^] | 92 | if not any((file_name in self.unreviewed_files) |
| 93 | for file_name in self.owners_to_files[owner]): |
| 94 | self.deselect_owner(owner) |
| 95 | continue |
ikarienator@chromium.org | faf3fdf | 2013-09-20 02:11:48 +0000 | [diff] [blame] | 96 | |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame^] | 97 | self.print_info(owner) |
ikarienator@chromium.org | faf3fdf | 2013-09-20 02:11:48 +0000 | [diff] [blame] | 98 | |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame^] | 99 | while True: |
| 100 | inp = self.input_command(owner) |
| 101 | if inp in ('y', 'yes'): |
| 102 | self.select_owner(owner) |
| 103 | break |
Aravind Vasudevan | c5f0cbb | 2022-01-24 23:56:57 +0000 | [diff] [blame] | 104 | |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame^] | 105 | if inp in ('n', 'no'): |
| 106 | self.deselect_owner(owner) |
| 107 | break |
Aravind Vasudevan | c5f0cbb | 2022-01-24 23:56:57 +0000 | [diff] [blame] | 108 | |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame^] | 109 | if inp in ('', 'd', 'defer'): |
| 110 | self.owners_queue.append(self.owners_queue.pop(0)) |
| 111 | break |
Aravind Vasudevan | c5f0cbb | 2022-01-24 23:56:57 +0000 | [diff] [blame] | 112 | |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame^] | 113 | if inp in ('f', 'files'): |
| 114 | self.list_files() |
| 115 | break |
Aravind Vasudevan | c5f0cbb | 2022-01-24 23:56:57 +0000 | [diff] [blame] | 116 | |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame^] | 117 | if inp in ('o', 'owners'): |
| 118 | self.list_owners(self.owners_queue) |
| 119 | break |
Aravind Vasudevan | c5f0cbb | 2022-01-24 23:56:57 +0000 | [diff] [blame] | 120 | |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame^] | 121 | if inp in ('p', 'pick'): |
| 122 | self.pick_owner(gclient_utils.AskForData('Pick an owner: ')) |
| 123 | break |
Aravind Vasudevan | c5f0cbb | 2022-01-24 23:56:57 +0000 | [diff] [blame] | 124 | |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame^] | 125 | if inp.startswith('p ') or inp.startswith('pick '): |
| 126 | self.pick_owner(inp.split(' ', 2)[1].strip()) |
| 127 | break |
Aravind Vasudevan | c5f0cbb | 2022-01-24 23:56:57 +0000 | [diff] [blame] | 128 | |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame^] | 129 | if inp in ('r', 'restart'): |
| 130 | self.reset() |
| 131 | break |
Aravind Vasudevan | c5f0cbb | 2022-01-24 23:56:57 +0000 | [diff] [blame] | 132 | |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame^] | 133 | if inp in ('q', 'quit'): |
| 134 | # Exit with error |
| 135 | return 1 |
ikarienator@chromium.org | faf3fdf | 2013-09-20 02:11:48 +0000 | [diff] [blame] | 136 | |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame^] | 137 | self.print_result() |
| 138 | return 0 |
ikarienator@chromium.org | faf3fdf | 2013-09-20 02:11:48 +0000 | [diff] [blame] | 139 | |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame^] | 140 | def _map_owners_to_files(self): |
| 141 | for file_name in self.files_to_owners: |
| 142 | for owner in self.files_to_owners[file_name]: |
| 143 | self.owners_to_files.setdefault(owner, set()) |
| 144 | self.owners_to_files[owner].add(file_name) |
ikarienator@chromium.org | faf3fdf | 2013-09-20 02:11:48 +0000 | [diff] [blame] | 145 | |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame^] | 146 | def reset(self): |
| 147 | self.files_to_owners = copy.deepcopy(self.original_files_to_owners) |
| 148 | self.unreviewed_files = set(self.files_to_owners.keys()) |
| 149 | self.reviewed_by = {} |
| 150 | self.selected_owners = set() |
| 151 | self.deselected_owners = set() |
ikarienator@chromium.org | faf3fdf | 2013-09-20 02:11:48 +0000 | [diff] [blame] | 152 | |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame^] | 153 | # Randomize owners' names so that if many reviewers have identical |
| 154 | # scores they will be randomly ordered to avoid bias. |
| 155 | owners = list( |
| 156 | self.owners_client.ScoreOwners(self.files_to_owners.keys())) |
| 157 | if self.author and self.author in owners: |
| 158 | owners.remove(self.author) |
| 159 | self.owners_queue = owners |
| 160 | self.find_mandatory_owners() |
ikarienator@chromium.org | faf3fdf | 2013-09-20 02:11:48 +0000 | [diff] [blame] | 161 | |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame^] | 162 | def select_owner(self, owner, findMandatoryOwners=True): |
| 163 | if owner in self.selected_owners or owner in self.deselected_owners\ |
| 164 | or not (owner in self.owners_queue): |
| 165 | return |
| 166 | self.writeln('Selected: ' + owner) |
| 167 | self.owners_queue.remove(owner) |
| 168 | self.selected_owners.add(owner) |
| 169 | for file_name in filter( |
| 170 | lambda file_name: file_name in self.unreviewed_files, |
| 171 | self.owners_to_files[owner]): |
| 172 | self.unreviewed_files.remove(file_name) |
| 173 | self.reviewed_by[file_name] = owner |
| 174 | if findMandatoryOwners: |
| 175 | self.find_mandatory_owners() |
ikarienator@chromium.org | faf3fdf | 2013-09-20 02:11:48 +0000 | [diff] [blame] | 176 | |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame^] | 177 | def deselect_owner(self, owner, findMandatoryOwners=True): |
| 178 | if owner in self.selected_owners or owner in self.deselected_owners\ |
| 179 | or not (owner in self.owners_queue): |
| 180 | return |
| 181 | self.writeln('Deselected: ' + owner) |
| 182 | self.owners_queue.remove(owner) |
| 183 | self.deselected_owners.add(owner) |
| 184 | for file_name in self.owners_to_files[owner] & self.unreviewed_files: |
| 185 | self.files_to_owners[file_name].remove(owner) |
| 186 | if findMandatoryOwners: |
| 187 | self.find_mandatory_owners() |
ikarienator@chromium.org | faf3fdf | 2013-09-20 02:11:48 +0000 | [diff] [blame] | 188 | |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame^] | 189 | def find_mandatory_owners(self): |
ikarienator@chromium.org | faf3fdf | 2013-09-20 02:11:48 +0000 | [diff] [blame] | 190 | continues = True |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame^] | 191 | for owner in self.owners_queue: |
| 192 | if owner in self.selected_owners: |
| 193 | continue |
| 194 | if owner in self.deselected_owners: |
| 195 | continue |
| 196 | if len(self.owners_to_files[owner] & self.unreviewed_files) == 0: |
| 197 | self.deselect_owner(owner, False) |
ikarienator@chromium.org | faf3fdf | 2013-09-20 02:11:48 +0000 | [diff] [blame] | 198 | |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame^] | 199 | while continues: |
| 200 | continues = False |
| 201 | for file_name in filter( |
| 202 | lambda file_name: len(self.files_to_owners[file_name]) == 1, |
| 203 | self.unreviewed_files): |
| 204 | owner = first(self.files_to_owners[file_name]) |
| 205 | self.select_owner(owner, False) |
| 206 | continues = True |
| 207 | break |
ikarienator@chromium.org | faf3fdf | 2013-09-20 02:11:48 +0000 | [diff] [blame] | 208 | |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame^] | 209 | def print_file_info(self, file_name, except_owner=''): |
| 210 | if file_name not in self.unreviewed_files: |
| 211 | self.writeln( |
| 212 | self.greyed(file_name + ' (by ' + |
| 213 | self.bold_name(self.reviewed_by[file_name]) + ')')) |
| 214 | else: |
| 215 | if len(self.files_to_owners[file_name]) <= 3: |
| 216 | other_owners = [] |
| 217 | for ow in self.files_to_owners[file_name]: |
| 218 | if ow != except_owner: |
| 219 | other_owners.append(self.bold_name(ow)) |
| 220 | self.writeln(file_name + ' [' + (', '.join(other_owners)) + ']') |
| 221 | else: |
| 222 | self.writeln( |
| 223 | file_name + ' [' + |
| 224 | self.bold(str(len(self.files_to_owners[file_name]))) + ']') |
ikarienator@chromium.org | faf3fdf | 2013-09-20 02:11:48 +0000 | [diff] [blame] | 225 | |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame^] | 226 | def print_file_info_detailed(self, file_name): |
| 227 | self.writeln(file_name) |
Bruce Dawson | 9b4a057 | 2020-05-06 17:05:01 +0000 | [diff] [blame] | 228 | self.indent() |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame^] | 229 | for ow in sorted(self.files_to_owners[file_name]): |
| 230 | if ow in self.deselected_owners: |
| 231 | self.writeln(self.bold_name(self.greyed(ow))) |
| 232 | elif ow in self.selected_owners: |
| 233 | self.writeln(self.bold_name(self.greyed(ow))) |
| 234 | else: |
| 235 | self.writeln(self.bold_name(ow)) |
Bruce Dawson | 9b4a057 | 2020-05-06 17:05:01 +0000 | [diff] [blame] | 236 | self.unindent() |
ikarienator@chromium.org | faf3fdf | 2013-09-20 02:11:48 +0000 | [diff] [blame] | 237 | |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame^] | 238 | def print_owned_files_for(self, owner): |
| 239 | # Print owned files |
| 240 | self.writeln(self.bold_name(owner)) |
| 241 | self.writeln( |
| 242 | self.bold_name(owner) + ' owns ' + |
| 243 | str(len(self.owners_to_files[owner])) + ' file(s):') |
| 244 | self.indent() |
| 245 | for file_name in sorted(self.owners_to_files[owner]): |
| 246 | self.print_file_info(file_name, owner) |
| 247 | self.unindent() |
| 248 | self.writeln() |
ikarienator@chromium.org | faf3fdf | 2013-09-20 02:11:48 +0000 | [diff] [blame] | 249 | |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame^] | 250 | def list_owners(self, owners_queue): |
| 251 | if (len(self.owners_to_files) - len(self.deselected_owners) - |
| 252 | len(self.selected_owners)) > 3: |
| 253 | for ow in owners_queue: |
| 254 | if (ow not in self.deselected_owners |
| 255 | and ow not in self.selected_owners): |
| 256 | self.writeln(self.bold_name(ow)) |
| 257 | else: |
| 258 | for ow in owners_queue: |
| 259 | if (ow not in self.deselected_owners |
| 260 | and ow not in self.selected_owners): |
| 261 | self.writeln() |
| 262 | self.print_owned_files_for(ow) |
ikarienator@chromium.org | faf3fdf | 2013-09-20 02:11:48 +0000 | [diff] [blame] | 263 | |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame^] | 264 | def list_files(self): |
| 265 | self.indent() |
| 266 | if len(self.unreviewed_files) > 5: |
| 267 | for file_name in sorted(self.unreviewed_files): |
| 268 | self.print_file_info(file_name) |
| 269 | else: |
| 270 | for file_name in self.unreviewed_files: |
| 271 | self.print_file_info_detailed(file_name) |
| 272 | self.unindent() |
ikarienator@chromium.org | faf3fdf | 2013-09-20 02:11:48 +0000 | [diff] [blame] | 273 | |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame^] | 274 | def pick_owner(self, ow): |
| 275 | # Allowing to omit domain suffixes |
| 276 | if ow not in self.owners_to_files: |
| 277 | if ow + self.email_postfix in self.owners_to_files: |
| 278 | ow += self.email_postfix |
ikarienator@chromium.org | faf3fdf | 2013-09-20 02:11:48 +0000 | [diff] [blame] | 279 | |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame^] | 280 | if ow not in self.owners_to_files: |
| 281 | self.writeln( |
| 282 | 'You cannot pick ' + self.bold_name(ow) + ' manually. ' + |
| 283 | 'It\'s an invalid name or not related to the change list.') |
| 284 | return False |
ikarienator@chromium.org | faf3fdf | 2013-09-20 02:11:48 +0000 | [diff] [blame] | 285 | |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame^] | 286 | if ow in self.selected_owners: |
| 287 | self.writeln('You cannot pick ' + self.bold_name(ow) + |
| 288 | ' manually. ' + 'It\'s already selected.') |
| 289 | return False |
ikarienator@chromium.org | faf3fdf | 2013-09-20 02:11:48 +0000 | [diff] [blame] | 290 | |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame^] | 291 | if ow in self.deselected_owners: |
| 292 | self.writeln('You cannot pick ' + self.bold_name(ow) + |
| 293 | ' manually.' + 'It\'s already unselected.') |
| 294 | return False |
ikarienator@chromium.org | faf3fdf | 2013-09-20 02:11:48 +0000 | [diff] [blame] | 295 | |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame^] | 296 | self.select_owner(ow) |
| 297 | return True |
ikarienator@chromium.org | faf3fdf | 2013-09-20 02:11:48 +0000 | [diff] [blame] | 298 | |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame^] | 299 | def print_result(self): |
| 300 | # Print results |
| 301 | self.writeln() |
| 302 | self.writeln() |
| 303 | if len(self.selected_owners) == 0: |
| 304 | self.writeln('This change list already has owner-reviewers for all ' |
| 305 | 'files.') |
| 306 | self.writeln('Use --ignore-current if you want to ignore them.') |
| 307 | else: |
| 308 | self.writeln('** You selected these owners **') |
| 309 | self.writeln() |
| 310 | for owner in self.selected_owners: |
| 311 | self.writeln(self.bold_name(owner) + ':') |
| 312 | self.indent() |
| 313 | for file_name in sorted(self.owners_to_files[owner]): |
| 314 | self.writeln(file_name) |
| 315 | self.unindent() |
ikarienator@chromium.org | faf3fdf | 2013-09-20 02:11:48 +0000 | [diff] [blame] | 316 | |
Mike Frysinger | 124bb8e | 2023-09-06 05:48:55 +0000 | [diff] [blame^] | 317 | def bold(self, text): |
| 318 | return self.COLOR_BOLD + text + self.COLOR_RESET |
| 319 | |
| 320 | def bold_name(self, name): |
| 321 | return (self.COLOR_BOLD + name.replace(self.email_postfix, '') + |
| 322 | self.COLOR_RESET) |
| 323 | |
| 324 | def greyed(self, text): |
| 325 | return self.COLOR_GREY + text + self.COLOR_RESET |
| 326 | |
| 327 | def indent(self): |
| 328 | self.indentation += 1 |
| 329 | |
| 330 | def unindent(self): |
| 331 | self.indentation -= 1 |
| 332 | |
| 333 | def print_indent(self): |
| 334 | return ' ' * self.indentation |
| 335 | |
| 336 | def writeln(self, text=''): |
| 337 | print(self.print_indent() + text) |
| 338 | |
| 339 | def hr(self): |
| 340 | self.writeln('=====================') |
| 341 | |
| 342 | def print_info(self, owner): |
| 343 | self.hr() |
| 344 | self.writeln( |
| 345 | self.bold(str(len(self.unreviewed_files))) + ' file(s) left.') |
| 346 | self.print_owned_files_for(owner) |
| 347 | |
| 348 | def input_command(self, owner): |
| 349 | self.writeln('Add ' + self.bold_name(owner) + ' as your reviewer? ') |
| 350 | return gclient_utils.AskForData( |
| 351 | '[yes/no/Defer/pick/files/owners/quit/restart]: ').lower() |