OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 """Get stats about your activity. | 6 """Get stats about your activity. |
7 | 7 |
8 Example: | 8 Example: |
9 - my_activity.py for stats for the current week (last week on mondays). | 9 - my_activity.py for stats for the current week (last week on mondays). |
10 - my_activity.py -Q for stats for last quarter. | 10 - my_activity.py -Q for stats for last quarter. |
(...skipping 452 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
463 issues = filter(self.filter_issue, issues) | 463 issues = filter(self.filter_issue, issues) |
464 issues = sorted(issues, key=lambda i: i['modified'], reverse=True) | 464 issues = sorted(issues, key=lambda i: i['modified'], reverse=True) |
465 return issues | 465 return issues |
466 | 466 |
467 def process_google_code_issue(self, project, issue): | 467 def process_google_code_issue(self, project, issue): |
468 ret = {} | 468 ret = {} |
469 ret['created'] = datetime_from_google_code(issue['published']['$t']) | 469 ret['created'] = datetime_from_google_code(issue['published']['$t']) |
470 ret['modified'] = datetime_from_google_code(issue['updated']['$t']) | 470 ret['modified'] = datetime_from_google_code(issue['updated']['$t']) |
471 | 471 |
472 ret['owner'] = '' | 472 ret['owner'] = '' |
473 if 'issues:owner' in issue: | 473 if 'issues$owner' in issue: |
474 ret['owner'] = issue['issues:owner'][0]['issues:username'][0]['$t'] | 474 ret['owner'] = issue['issues$owner']['issues$username']['$t'] |
475 ret['author'] = issue['author'][0]['name']['$t'] | 475 ret['author'] = issue['author'][0]['name']['$t'] |
476 | 476 |
477 if 'shorturl' in project: | 477 if 'shorturl' in project: |
478 issue_id = issue['id']['$t'] | 478 issue_id = issue['id']['$t'] |
479 issue_id = issue_id[issue_id.rfind('/') + 1:] | 479 issue_id = issue_id[issue_id.rfind('/') + 1:] |
480 ret['url'] = 'http://%s/%d' % (project['shorturl'], int(issue_id)) | 480 ret['url'] = 'http://%s/%d' % (project['shorturl'], int(issue_id)) |
481 else: | 481 else: |
482 issue_url = issue['link'][1] | 482 issue_url = issue['link'][1] |
483 if issue_url['rel'] != 'alternate': | 483 if issue_url['rel'] != 'alternate': |
484 raise RuntimeError | 484 raise RuntimeError |
(...skipping 216 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
701 except ImportError: | 701 except ImportError: |
702 print "Failed to import WebKit committer list, skipping WebKit checks." | 702 print "Failed to import WebKit committer list, skipping WebKit checks." |
703 self.webkit_repo = None | 703 self.webkit_repo = None |
704 return | 704 return |
705 | 705 |
706 if not webkit_account(self.user): | 706 if not webkit_account(self.user): |
707 email = self.user + "@chromium.org" | 707 email = self.user + "@chromium.org" |
708 print "No %s in committers.py, skipping WebKit checks." % email | 708 print "No %s in committers.py, skipping WebKit checks." % email |
709 self.webkit_repo = None | 709 self.webkit_repo = None |
710 | 710 |
711 @staticmethod | 711 def print_change(self, change): |
712 def print_change(change): | 712 self.print_generic(self.options.output_format, |
713 print '%s %s' % ( | 713 self.options.output_format_changes, |
714 change['review_url'], | 714 change['header'], |
715 change['header'], | 715 change['review_url'], |
716 ) | 716 change['author']) |
| 717 |
| 718 def print_issue(self, issue): |
| 719 optional_values = { |
| 720 'owner': issue['owner'], |
| 721 } |
| 722 self.print_generic(self.options.output_format, |
| 723 self.options.output_format_issues, |
| 724 issue['header'], |
| 725 issue['url'], |
| 726 issue['author'], |
| 727 optional_values) |
| 728 |
| 729 def print_review(self, review): |
| 730 self.print_generic(self.options.output_format, |
| 731 self.options.output_format_reviews, |
| 732 review['header'], |
| 733 review['review_url'], |
| 734 review['author']) |
717 | 735 |
718 @staticmethod | 736 @staticmethod |
719 def print_issue(issue): | 737 def print_generic(default_fmt, specific_fmt, |
720 print '%s %s' % ( | 738 title, url, author, |
721 issue['url'], | 739 optional_values=None): |
722 issue['header'], | 740 output_format = specific_fmt if specific_fmt is not None else default_fmt |
723 ) | 741 output_format = unicode(output_format) |
| 742 required_values = { |
| 743 'title': title, |
| 744 'url': url, |
| 745 'author': author, |
| 746 } |
| 747 # Merge required and optional values. |
| 748 if optional_values is not None: |
| 749 values = dict(required_values.items() + optional_values.items()) |
| 750 else: |
| 751 values = required_values |
| 752 print output_format.format(**values) |
| 753 |
724 | 754 |
725 def filter_issue(self, issue, should_filter_by_user=True): | 755 def filter_issue(self, issue, should_filter_by_user=True): |
726 def maybe_filter_username(email): | 756 def maybe_filter_username(email): |
727 return not should_filter_by_user or username(email) == self.user | 757 return not should_filter_by_user or username(email) == self.user |
728 if (maybe_filter_username(issue['author']) and | 758 if (maybe_filter_username(issue['author']) and |
729 self.filter_modified(issue['created'])): | 759 self.filter_modified(issue['created'])): |
730 return True | 760 return True |
731 if (maybe_filter_username(issue['owner']) and | 761 if (maybe_filter_username(issue['owner']) and |
732 (self.filter_modified(issue['created']) or | 762 (self.filter_modified(issue['created']) or |
733 self.filter_modified(issue['modified']))): | 763 self.filter_modified(issue['modified']))): |
(...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
784 reviews = filter(lambda r: not username(r['owner']) == self.user, reviews) | 814 reviews = filter(lambda r: not username(r['owner']) == self.user, reviews) |
785 self.reviews += reviews | 815 self.reviews += reviews |
786 | 816 |
787 for instance in git_instances: | 817 for instance in git_instances: |
788 self.reviews += self.git_search(instance, reviewer=self.user) | 818 self.reviews += self.git_search(instance, reviewer=self.user) |
789 | 819 |
790 def print_reviews(self): | 820 def print_reviews(self): |
791 if self.reviews: | 821 if self.reviews: |
792 print '\nReviews:' | 822 print '\nReviews:' |
793 for review in self.reviews: | 823 for review in self.reviews: |
794 self.print_change(review) | 824 self.print_review(review) |
795 | 825 |
796 def get_issues(self): | 826 def get_issues(self): |
797 for project in google_code_projects: | 827 for project in google_code_projects: |
798 self.issues += self.google_code_issue_search(project) | 828 self.issues += self.google_code_issue_search(project) |
799 | 829 |
800 for instance in bugzilla_instances: | 830 for instance in bugzilla_instances: |
801 self.issues += self.bugzilla_issues(instance, self.user) | 831 self.issues += self.bugzilla_issues(instance, self.user) |
802 | 832 |
| 833 def print_issues(self): |
| 834 if self.issues: |
| 835 print '\nIssues:' |
| 836 for issue in self.issues: |
| 837 self.print_issue(issue) |
| 838 |
803 def process_activities(self): | 839 def process_activities(self): |
804 # If a webkit bug was a review, don't list it as an issue. | 840 # If a webkit bug was a review, don't list it as an issue. |
805 ids = {} | 841 ids = {} |
806 for review in self.reviews + self.changes: | 842 for review in self.reviews + self.changes: |
807 if 'webkit_bug_id' in review: | 843 if 'webkit_bug_id' in review: |
808 ids[review['webkit_bug_id']] = True | 844 ids[review['webkit_bug_id']] = True |
809 | 845 |
810 def duplicate_issue(issue): | 846 def duplicate_issue(issue): |
811 if 'webkit_bug_id' not in issue: | 847 if 'webkit_bug_id' not in issue: |
812 return False | 848 return False |
813 return issue['webkit_bug_id'] in ids | 849 return issue['webkit_bug_id'] in ids |
814 | 850 |
815 self.issues = filter(lambda issue: not duplicate_issue(issue), self.issues) | 851 self.issues = filter(lambda issue: not duplicate_issue(issue), self.issues) |
816 | 852 |
817 def print_issues(self): | |
818 if self.issues: | |
819 print '\nIssues:' | |
820 for c in self.issues: | |
821 self.print_issue(c) | |
822 | |
823 def print_activity(self): | 853 def print_activity(self): |
824 self.print_changes() | 854 self.print_changes() |
825 self.print_reviews() | 855 self.print_reviews() |
826 self.print_issues() | 856 self.print_issues() |
827 | 857 |
828 | 858 |
829 def main(): | 859 def main(): |
830 # Silence upload.py. | 860 # Silence upload.py. |
831 rietveld.upload.verbosity = 0 | 861 rietveld.upload.verbosity = 0 |
832 | 862 |
(...skipping 22 matching lines...) Expand all Loading... |
855 '-Y', '--this_year', action='store_true', | 885 '-Y', '--this_year', action='store_true', |
856 help='Use this year\'s dates') | 886 help='Use this year\'s dates') |
857 parser.add_option( | 887 parser.add_option( |
858 '-w', '--week_of', metavar='<date>', | 888 '-w', '--week_of', metavar='<date>', |
859 help='Show issues for week of the date') | 889 help='Show issues for week of the date') |
860 parser.add_option( | 890 parser.add_option( |
861 '-a', '--auth', | 891 '-a', '--auth', |
862 action='store_true', | 892 action='store_true', |
863 help='Ask to authenticate for instances with no auth cookie') | 893 help='Ask to authenticate for instances with no auth cookie') |
864 | 894 |
865 group = optparse.OptionGroup(parser, 'Activity Types', | 895 activity_types_group = optparse.OptionGroup(parser, 'Activity Types', |
866 'By default, all activity will be looked up and ' | 896 'By default, all activity will be looked up and ' |
867 'printed. If any of these are specified, only ' | 897 'printed. If any of these are specified, only ' |
868 'those specified will be searched.') | 898 'those specified will be searched.') |
869 group.add_option( | 899 activity_types_group.add_option( |
870 '-c', '--changes', | 900 '-c', '--changes', |
871 action='store_true', | 901 action='store_true', |
872 help='Show changes.') | 902 help='Show changes.') |
873 group.add_option( | 903 activity_types_group.add_option( |
874 '-i', '--issues', | 904 '-i', '--issues', |
875 action='store_true', | 905 action='store_true', |
876 help='Show issues.') | 906 help='Show issues.') |
877 group.add_option( | 907 activity_types_group.add_option( |
878 '-r', '--reviews', | 908 '-r', '--reviews', |
879 action='store_true', | 909 action='store_true', |
880 help='Show reviews.') | 910 help='Show reviews.') |
881 parser.add_option_group(group) | 911 parser.add_option_group(activity_types_group) |
| 912 |
| 913 output_format_group = optparse.OptionGroup(parser, 'Output Format', |
| 914 'By default, all activity will be printed in the ' |
| 915 'following format: {url} {title}. This can be ' |
| 916 'changed for either all activity types or ' |
| 917 'individually for each activity type. The format ' |
| 918 'is defined as documented for ' |
| 919 'string.format(...). The variables available for ' |
| 920 'all activity types are url, title and author. ' |
| 921 'Format options for specific activity types will ' |
| 922 'override the generic format.') |
| 923 output_format_group.add_option( |
| 924 '-f', '--output-format', metavar='<format>', |
| 925 default=u'{url} {title}', |
| 926 help='Specifies the format to use when printing all your activity.') |
| 927 output_format_group.add_option( |
| 928 '--output-format-changes', metavar='<format>', |
| 929 default=None, |
| 930 help='Specifies the format to use when printing changes.') |
| 931 output_format_group.add_option( |
| 932 '--output-format-issues', metavar='<format>', |
| 933 default=None, |
| 934 help='Specifies the format to use when printing issues. Has support ' |
| 935 'for the additional variable owner.') |
| 936 output_format_group.add_option( |
| 937 '--output-format-reviews', metavar='<format>', |
| 938 default=None, |
| 939 help='Specifies the format to use when printing reviews.') |
| 940 parser.add_option_group(output_format_group) |
882 | 941 |
883 # Remove description formatting | 942 # Remove description formatting |
884 parser.format_description = ( | 943 parser.format_description = ( |
885 lambda _: parser.description) # pylint: disable=E1101 | 944 lambda _: parser.description) # pylint: disable=E1101 |
886 | 945 |
887 options, args = parser.parse_args() | 946 options, args = parser.parse_args() |
888 options.local_user = os.environ.get('USER') | 947 options.local_user = os.environ.get('USER') |
889 if args: | 948 if args: |
890 parser.error('Args unsupported') | 949 parser.error('Args unsupported') |
891 if not options.user: | 950 if not options.user: |
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
943 print '\n\n\n' | 1002 print '\n\n\n' |
944 | 1003 |
945 my_activity.print_changes() | 1004 my_activity.print_changes() |
946 my_activity.print_reviews() | 1005 my_activity.print_reviews() |
947 my_activity.print_issues() | 1006 my_activity.print_issues() |
948 return 0 | 1007 return 0 |
949 | 1008 |
950 | 1009 |
951 if __name__ == '__main__': | 1010 if __name__ == '__main__': |
952 sys.exit(main()) | 1011 sys.exit(main()) |
OLD | NEW |