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.
4
5import optparse
6import re
7
8from telemetry.internal.util import command_line
9
10
11class _StoryMatcher(object):
12  def __init__(self, pattern):
13    self._regex = None
14    self.has_compile_error = False
15    if pattern:
16      try:
17        self._regex = re.compile(pattern)
18      except re.error:
19        self.has_compile_error = True
20
21  def __nonzero__(self):
22    return self._regex is not None
23
24  def HasMatch(self, story):
25    return self and bool(
26        self._regex.search(story.display_name) or
27        (story.name and self._regex.search(story.name)))
28
29
30class _StoryLabelMatcher(object):
31  def __init__(self, labels_str):
32    self._labels = labels_str.split(',') if labels_str else None
33
34  def __nonzero__(self):
35    return self._labels is not None
36
37  def HasLabelIn(self, story):
38    return self and bool(story.labels.intersection(self._labels))
39
40
41class StoryFilter(command_line.ArgumentHandlerMixIn):
42  """Filters stories in the story set based on command-line flags."""
43
44  @classmethod
45  def AddCommandLineArgs(cls, parser):
46    group = optparse.OptionGroup(parser, 'User story filtering options')
47    group.add_option('--story-filter',
48        help='Use only stories whose names match the given filter regexp.')
49    group.add_option('--story-filter-exclude',
50        help='Exclude stories whose names match the given filter regexp.')
51    group.add_option('--story-label-filter',
52        help='Use only stories that have any of these labels')
53    group.add_option('--story-label-filter-exclude',
54        help='Exclude stories that have any of these labels')
55    parser.add_option_group(group)
56
57  @classmethod
58  def ProcessCommandLineArgs(cls, parser, args):
59    cls._include_regex = _StoryMatcher(args.story_filter)
60    cls._exclude_regex = _StoryMatcher(args.story_filter_exclude)
61    cls._include_labels = _StoryLabelMatcher(args.story_label_filter)
62    cls._exclude_labels = _StoryLabelMatcher(args.story_label_filter_exclude)
63
64    if cls._include_regex.has_compile_error:
65      raise parser.error('--story-filter: Invalid regex.')
66    if cls._exclude_regex.has_compile_error:
67      raise parser.error('--story-filter-exclude: Invalid regex.')
68
69  @classmethod
70  def IsSelected(cls, story):
71    # Exclude filters take priority.
72    if cls._exclude_labels.HasLabelIn(story):
73      return False
74    if cls._exclude_regex.HasMatch(story):
75      return False
76
77    if cls._include_labels and not cls._include_labels.HasLabelIn(story):
78      return False
79    if cls._include_regex and not cls._include_regex.HasMatch(story):
80      return False
81    return True
82