1# Copyright (C) 2009 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#      http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""A module for reading and parsing event-log-tags files."""
16
17import re
18import sys
19
20class Tag(object):
21  __slots__ = ["tagnum", "tagname", "description", "filename", "linenum"]
22
23  def __init__(self, tagnum, tagname, description, filename, linenum):
24    self.tagnum = tagnum
25    self.tagname = tagname
26    self.description = description
27    self.filename = filename
28    self.linenum = linenum
29
30
31class TagFile(object):
32  """Read an input event-log-tags file."""
33  def AddError(self, msg, linenum=None):
34    if linenum is None:
35      linenum = self.linenum
36    self.errors.append((self.filename, linenum, msg))
37
38  def AddWarning(self, msg, linenum=None):
39    if linenum is None:
40      linenum = self.linenum
41    self.warnings.append((self.filename, linenum, msg))
42
43  def __init__(self, filename, file_object=None):
44    """'filename' is the name of the file (included in any error
45    messages).  If 'file_object' is None, 'filename' will be opened
46    for reading."""
47    self.errors = []
48    self.warnings = []
49    self.tags = []
50    self.options = {}
51
52    self.filename = filename
53    self.linenum = 0
54
55    if file_object is None:
56      try:
57        file_object = open(filename, "rb")
58      except (IOError, OSError) as e:
59        self.AddError(str(e))
60        return
61
62    try:
63      for self.linenum, line in enumerate(file_object):
64        line = line.decode('utf-8')
65        self.linenum += 1
66        line = re.sub('#.*$', '', line) # strip trailing comments
67        line = line.strip()
68        if not line: continue
69        parts = re.split(r"\s+", line, 2)
70
71        if len(parts) < 2:
72          self.AddError("failed to parse \"%s\"" % (line,))
73          continue
74
75        if parts[0] == "option":
76          self.options[parts[1]] = parts[2:]
77          continue
78
79        if parts[0] == "?":
80          tag = None
81        else:
82          try:
83            tag = int(parts[0])
84          except ValueError:
85            self.AddError("\"%s\" isn't an integer tag or '?'" % (parts[0],))
86            continue
87
88        tagname = parts[1]
89        if len(parts) == 3:
90          description = parts[2]
91        else:
92          description = None
93
94        if description:
95          # EventLog.java checks that the description field is
96          # surrounded by parens, so we should too (to avoid a runtime
97          # crash from badly-formatted descriptions).
98          if not re.match(r"\(.*\)\s*$", description):
99            self.AddError("tag \"%s\" has unparseable description" % (tagname,))
100            continue
101
102        self.tags.append(Tag(tag, tagname, description,
103                             self.filename, self.linenum))
104    except (IOError, OSError) as e:
105      self.AddError(str(e))
106
107
108def BooleanFromString(s):
109  """Interpret 's' as a boolean and return its value.  Raise
110  ValueError if it's not something we can interpret as true or
111  false."""
112  s = s.lower()
113  if s in ("true", "t", "1", "on", "yes", "y"):
114    return True
115  if s in ("false", "f", "0", "off", "no", "n"):
116    return False
117  raise ValueError("'%s' not a valid boolean" % (s,))
118
119
120def WriteOutput(output_file, data):
121  """Write 'data' to the given output filename (which may be None to
122  indicate stdout).  Emit an error message and die on any failure.
123  'data' may be a string or a StringIO object."""
124  if not isinstance(data, str):
125    data = data.getvalue()
126  try:
127    if output_file is None:
128      out = sys.stdout
129      output_file = "<stdout>"
130    else:
131      out = open(output_file, "wb")
132    out.write(str.encode(data))
133    out.close()
134  except (IOError, OSError) as e:
135    print("failed to write %s: %s" % (output_file, e), file=sys.stderr)
136    sys.exit(1)
137