1# Copyright (C) 2020 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
16"""This file reads entries from a Ninja rsp file."""
17
18class NinjaRspFileReader:
19  """
20  Reads entries from a Ninja rsp file.  Ninja escapes any entries in the file that contain a
21  non-standard character by surrounding the whole entry with single quotes, and then replacing
22  any single quotes in the entry with the escape sequence '\''.
23  """
24
25  def __init__(self, filename):
26    self.f = open(filename, 'r')
27    self.r = self.character_reader(self.f)
28
29  def __iter__(self):
30    return self
31
32  def character_reader(self, f):
33    """Turns a file into a generator that returns one character at a time."""
34    while True:
35      c = f.read(1)
36      if c:
37        yield c
38      else:
39        return
40
41  def __next__(self):
42    entry = self.read_entry()
43    if entry:
44      return entry
45    else:
46      raise StopIteration
47
48  def read_entry(self):
49    c = next(self.r, "")
50    if not c:
51      return ""
52    elif c == "'":
53      return self.read_quoted_entry()
54    else:
55      entry = c
56      for c in self.r:
57        if c == " " or c == "\n":
58          break
59        entry += c
60      return entry
61
62  def read_quoted_entry(self):
63    entry = ""
64    for c in self.r:
65      if c == "'":
66        # Either the end of the quoted entry, or the beginning of an escape sequence, read the next
67        # character to find out.
68        c = next(self.r)
69        if not c or c == " " or c == "\n":
70          # End of the item
71          return entry
72        elif c == "\\":
73          # Escape sequence, expect a '
74          c = next(self.r)
75          if c != "'":
76            # Malformed escape sequence
77            raise "malformed escape sequence %s'\\%s" % (entry, c)
78          entry += "'"
79        else:
80          raise "malformed escape sequence %s'%s" % (entry, c)
81      else:
82        entry += c
83    raise "unterminated quoted entry %s" % entry
84