aboutsummaryrefslogtreecommitdiff
path: root/bestflags/flags.py
blob: b316421e9c0dbe3ca48e14f5edd7a576450381a4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Manage bundles of flags used for the optimizing of ChromeOS.

Part of the Chrome build flags optimization.

The content of this module is adapted from the Trakhelp JVM project. This module
contains the basic Class Flag and the Class FlagSet. The core abstractions are:

The class Flag, which takes a domain specific language describing how to fill
the flags with values.

The class FlagSet, which contains a number of flags and can create new FlagSets
by mixing with other FlagSets.

The Flag DSL works by replacing value ranges in [x-y] with numbers in the range
x through y.

Examples:
  "foo[0-9]bar" will expand to e.g. "foo5bar".
"""

__author__ = 'yuhenglong@google.com (Yuheng Long)'

import random
import re

#
# This matches a [...] group in the internal representation for a flag
# specification, and is used in "filling out" flags - placing values inside
# the flag_spec.  The internal flag_spec format is like "foo[0]", with
# values filled out like 5; this would be transformed by
# FormattedForUse() into "foo5".
_FLAG_FILLOUT_VALUE_RE = re.compile(r'\[([^\]]*)\]')

# This matches a numeric flag flag=[start-end].
rx = re.compile(r'\[(?P<start>\d+)-(?P<end>\d+)\]')


# Search the numeric flag pattern.
def Search(spec):
  return rx.search(spec)


class NoSuchFileError(Exception):
  """Define an Exception class for user providing invalid input file."""
  pass


def ReadConf(file_name):
  """Parse the configuration file.

  The configuration contains one flag specification in each line.

  Args:
    file_name: The name of the configuration file.

  Returns:
    A list of specs in the configuration file.

  Raises:
    NoSuchFileError: The caller should provide a valid configuration file.
  """

  with open(file_name, 'r') as input_file:
    lines = input_file.readlines()

    return sorted([line.strip() for line in lines if line.strip()])

  raise NoSuchFileError()


class Flag(object):
  """A class representing a particular command line flag argument.

  The Flag consists of two parts: The spec and the value.
  The spec is a definition of the following form: a string with escaped
  sequences of the form [<start>-<end>] where start and end is an positive
  integer for a fillable value.

  An example of a spec is "foo[0-9]".
  There are two kinds of flags, boolean flag and numeric flags. Boolean flags
  can either be turned on or off, which numeric flags can have different
  positive integer values. For example, -finline-limit=[1-1000] is a numeric
  flag and -ftree-vectorize is a boolean flag.

  A (boolean/numeric) flag is not turned on if it is not selected in the
  FlagSet.
  """

  def __init__(self, spec, value=-1):
    self._spec = spec

    # If the value is not specified, generate a random value to use.
    if value == -1:
      # If creating a boolean flag, the value will be 0.
      value = 0

      # Parse the spec's expression for the flag value's numeric range.
      numeric_flag_match = Search(spec)

      # If this is a numeric flag, a value is chosen within start and end, start
      # inclusive and end exclusive.
      if numeric_flag_match:
        start = int(numeric_flag_match.group('start'))
        end = int(numeric_flag_match.group('end'))

        assert start < end
        value = random.randint(start, end)

    self._value = value

  def __eq__(self, other):
    if isinstance(other, Flag):
      return self._spec == other.GetSpec() and self._value == other.GetValue()
    return False

  def __hash__(self):
    return hash(self._spec) + self._value

  def GetValue(self):
    """Get the value for this flag.

    Returns:
     The value.
    """

    return self._value

  def GetSpec(self):
    """Get the spec for this flag.

    Returns:
     The spec.
    """

    return self._spec

  def FormattedForUse(self):
    """Calculate the combination of flag_spec and values.

    For e.g. the flag_spec 'foo[0-9]' and the value equals to 5, this will
    return 'foo5'. The filled out version of the flag is the text string you use
    when you actually want to pass the flag to some binary.

    Returns:
      A string that represent the filled out flag, e.g. the flag with the
      FlagSpec '-X[0-9]Y' and value equals to 5 would return '-X5Y'.
    """

    return _FLAG_FILLOUT_VALUE_RE.sub(str(self._value), self._spec)


class FlagSet(object):
  """A dictionary of Flag objects.

  The flags dictionary stores the spec and flag pair.
  """

  def __init__(self, flag_array):
    # Store the flags as a dictionary mapping of spec -> flag object
    self._flags = dict([(flag.GetSpec(), flag) for flag in flag_array])

  def __eq__(self, other):
    return isinstance(other, FlagSet) and self._flags == other.GetFlags()

  def __hash__(self):
    return sum([hash(flag) for flag in self._flags.values()])

  def __getitem__(self, flag_spec):
    """Get flag with a particular flag_spec.

    Args:
      flag_spec: The flag_spec to find.

    Returns:
      A flag.
    """

    return self._flags[flag_spec]

  def __contains__(self, flag_spec):
    return self._flags.has_key(flag_spec)

  def GetFlags(self):
    return self._flags

  def FormattedForUse(self):
    """Format this for use in an application.

    Returns:
      A list of flags, sorted alphabetically and filled in with the values
      for each flag.
    """

    return sorted([f.FormattedForUse() for f in self._flags.values()])