From b363f6d8c456e24efb47b1b8d3c48f93c53ae5a4 Mon Sep 17 00:00:00 2001 From: Jeremy Apthorp Date: Mon, 21 Nov 2011 14:07:56 +1100 Subject: [PATCH 1/3] Allow '$ ' to escape spaces in identifiers. --- src/eval_env.cc | 3 +++ src/parsers.cc | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/eval_env.cc b/src/eval_env.cc index a5127d30..fa5e35bf 100644 --- a/src/eval_env.cc +++ b/src/eval_env.cc @@ -58,6 +58,9 @@ bool EvalString::Parse(const string& input, string* err, size_t* err_index) { } else if (start < input.size() && input[start] == '$') { parsed_.push_back(make_pair("$", RAW)); end = start + 1; + } else if (start < input.size() && input[start] == ' ') { + parsed_.push_back(make_pair(" ", RAW)); + end = start + 1; } else { for (end = start; end < input.size(); ++end) { char c = input[end]; diff --git a/src/parsers.cc b/src/parsers.cc index 4920496c..97fa4a6f 100644 --- a/src/parsers.cc +++ b/src/parsers.cc @@ -232,6 +232,9 @@ Token::Type Tokenizer::PeekToken() { if (IsIdentChar(*cur_)) { while (cur_ < end_ && IsIdentChar(*cur_)) { + if (*cur_ == '$' && cur_ + 1 < end_ && cur_[1] == ' ') { + ++cur_; + } ++cur_; } token_.end_ = cur_; From 4d142a410891144f11259b7e8432c0a81c88215c Mon Sep 17 00:00:00 2001 From: Jeremy Apthorp Date: Mon, 28 Nov 2011 14:25:16 +1100 Subject: [PATCH 2/3] Add tests for escaping spaces with '$ '. --- src/parsers_test.cc | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/parsers_test.cc b/src/parsers_test.cc index a99b5109..3cd85cb7 100644 --- a/src/parsers_test.cc +++ b/src/parsers_test.cc @@ -149,6 +149,19 @@ TEST_F(ParserTest, Dollars) { EXPECT_EQ("$dollarbar$baz$blah", state.edges_[0]->EvaluateCommand()); } +TEST_F(ParserTest, EscapeSpaces) { + ASSERT_NO_FATAL_FAILURE(AssertParse( +"rule has$ spaces\n" +" command = something\n" +"build foo$ bar: has$ spaces $$one two$$$ three\n" +)); + EXPECT_TRUE(state.LookupNode("foo bar")); + EXPECT_EQ(state.edges_[0]->outputs_[0]->file_->path_, "foo bar"); + EXPECT_EQ(state.edges_[0]->inputs_[0]->file_->path_, "$one"); + EXPECT_EQ(state.edges_[0]->inputs_[1]->file_->path_, "two$ three"); + EXPECT_EQ(state.edges_[0]->EvaluateCommand(), "something"); +} + TEST_F(ParserTest, CanonicalizeFile) { ASSERT_NO_FATAL_FAILURE(AssertParse( "rule cat\n" From 9be1597a218e61fd6f7f5b3c3a52ebd2e51cac6b Mon Sep 17 00:00:00 2001 From: Jeremy Apthorp Date: Mon, 28 Nov 2011 14:27:02 +1100 Subject: [PATCH 3/3] Update ninja_syntax.py and tests to reflect '$ ' syntax. --- misc/ninja_syntax.py | 57 +++++++++++++++++++++++++++++++++----------- misc/ninja_test.py | 18 ++++++++++++++ 2 files changed, 61 insertions(+), 14 deletions(-) diff --git a/misc/ninja_syntax.py b/misc/ninja_syntax.py index 6e8a87c7..06fe3cbc 100644 --- a/misc/ninja_syntax.py +++ b/misc/ninja_syntax.py @@ -8,6 +8,10 @@ use Python. """ import textwrap +import re + +def escape_spaces(word): + return word.replace('$ ','$$ ').replace(' ','$ ') class Writer(object): def __init__(self, output, width=78): @@ -43,15 +47,19 @@ class Writer(object): variables=None): outputs = self._as_list(outputs) all_inputs = self._as_list(inputs)[:] + out_outputs = map(escape_spaces, outputs) + all_inputs = map(escape_spaces, all_inputs) if implicit: + implicit = map(escape_spaces, self._as_list(implicit)) all_inputs.append('|') - all_inputs.extend(self._as_list(implicit)) + all_inputs.extend(implicit) if order_only: + order_only = map(escape_spaces, self._as_list(order_only)) all_inputs.append('||') - all_inputs.extend(self._as_list(order_only)) + all_inputs.extend(order_only) - self._line('build %s: %s %s' % (' '.join(outputs), + self._line('build %s: %s %s' % (' '.join(out_outputs), rule, ' '.join(all_inputs))) @@ -76,22 +84,43 @@ class Writer(object): while len(text) > self.width: # The text is too wide; wrap if possible. - # Find the rightmost space that would obey our width constraint. - available_space = self.width - len(leading_space) - len(' $') - space = text.rfind(' ', 0, available_space) - if space < 0: - # No such space; just use the first space we can find. - space = text.find(' ', available_space) - if space < 0: - # Give up on breaking. - break + self.output.write(leading_space) - self.output.write(leading_space + text[0:space] + ' $\n') - text = text[space+1:] + available_space = self.width - len(leading_space) - len(' $') + + # Write as much as we can into this line. + done = False + written_stuff = False + while available_space > 0: + space = re.search('((\$\$)+([^$]|^)|[^$]|^) ', text) + if space: + space_idx = space.start() + 1 + else: + # No spaces left. + done = True + break + + if space_idx > available_space: + # We're out of space. + if written_stuff: + # See if we can fit it on the next line. + break + # If we haven't written anything yet on this line, don't + # try to wrap. + self.output.write(text[0:space_idx] + ' ') + written_stuff = True + text = text[space_idx+1:] + available_space -= space_idx+1 + + self.output.write('$\n') # Subsequent lines are continuations, so indent them. leading_space = ' ' * (indent+2) + if done: + # No more spaces, so bail. + break + self.output.write(leading_space + text + '\n') def _as_list(self, input): diff --git a/misc/ninja_test.py b/misc/ninja_test.py index 762fe163..481912e3 100755 --- a/misc/ninja_test.py +++ b/misc/ninja_test.py @@ -20,6 +20,7 @@ from StringIO import StringIO import ninja_syntax LONGWORD = 'a' * 10 +LONGWORDWITHSPACES = 'a'*5 + '$ ' + 'a'*5 INDENT = ' ' class TestLineWordWrap(unittest.TestCase): @@ -48,5 +49,22 @@ class TestLineWordWrap(unittest.TestCase): ' ' + INDENT + 'y']) + '\n', self.out.getvalue()) + def test_escaped_spaces(self): + self.n._line(' '.join(['x', LONGWORDWITHSPACES, 'y'])) + self.assertEqual(' $\n'.join(['x', + INDENT + LONGWORDWITHSPACES, + INDENT + 'y']) + '\n', + self.out.getvalue()) + + def test_fit_many_words(self): + self.n = ninja_syntax.Writer(self.out, width=78) + self.n._line('command = cd ../../chrome; python ../tools/grit/grit/format/repack.py ../out/Debug/obj/chrome/chrome_dll.gen/repack/theme_resources_large.pak ../out/Debug/gen/chrome/theme_resources_large.pak', 1) + self.assertEqual('''\ + command = cd ../../chrome; python ../tools/grit/grit/format/repack.py $ + ../out/Debug/obj/chrome/chrome_dll.gen/repack/theme_resources_large.pak $ + ../out/Debug/gen/chrome/theme_resources_large.pak +''', + self.out.getvalue()) + if __name__ == '__main__': unittest.main()