1# This file comes from
2#   https://github.com/martine/ninja/blob/master/misc/ninja_syntax.py
3# Do not edit!  Edit the upstream one instead.
4
5"""Python module for generating .ninja files.
6
7Note that this is emphatically not a required piece of Ninja; it's
8just a helpful utility for build-file-generation systems that already
9use Python.
10"""
11
12import textwrap
13import re
14
15def escape_path(word):
16    return word.replace('$ ','$$ ').replace(' ','$ ').replace(':', '$:')
17
18class Writer(object):
19    def __init__(self, output, width=78):
20        self.output = output
21        self.width = width
22
23    def newline(self):
24        self.output.write('\n')
25
26    def comment(self, text):
27        for line in textwrap.wrap(text, self.width - 2):
28            self.output.write('# ' + line + '\n')
29
30    def variable(self, key, value, indent=0):
31        if value is None:
32            return
33        if isinstance(value, list):
34            value = ' '.join(filter(None, value))  # Filter out empty strings.
35        self._line('%s = %s' % (key, value), indent)
36
37    def pool(self, name, depth):
38        self._line('pool %s' % name)
39        self.variable('depth', depth, indent=1)
40
41    def rule(self, name, command, description=None, depfile=None,
42             generator=False, pool=None, restat=False, rspfile=None,
43             rspfile_content=None, deps=None):
44        self._line('rule %s' % name)
45        self.variable('command', command, indent=1)
46        if description:
47            self.variable('description', description, indent=1)
48        if depfile:
49            self.variable('depfile', depfile, indent=1)
50        if generator:
51            self.variable('generator', '1', indent=1)
52        if pool:
53            self.variable('pool', pool, indent=1)
54        if restat:
55            self.variable('restat', '1', indent=1)
56        if rspfile:
57            self.variable('rspfile', rspfile, indent=1)
58        if rspfile_content:
59            self.variable('rspfile_content', rspfile_content, indent=1)
60        if deps:
61            self.variable('deps', deps, indent=1)
62
63    def build(self, outputs, rule, inputs=None, implicit=None, order_only=None,
64              variables=None):
65        outputs = self._as_list(outputs)
66        all_inputs = self._as_list(inputs)[:]
67        out_outputs = list(map(escape_path, outputs))
68        all_inputs = list(map(escape_path, all_inputs))
69
70        if implicit:
71            implicit = map(escape_path, self._as_list(implicit))
72            all_inputs.append('|')
73            all_inputs.extend(implicit)
74        if order_only:
75            order_only = map(escape_path, self._as_list(order_only))
76            all_inputs.append('||')
77            all_inputs.extend(order_only)
78
79        self._line('build %s: %s' % (' '.join(out_outputs),
80                                        ' '.join([rule] + all_inputs)))
81
82        if variables:
83            if isinstance(variables, dict):
84                iterator = iter(variables.items())
85            else:
86                iterator = iter(variables)
87
88            for key, val in iterator:
89                self.variable(key, val, indent=1)
90
91        return outputs
92
93    def include(self, path):
94        self._line('include %s' % path)
95
96    def subninja(self, path):
97        self._line('subninja %s' % path)
98
99    def default(self, paths):
100        self._line('default %s' % ' '.join(self._as_list(paths)))
101
102    def _count_dollars_before_index(self, s, i):
103      """Returns the number of '$' characters right in front of s[i]."""
104      dollar_count = 0
105      dollar_index = i - 1
106      while dollar_index > 0 and s[dollar_index] == '$':
107        dollar_count += 1
108        dollar_index -= 1
109      return dollar_count
110
111    def _line(self, text, indent=0):
112        """Write 'text' word-wrapped at self.width characters."""
113        leading_space = '  ' * indent
114        while len(leading_space) + len(text) > self.width:
115            # The text is too wide; wrap if possible.
116
117            # Find the rightmost space that would obey our width constraint and
118            # that's not an escaped space.
119            available_space = self.width - len(leading_space) - len(' $')
120            space = available_space
121            while True:
122              space = text.rfind(' ', 0, space)
123              if space < 0 or \
124                 self._count_dollars_before_index(text, space) % 2 == 0:
125                break
126
127            if space < 0:
128                # No such space; just use the first unescaped space we can find.
129                space = available_space - 1
130                while True:
131                  space = text.find(' ', space + 1)
132                  if space < 0 or \
133                     self._count_dollars_before_index(text, space) % 2 == 0:
134                    break
135            if space < 0:
136                # Give up on breaking.
137                break
138
139            self.output.write(leading_space + text[0:space] + ' $\n')
140            text = text[space+1:]
141
142            # Subsequent lines are continuations, so indent them.
143            leading_space = '  ' * (indent+2)
144
145        self.output.write(leading_space + text + '\n')
146
147    def _as_list(self, input):
148        if input is None:
149            return []
150        if isinstance(input, list):
151            return input
152
153        # map is not a class in Python 2
154        try:
155            if isinstance(input, map):
156                return list(input)
157        except TypeError:
158            pass
159
160        return [input]
161
162
163def escape(string):
164    """Escape a string such that it can be embedded into a Ninja file without
165    further interpretation."""
166    assert '\n' not in string, 'Ninja syntax does not allow newlines'
167    # We only have one special metacharacter: '$'.
168    return string.replace('$', '$$')
169