OLD | NEW |
(Empty) | |
| 1 #!/usr/bin/env python |
| 2 |
| 3 # For Spreadsheets: |
| 4 try: |
| 5 from xml.etree import ElementTree |
| 6 except ImportError: |
| 7 from elementtree import ElementTree |
| 8 import gdata.spreadsheet.service |
| 9 import gdata.service |
| 10 import atom.service |
| 11 import gdata.spreadsheet |
| 12 import atom |
| 13 |
| 14 # For Issue Tracker: |
| 15 import gdata.projecthosting.client |
| 16 import gdata.projecthosting.data |
| 17 import gdata.gauth |
| 18 import gdata.client |
| 19 import gdata.data |
| 20 import atom.http_core |
| 21 import atom.core |
| 22 |
| 23 # For this script: |
| 24 import getpass |
| 25 from optparse import OptionParser |
| 26 import pickle |
| 27 |
| 28 |
| 29 # Settings |
| 30 credentials_store = 'creds.dat' |
| 31 |
| 32 class Merger(object): |
| 33 def __init__(self, ss_key, ss_ws_key, tracker_message, tracker_project, |
| 34 debug): |
| 35 self.ss_key = ss_key |
| 36 self.ss_ws_key = ss_ws_key |
| 37 self.tracker_message = tracker_message |
| 38 self.tracker_project = tracker_project |
| 39 self.debug_enabled = debug |
| 40 self.user_agent = 'adlr-tracker-spreadsheet-merger' |
| 41 self.it_keys = ['id', 'owner', 'status', 'title'] |
| 42 |
| 43 def debug(self): |
| 44 """Returns true if debug mode is set.""" |
| 45 return self.debug_enabled |
| 46 |
| 47 def print_feed(self, feed): |
| 48 'Handy for debugging' |
| 49 for i, entry in enumerate(feed.entry): |
| 50 print 'id:', entry.id |
| 51 if isinstance(feed, gdata.spreadsheet.SpreadsheetsCellsFeed): |
| 52 print '%s %s\n' % (entry.title.text, entry.content.text) |
| 53 elif isinstance(feed, gdata.spreadsheet.SpreadsheetsListFeed): |
| 54 print '%s %s %s' % (i, entry.title.text, entry.content.text) |
| 55 # Print this row's value for each column (the custom dictionary is |
| 56 # built using the gsx: elements in the entry.) |
| 57 print 'Contents:' |
| 58 for key in entry.custom: |
| 59 print ' %s: %s' % (key, entry.custom[key].text) |
| 60 print '\n', |
| 61 else: |
| 62 print '%s %s\n' % (i, entry.title.text) |
| 63 |
| 64 |
| 65 def tracker_login(self): |
| 66 """Logs user into Tracker, using cached credentials if possible. |
| 67 Saves credentials after login.""" |
| 68 self.it_client = gdata.projecthosting.client.ProjectHostingClient() |
| 69 self.it_client.source = self.user_agent |
| 70 |
| 71 self.load_creds() |
| 72 |
| 73 if self.tracker_token and self.tracker_user: |
| 74 print 'Using existing credential for tracker login' |
| 75 self.it_client.auth_token = self.tracker_token |
| 76 else: |
| 77 self.tracker_user = raw_input('Issue Tracker Login:') |
| 78 password = getpass.getpass('Password:') |
| 79 self.it_client.ClientLogin(self.tracker_user, password, |
| 80 source=self.user_agent, service='code', |
| 81 account_type='GOOGLE') |
| 82 self.tracker_token = self.it_client.auth_token |
| 83 self.store_creds() |
| 84 |
| 85 def spreadsheet_login(self): |
| 86 """Logs user into Google Spreadsheets, using cached credentials if possible. |
| 87 Saves credentials after login.""" |
| 88 self.gd_client = gdata.spreadsheet.service.SpreadsheetsService() |
| 89 self.gd_client.source = self.user_agent |
| 90 |
| 91 self.load_creds() |
| 92 if self.docs_token: |
| 93 print 'Using existing credential for docs login' |
| 94 self.gd_client.SetClientLoginToken(self.docs_token) |
| 95 else: |
| 96 self.gd_client.email = raw_input('Google Docs Login:') |
| 97 self.gd_client.password = getpass.getpass('Password:') |
| 98 self.gd_client.ProgrammaticLogin() |
| 99 self.docs_token = self.gd_client.GetClientLoginToken() |
| 100 self.store_creds() |
| 101 |
| 102 def fetch_spreadsheet_issues(self): |
| 103 """Fetches all issues from the user-specified spreadsheet. Returns |
| 104 them as an array or dictionaries.""" |
| 105 feed = self.gd_client.GetListFeed(self.ss_key, self.ss_ws_key) |
| 106 issues = [] |
| 107 for entry in feed.entry: |
| 108 issue = {} |
| 109 for key in entry.custom: |
| 110 issue[key] = entry.custom[key].text |
| 111 issue['__raw_entry'] = entry |
| 112 issues.append(issue) |
| 113 return issues |
| 114 |
| 115 def fetch_tracker_issues(self): |
| 116 """Fetches all issues matching the query and returns them as an array |
| 117 of dictionaries.""" |
| 118 issues = [] |
| 119 got_results = True |
| 120 index = 1 |
| 121 while got_results: |
| 122 query = gdata.projecthosting.client.Query(label='Area-Installer', |
| 123 max_results=50, |
| 124 start_index=index) |
| 125 feed = self.it_client.get_issues('chromium-os', query=query) |
| 126 if not feed.entry: |
| 127 got_results = False |
| 128 index = index + len(feed.entry) |
| 129 for issue in feed.entry: |
| 130 issue_dict = {} |
| 131 issue_dict['labels'] = [label.text for label in issue.label] |
| 132 issue_dict['id'] = issue.id.text.split('/')[-1] |
| 133 issue_dict['title'] = issue.title.text |
| 134 issue_dict['status'] = issue.status.text |
| 135 if issue.owner: |
| 136 issue_dict['owner'] = issue.owner.username.text |
| 137 issues.append(issue_dict) |
| 138 return issues |
| 139 |
| 140 def store_creds(self): |
| 141 """Stores login credentials to disk.""" |
| 142 obj = {} |
| 143 if self.docs_token: |
| 144 obj['docs_token'] = self.docs_token |
| 145 if self.tracker_token: |
| 146 obj['tracker_token'] = self.tracker_token |
| 147 if self.tracker_user: |
| 148 obj['tracker_user'] = self.tracker_user |
| 149 try: |
| 150 f = open(credentials_store, 'w') |
| 151 pickle.dump(obj, f) |
| 152 f.close() |
| 153 except IOError: |
| 154 print 'Unable to store credentials' |
| 155 |
| 156 def load_creds(self): |
| 157 """Loads login credentials from disk.""" |
| 158 self.docs_token = None |
| 159 self.tracker_token = None |
| 160 self.tracker_user = None |
| 161 try: |
| 162 f = open(credentials_store, 'r') |
| 163 obj = pickle.load(f) |
| 164 f.close() |
| 165 if obj.has_key('docs_token'): |
| 166 self.docs_token = obj['docs_token'] |
| 167 if obj.has_key('tracker_token'): |
| 168 self.tracker_token = obj['tracker_token'] |
| 169 if obj.has_key('tracker_user'): |
| 170 self.tracker_user = obj['tracker_user'] |
| 171 except IOError: |
| 172 print 'Unable to load credentials' |
| 173 |
| 174 def browse(self): |
| 175 """Browses Spreadsheets to help the user find the spreadsheet and |
| 176 worksheet keys""" |
| 177 print 'Browsing spreadsheets...' |
| 178 |
| 179 if self.ss_key and self.ss_ws_key: |
| 180 print 'You already passed in --ss_key and --ss_ws_key. No need to browse.' |
| 181 return |
| 182 |
| 183 print 'Logging in...' |
| 184 self.spreadsheet_login() |
| 185 |
| 186 if not self.ss_key: |
| 187 print 'Fetching spreadsheets...' |
| 188 feed = self.gd_client.GetSpreadsheetsFeed() |
| 189 print '' |
| 190 print 'Spreadsheet key - Title' |
| 191 for entry in feed.entry: |
| 192 key = entry.id.text.split('/')[-1] |
| 193 title = entry.title.text |
| 194 print '"%s" - "%s"' % (key, title) |
| 195 print '' |
| 196 print 'Done. Rerun with --ss_key=KEY to browse a list of worksheet keys.' |
| 197 else: |
| 198 print 'Fetching worksheets for spreadsheet', self.ss_key |
| 199 feed = self.gd_client.GetWorksheetsFeed(self.ss_key) |
| 200 for entry in feed.entry: |
| 201 key = entry.id.text.split('/')[-1] |
| 202 title = entry.title.text |
| 203 print '' |
| 204 print 'Worksheet key - Title' |
| 205 print '"%s" - "%s"' % (key, title) |
| 206 print '' |
| 207 print 'Done. You now have keys for --ss_key and --ss_ws_key.' |
| 208 |
| 209 def tracker_issue_for_id(self, issues, id_): |
| 210 """Returns the element of issues which has id_ for the key 'id'""" |
| 211 for issue in issues: |
| 212 if issue['id'] == id_: |
| 213 return issue |
| 214 return None |
| 215 |
| 216 def spreadsheet_issue_to_tracker_dict(self, ss_issue): |
| 217 """Converts a spreadsheet issue to the dict format that is used to |
| 218 represent a tracker issue.""" |
| 219 ret = {} |
| 220 ret['project'] = self.tracker_project |
| 221 ret['title'] = ss_issue['title'] |
| 222 ret['summary'] = self.tracker_message |
| 223 ret['owner'] = ss_issue['owner'] |
| 224 if ss_issue.get('status') is not None: |
| 225 ret['status'] = ss_issue['status'] |
| 226 ret['labels'] = [] |
| 227 for (key, value) in ss_issue.items(): |
| 228 if key.endswith('-') and (value is not None): |
| 229 ret['labels'].append(key.title() + value) |
| 230 return ret |
| 231 |
| 232 def label_from_prefix(self, prefix, corpus): |
| 233 """Given a corpus (array of lable strings), return the first label |
| 234 that begins with the specified prefix.""" |
| 235 for label in corpus: |
| 236 if label.startswith(prefix): |
| 237 return label |
| 238 return None |
| 239 |
| 240 def update_spreadsheet_issue_to_tracker_dict(self, ss_issue, t_issue): |
| 241 """Updates a given tracker issue with data from the spreadsheet issue.""" |
| 242 ret = {} |
| 243 ret['title'] = ss_issue['title'] |
| 244 ret['id'] = ss_issue['id'] |
| 245 ret['summary'] = self.tracker_message |
| 246 if ss_issue['status'] != t_issue['status']: |
| 247 ret['status'] = ss_issue['status'] |
| 248 |
| 249 if ss_issue.get('owner'): |
| 250 if (not t_issue.has_key('owner')) or \ |
| 251 (ss_issue['owner'] != t_issue['owner']): |
| 252 ret['owner'] = ss_issue['owner'] |
| 253 # labels |
| 254 ret['labels'] = [] |
| 255 for (key, value) in ss_issue.items(): |
| 256 caps_key = key.title() |
| 257 if not caps_key.endswith('-'): |
| 258 continue |
| 259 ss_label = None |
| 260 if value: |
| 261 ss_label = caps_key + value.title() |
| 262 t_label = self.label_from_prefix(caps_key, t_issue['labels']) |
| 263 |
| 264 if t_label is None and ss_label is None: |
| 265 # Nothing |
| 266 continue |
| 267 |
| 268 if (ss_label is None) or (ss_label != t_label): |
| 269 ret['labels'].append('-' + t_label) |
| 270 |
| 271 if (t_label is None) or (t_label != ss_label): |
| 272 ret['labels'].append(ss_label) |
| 273 return ret |
| 274 |
| 275 def tracker_issue_has_changed(self, t_issue, ss_issue): |
| 276 """Returns True iff ss_issue indicates changes in t_issue that need to be |
| 277 committed up to the Issue Tracker.""" |
| 278 if t_issue is None: |
| 279 return True |
| 280 potential_commit = \ |
| 281 self.update_spreadsheet_issue_to_tracker_dict(ss_issue, t_issue) |
| 282 |
| 283 if potential_commit.has_key('status') or \ |
| 284 potential_commit.has_key('owner') or \ |
| 285 (len(potential_commit['labels']) > 0): |
| 286 return True |
| 287 if potential_commit['title'] != t_issue['title']: |
| 288 return True |
| 289 return False |
| 290 |
| 291 def spreadsheet_to_tracker_commits(self, ss_issues, t_issues): |
| 292 """Given the current state of all spreadsheet issues and tracker issues, |
| 293 returns a list of all commits that need to go to tracker to get it in |
| 294 line with the spreadsheet.""" |
| 295 ret = [] |
| 296 for ss_issue in ss_issues: |
| 297 t_issue = self.tracker_issue_for_id(t_issues, ss_issue['id']) |
| 298 commit = {} |
| 299 # TODO see if an update is needed at all |
| 300 if t_issue is None: |
| 301 commit['type'] = 'append' |
| 302 commit['dict'] = self.spreadsheet_issue_to_tracker_dict(ss_issue) |
| 303 commit['__ss_issue'] = ss_issue |
| 304 else: |
| 305 if not self.tracker_issue_has_changed(t_issue, ss_issue): |
| 306 continue |
| 307 commit['type'] = 'update' |
| 308 commit['dict'] = \ |
| 309 self.update_spreadsheet_issue_to_tracker_dict(ss_issue, t_issue) |
| 310 ret.append(commit) |
| 311 return ret |
| 312 |
| 313 def fetch_issues(self): |
| 314 """Logs into Docs/Tracker, and fetches spreadsheet and tracker issues""" |
| 315 print 'Logging into Docs...' |
| 316 self.spreadsheet_login() |
| 317 print 'Logging into Tracker...' |
| 318 self.tracker_login() |
| 319 |
| 320 print 'Fetching tracker issues...' |
| 321 t_issues = self.fetch_tracker_issues() |
| 322 if self.debug(): |
| 323 print 'Tracker issues:', t_issues |
| 324 print 'Fetching spreadsheet issues...' |
| 325 ss_issues = self.fetch_spreadsheet_issues() |
| 326 if self.debug(): |
| 327 print 'Spreadsheet issues:', ss_issues |
| 328 return (t_issues, ss_issues) |
| 329 |
| 330 def spreadsheet_to_tracker(self): |
| 331 """High-level function to manage migrating data from the spreadsheet |
| 332 to Tracker.""" |
| 333 (t_issues, ss_issues) = self.fetch_issues() |
| 334 print 'Calculating deltas...' |
| 335 commits = self.spreadsheet_to_tracker_commits(ss_issues, t_issues) |
| 336 if self.debug(): |
| 337 print 'got commits:', commits |
| 338 if len(commits) == 0: |
| 339 print 'No deltas. Done.' |
| 340 return |
| 341 |
| 342 for commit in commits: |
| 343 dic = commit['dict'] |
| 344 labels = dic.get('labels') |
| 345 owner = dic.get('owner') |
| 346 status = dic.get('status') |
| 347 |
| 348 if commit['type'] == 'append': |
| 349 print 'Creating new tracker issue...' |
| 350 created = self.it_client.add_issue(self.tracker_project, |
| 351 dic['title'], |
| 352 self.tracker_message, |
| 353 self.tracker_user, |
| 354 labels=labels, |
| 355 owner=owner, |
| 356 status=status) |
| 357 issue_id = created.id.text.split('/')[-1] |
| 358 print 'Created issue with id:', issue_id |
| 359 print 'Write id back to spreadsheet row...' |
| 360 raw_entry = commit['__ss_issue']['__raw_entry'] |
| 361 ss_issue = commit['__ss_issue'] |
| 362 del ss_issue['__raw_entry'] |
| 363 ss_issue.update({'id': issue_id}) |
| 364 self.gd_client.UpdateRow(raw_entry, ss_issue) |
| 365 print 'Done.' |
| 366 else: |
| 367 print 'Updating issue with id:', dic['id'] |
| 368 self.it_client.update_issue(self.tracker_project, |
| 369 dic['id'], |
| 370 self.tracker_user, |
| 371 comment=self.tracker_message, |
| 372 status=status, |
| 373 owner=owner, |
| 374 labels=labels) |
| 375 print 'Done.' |
| 376 |
| 377 def spreadsheet_issue_for_id(self, issues, id_): |
| 378 """Given the array of spreadsheet issues, return the first one that |
| 379 has id_ for the key 'id'.""" |
| 380 for issue in issues: |
| 381 if issue['id'] == id_: |
| 382 return issue |
| 383 return None |
| 384 |
| 385 def value_for_key_in_labels(self, label_array, prefix): |
| 386 """Given an array of labels and a prefix, return the non-prefix part |
| 387 of the first label that has that prefix. E.g. if label_array is |
| 388 ["Mstone-R7", "Area-Installer"] and prefix is "Area-", returns |
| 389 "Installer".""" |
| 390 for label in label_array: |
| 391 if label.startswith(prefix): |
| 392 return label[len(prefix):] |
| 393 return None |
| 394 |
| 395 def tracker_issue_to_spreadsheet_issue(self, t_issue, ss_keys): |
| 396 """Converts a tracker issue to the format used by spreadsheet, given |
| 397 the row headings ss_keys.""" |
| 398 new_row = {} |
| 399 for key in ss_keys: |
| 400 if key.endswith('-'): |
| 401 # label |
| 402 new_row[key] = self.value_for_key_in_labels(t_issue['labels'], |
| 403 key.title()) |
| 404 # Special cases |
| 405 if key in self.it_keys and key in t_issue: |
| 406 new_row[key] = t_issue[key] |
| 407 return new_row |
| 408 |
| 409 def spreadsheet_row_needs_update(self, ss_issue, t_issue): |
| 410 """Returns True iff the spreadsheet issue passed in needs to be updated |
| 411 to match data in the tracker issue.""" |
| 412 new_ss_issue = self.tracker_issue_to_spreadsheet_issue(t_issue, |
| 413 ss_issue.keys()) |
| 414 for key in new_ss_issue.keys(): |
| 415 if not ss_issue.has_key(key): |
| 416 continue |
| 417 if new_ss_issue[key] != ss_issue[key]: |
| 418 return True |
| 419 return False |
| 420 |
| 421 def tracker_to_spreadsheet_commits(self, t_issues, ss_issues): |
| 422 """Given the current set of spreadsheet and tracker issues, computes |
| 423 commits needed to go to Spreadsheets to get the spreadsheet in line |
| 424 with what's in Tracker.""" |
| 425 ret = [] |
| 426 keys = ss_issues[0].keys() |
| 427 for t_issue in t_issues: |
| 428 commit = {} |
| 429 ss_issue = self.spreadsheet_issue_for_id(ss_issues, t_issue['id']) |
| 430 if ss_issue is None: |
| 431 # New issue |
| 432 commit['new_row'] = self.tracker_issue_to_spreadsheet_issue(t_issue, |
| 433 keys) |
| 434 commit['type'] = 'append' |
| 435 elif self.spreadsheet_row_needs_update(ss_issue, t_issue): |
| 436 commit['__raw_entry'] = ss_issue['__raw_entry'] |
| 437 del ss_issue['__raw_entry'] |
| 438 ss_issue.update(self.tracker_issue_to_spreadsheet_issue(t_issue, keys)) |
| 439 commit['dict'] = ss_issue |
| 440 commit['type'] = 'update' |
| 441 else: |
| 442 continue |
| 443 ret.append(commit) |
| 444 return ret |
| 445 |
| 446 def tracker_to_spreadsheet(self): |
| 447 """High-level function to migrate data from Tracker to the spreadsheet.""" |
| 448 (t_issues, ss_issues) = self.fetch_issues() |
| 449 if len(ss_issues) == 0: |
| 450 raise Exception('Error: must have at least one non-header row in '\ |
| 451 'spreadsheet') |
| 452 return |
| 453 ss_keys = ss_issues[0].keys() |
| 454 |
| 455 print 'Calculating deltas...' |
| 456 ss_commits = self.tracker_to_spreadsheet_commits(t_issues, ss_issues) |
| 457 if self.debug(): |
| 458 print 'commits:', ss_commits |
| 459 if not ss_commits: |
| 460 print 'Nothing to commit.' |
| 461 return |
| 462 print 'Committing...' |
| 463 for commit in ss_commits: |
| 464 if self.debug(): |
| 465 print 'Operating on commit:', commit |
| 466 if commit['type'] == 'append': |
| 467 print 'Appending new row...' |
| 468 self.gd_client.InsertRow(commit['new_row'], self.ss_key, self.ss_ws_key) |
| 469 if commit['type'] == 'update': |
| 470 print 'Updating row...' |
| 471 self.gd_client.UpdateRow(commit['__raw_entry'], commit['dict']) |
| 472 print 'Done.' |
| 473 |
| 474 def main(): |
| 475 class PureEpilogOptionParser(OptionParser): |
| 476 def format_epilog(self, formatter): |
| 477 return self.epilog |
| 478 |
| 479 parser = PureEpilogOptionParser() |
| 480 parser.add_option('-a', '--action', dest='action', metavar='ACTION', |
| 481 help='Action to perform') |
| 482 parser.add_option('-d', '--debug', action='store_true', dest='debug', |
| 483 default=False, help='Print debug output.') |
| 484 parser.add_option('-m', '--message', dest='message', metavar='TEXT', |
| 485 help='Log message when updating Tracker issues') |
| 486 parser.add_option('--ss_key', dest='ss_key', metavar='KEY', |
| 487 help='Spreadsheets key (find with browse action)') |
| 488 parser.add_option('--ss_ws_key', dest='ss_ws_key', metavar='KEY', |
| 489 help='Spreadsheets worksheet key (find with browse action)') |
| 490 parser.add_option('--tracker_project', dest='tracker_project', |
| 491 metavar='PROJECT', |
| 492 help='Tracker project (default: chromium-os)', |
| 493 default='chromium-os') |
| 494 parser.epilog = """Actions: |
| 495 browse -- browse spreadsheets to find spreadsheet and worksheet keys. |
| 496 ss_to_t -- for each entry in spreadsheet, apply its values to tracker. |
| 497 If no ID is in the spreadsheet row, a new tracker item is created |
| 498 and the spreadsheet is updated. |
| 499 t_to_ss -- for each tracker entry, apply it or add it to the spreadsheet. |
| 500 |
| 501 |
| 502 This script can be used to migrate Issue Tracker issues between Issue Tracker |
| 503 and Google Spreadsheets. The spreadsheet should have certain columns in any |
| 504 order: Id, Owner, Title, Status. The spreadsheet may have any label of the |
| 505 form 'Key-'. For those labels that end in '-', this script assumes the cell |
| 506 value and the header form a label that should be applied to the issue. E.g. |
| 507 if the spredsheet has a column named 'Mstone-' and a cell under it called |
| 508 'R8' that corresponds to the label 'Mstone-R8' in Issue Tracker. |
| 509 |
| 510 To migrate data, you must choose on each invocation of this script if you |
| 511 wish to migrate data from Issue Tracker to a spreadsheet of vice-versa. |
| 512 |
| 513 When migrating from Tracker, all found issues based on the query |
| 514 (which is currently hard-coded to "label=Area-Installer") will be inserted |
| 515 into the spreadsheet (overwritng existing cells if a row with matching ID |
| 516 is found). Custom columns in the spreadsheet won't be overwritten, so if |
| 517 the spreadsheet contains extra columns about issues (e.g. time estimates) |
| 518 they will be preserved. |
| 519 |
| 520 When migrating from spreadsheet to Tracker, each row in the spreadsheet |
| 521 is compared to existing tracker issues that match the query |
| 522 (which is currently hard-coded to "label=Area-Installer"). If the |
| 523 spreadsheet row has no Id, a new Issue Tracker issue is created and the new |
| 524 Id is written back to the spreadsheet. If an existing tracker issue exists, |
| 525 it's updated with the data from the spreadsheet if anything has changed. |
| 526 |
| 527 Suggested usage: |
| 528 - Create a spreadsheet with columns Id, Owner, Title, Status, and any label |
| 529 prefixes as desired. |
| 530 - Run this script with '-b' to browse your spreadsheet and get the |
| 531 spreadsheet key. |
| 532 - Run this script again with '-b' and the spreadsheet key to get the |
| 533 worksheet key. |
| 534 - Run this script with "-a t_to_ss" or "-a ss_to_t" to migrate data in either |
| 535 direction. |
| 536 |
| 537 Known issues: |
| 538 - query is currently hardcoded to label=Area-Installer. That should be |
| 539 a command-line flag. |
| 540 - When creating a new issue on tracker, the owner field isn't set. I (adlr) |
| 541 am not sure why. Workaround: If you rerun this script, tho, it will detect |
| 542 a delta and update the tracker issue with the owner, which seems to succeed. |
| 543 """ |
| 544 |
| 545 (options, args) = parser.parse_args() |
| 546 |
| 547 merger = Merger(options.ss_key, options.ss_ws_key, |
| 548 options.message, options.tracker_project, |
| 549 options.debug) |
| 550 if options.action == 'browse': |
| 551 merger.browse() |
| 552 elif options.action == 'ss_to_t': |
| 553 if not options.message: |
| 554 print 'Error: when updating tracker, -m MESSAGE required.' |
| 555 return |
| 556 merger.spreadsheet_to_tracker() |
| 557 elif options.action == 't_to_ss': |
| 558 merger.tracker_to_spreadsheet() |
| 559 else: |
| 560 raise Exception('Unknown action requested.') |
| 561 |
| 562 if __name__ == '__main__': |
| 563 main() |
OLD | NEW |