1// Copyright 2014 Google Inc. All rights reserved.
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
15package blueprint
16
17import (
18	"io"
19	"strings"
20	"unicode"
21)
22
23const (
24	indentWidth    = 4
25	maxIndentDepth = 2
26	lineWidth      = 80
27)
28
29var indentString = strings.Repeat(" ", indentWidth*maxIndentDepth)
30
31type StringWriterWriter interface {
32	io.StringWriter
33	io.Writer
34}
35
36type ninjaWriter struct {
37	writer StringWriterWriter
38
39	justDidBlankLine bool // true if the last operation was a BlankLine
40}
41
42func newNinjaWriter(writer StringWriterWriter) *ninjaWriter {
43	return &ninjaWriter{
44		writer: writer,
45	}
46}
47
48func (n *ninjaWriter) Comment(comment string) error {
49	n.justDidBlankLine = false
50
51	const lineHeaderLen = len("# ")
52	const maxLineLen = lineWidth - lineHeaderLen
53
54	var lineStart, lastSplitPoint int
55	for i, r := range comment {
56		if unicode.IsSpace(r) {
57			// We know we can safely split the line here.
58			lastSplitPoint = i + 1
59		}
60
61		var line string
62		var writeLine bool
63		switch {
64		case r == '\n':
65			// Output the line without trimming the left so as to allow comments
66			// to contain their own indentation.
67			line = strings.TrimRightFunc(comment[lineStart:i], unicode.IsSpace)
68			writeLine = true
69
70		case (i-lineStart > maxLineLen) && (lastSplitPoint > lineStart):
71			// The line has grown too long and is splittable.  Split it at the
72			// last split point.
73			line = strings.TrimSpace(comment[lineStart:lastSplitPoint])
74			writeLine = true
75		}
76
77		if writeLine {
78			line = strings.TrimSpace("# "+line) + "\n"
79			_, err := n.writer.WriteString(line)
80			if err != nil {
81				return err
82			}
83			lineStart = lastSplitPoint
84		}
85	}
86
87	if lineStart != len(comment) {
88		line := strings.TrimSpace(comment[lineStart:])
89		_, err := n.writer.WriteString("# ")
90		if err != nil {
91			return err
92		}
93		_, err = n.writer.WriteString(line)
94		if err != nil {
95			return err
96		}
97		_, err = n.writer.WriteString("\n")
98		if err != nil {
99			return err
100		}
101	}
102
103	return nil
104}
105
106func (n *ninjaWriter) Pool(name string) error {
107	n.justDidBlankLine = false
108	return n.writeStatement("pool", name)
109}
110
111func (n *ninjaWriter) Rule(name string) error {
112	n.justDidBlankLine = false
113	return n.writeStatement("rule", name)
114}
115
116func (n *ninjaWriter) Build(comment string, rule string, outputs, implicitOuts,
117	explicitDeps, implicitDeps, orderOnlyDeps, validations []*ninjaString,
118	outputStrings, implicitOutStrings, explicitDepStrings,
119	implicitDepStrings, orderOnlyDepStrings, validationStrings []string,
120	nameTracker *nameTracker) error {
121
122	n.justDidBlankLine = false
123
124	const lineWrapLen = len(" $")
125	const maxLineLen = lineWidth - lineWrapLen
126
127	wrapper := &ninjaWriterWithWrap{
128		ninjaWriter: n,
129		maxLineLen:  maxLineLen,
130	}
131
132	if comment != "" {
133		err := wrapper.Comment(comment)
134		if err != nil {
135			return err
136		}
137	}
138
139	wrapper.WriteString("build")
140
141	for _, output := range outputStrings {
142		wrapper.Space()
143		outputEscaper.WriteString(wrapper, output)
144	}
145	for _, output := range outputs {
146		wrapper.Space()
147		output.ValueWithEscaper(wrapper, nameTracker, outputEscaper)
148	}
149
150	if len(implicitOuts) > 0 || len(implicitOutStrings) > 0 {
151		wrapper.WriteStringWithSpace("|")
152
153		for _, out := range implicitOutStrings {
154			wrapper.Space()
155			outputEscaper.WriteString(wrapper, out)
156		}
157		for _, out := range implicitOuts {
158			wrapper.Space()
159			out.ValueWithEscaper(wrapper, nameTracker, outputEscaper)
160		}
161	}
162
163	wrapper.WriteString(":")
164
165	wrapper.WriteStringWithSpace(rule)
166
167	for _, dep := range explicitDepStrings {
168		wrapper.Space()
169		inputEscaper.WriteString(wrapper, dep)
170	}
171	for _, dep := range explicitDeps {
172		wrapper.Space()
173		dep.ValueWithEscaper(wrapper, nameTracker, inputEscaper)
174	}
175
176	if len(implicitDeps) > 0 || len(implicitDepStrings) > 0 {
177		wrapper.WriteStringWithSpace("|")
178
179		for _, dep := range implicitDepStrings {
180			wrapper.Space()
181			inputEscaper.WriteString(wrapper, dep)
182		}
183		for _, dep := range implicitDeps {
184			wrapper.Space()
185			dep.ValueWithEscaper(wrapper, nameTracker, inputEscaper)
186		}
187	}
188
189	if len(orderOnlyDeps) > 0 || len(orderOnlyDepStrings) > 0 {
190		wrapper.WriteStringWithSpace("||")
191
192		for _, dep := range orderOnlyDepStrings {
193			wrapper.Space()
194			inputEscaper.WriteString(wrapper, dep)
195		}
196		for _, dep := range orderOnlyDeps {
197			wrapper.Space()
198			dep.ValueWithEscaper(wrapper, nameTracker, inputEscaper)
199		}
200	}
201
202	if len(validations) > 0 || len(validationStrings) > 0 {
203		wrapper.WriteStringWithSpace("|@")
204
205		for _, dep := range validationStrings {
206			wrapper.Space()
207			inputEscaper.WriteString(wrapper, dep)
208		}
209		for _, dep := range validations {
210			wrapper.Space()
211			dep.ValueWithEscaper(wrapper, nameTracker, inputEscaper)
212		}
213	}
214
215	return wrapper.Flush()
216}
217
218func (n *ninjaWriter) Assign(name, value string) error {
219	n.justDidBlankLine = false
220	_, err := n.writer.WriteString(name)
221	if err != nil {
222		return err
223	}
224	_, err = n.writer.WriteString(" = ")
225	if err != nil {
226		return err
227	}
228	_, err = n.writer.WriteString(value)
229	if err != nil {
230		return err
231	}
232	_, err = n.writer.WriteString("\n")
233	if err != nil {
234		return err
235	}
236	return nil
237}
238
239func (n *ninjaWriter) ScopedAssign(name, value string) error {
240	n.justDidBlankLine = false
241	_, err := n.writer.WriteString(indentString[:indentWidth])
242	if err != nil {
243		return err
244	}
245	_, err = n.writer.WriteString(name)
246	if err != nil {
247		return err
248	}
249	_, err = n.writer.WriteString(" = ")
250	if err != nil {
251		return err
252	}
253	_, err = n.writer.WriteString(value)
254	if err != nil {
255		return err
256	}
257	_, err = n.writer.WriteString("\n")
258	if err != nil {
259		return err
260	}
261	return nil
262}
263
264func (n *ninjaWriter) Default(nameTracker *nameTracker, targets []*ninjaString, targetStrings []string) error {
265	n.justDidBlankLine = false
266
267	const lineWrapLen = len(" $")
268	const maxLineLen = lineWidth - lineWrapLen
269
270	wrapper := &ninjaWriterWithWrap{
271		ninjaWriter: n,
272		maxLineLen:  maxLineLen,
273	}
274
275	wrapper.WriteString("default")
276
277	for _, target := range targetStrings {
278		wrapper.Space()
279		outputEscaper.WriteString(wrapper, target)
280	}
281	for _, target := range targets {
282		wrapper.Space()
283		target.ValueWithEscaper(wrapper, nameTracker, outputEscaper)
284	}
285
286	return wrapper.Flush()
287}
288
289func (n *ninjaWriter) Subninja(file string) error {
290	n.justDidBlankLine = false
291	return n.writeStatement("subninja", file)
292}
293
294func (n *ninjaWriter) BlankLine() (err error) {
295	// We don't output multiple blank lines in a row.
296	if !n.justDidBlankLine {
297		n.justDidBlankLine = true
298		_, err = n.writer.WriteString("\n")
299	}
300	return err
301}
302
303func (n *ninjaWriter) writeStatement(directive, name string) error {
304	_, err := n.writer.WriteString(directive + " ")
305	if err != nil {
306		return err
307	}
308	_, err = n.writer.WriteString(name)
309	if err != nil {
310		return err
311	}
312	_, err = n.writer.WriteString("\n")
313	if err != nil {
314		return err
315	}
316	return nil
317}
318
319// ninjaWriterWithWrap is an io.StringWriter that writes through to a ninjaWriter, but supports
320// user-readable line wrapping on boundaries when ninjaWriterWithWrap.Space is called.
321// It collects incoming calls to WriteString until either the line length is exceeded, in which case
322// it inserts a wrap before the pending strings and then writes them, or the next call to Space, in
323// which case it writes out the pending strings.
324//
325// WriteString never returns an error, all errors are held until Flush is called.  Once an error has
326// occurred all writes become noops.
327type ninjaWriterWithWrap struct {
328	*ninjaWriter
329	// pending lists the strings that have been written since the last call to Space.
330	pending []string
331
332	// pendingLen accumulates the lengths of the strings in pending.
333	pendingLen int
334
335	// lineLen accumulates the number of bytes on the current line.
336	lineLen int
337
338	// maxLineLen is the length of the line before wrapping.
339	maxLineLen int
340
341	// space is true if the strings in pending should be preceded by a space.
342	space bool
343
344	// err holds any error that has occurred to return in Flush.
345	err error
346}
347
348// WriteString writes the string to buffer, wrapping on a previous Space call if necessary.
349// It never returns an error, all errors are held until Flush is called.
350func (n *ninjaWriterWithWrap) WriteString(s string) (written int, noError error) {
351	// Always return the full length of the string and a nil error.
352	// ninjaWriterWithWrap doesn't return errors to the caller, it saves them until Flush()
353	written = len(s)
354
355	if n.err != nil {
356		return
357	}
358
359	const spaceLen = 1
360	if !n.space {
361		// No space is pending, so a line wrap can't be inserted before this, so just write
362		// the string.
363		n.lineLen += len(s)
364		_, n.err = n.writer.WriteString(s)
365	} else if n.lineLen+len(s)+spaceLen > n.maxLineLen {
366		// A space is pending, and the pending strings plus the current string would exceed the
367		// maximum line length.  Wrap and indent before the pending space and strings, then write
368		// the pending and current strings.
369		_, n.err = n.writer.WriteString(" $\n")
370		if n.err != nil {
371			return
372		}
373		_, n.err = n.writer.WriteString(indentString[:indentWidth*2])
374		if n.err != nil {
375			return
376		}
377		n.lineLen = indentWidth*2 + n.pendingLen
378		s = strings.TrimLeftFunc(s, unicode.IsSpace)
379		n.pending = append(n.pending, s)
380		n.lineLen += len(s)
381		n.writePending()
382
383		n.space = false
384	} else {
385		// A space is pending but the current string would not reach the maximum line length,
386		// add it to the pending list.
387		n.pending = append(n.pending, s)
388		n.pendingLen += len(s)
389		n.lineLen += len(s)
390	}
391
392	return
393}
394
395func (n *ninjaWriterWithWrap) Write(p []byte) (written int, noError error) {
396	// Write is rarely used, implement it via WriteString.
397	return n.WriteString(string(p))
398}
399
400// Space inserts a space that is also a possible wrapping point into the string.
401func (n *ninjaWriterWithWrap) Space() {
402	if n.err != nil {
403		return
404	}
405	if n.space {
406		// A space was already pending, and the space plus any strings written after the space did
407		// not reach the maxmimum line length, so write out the old space and pending strings.
408		_, n.err = n.writer.WriteString(" ")
409		n.lineLen++
410		n.writePending()
411	}
412	n.space = true
413}
414
415// writePending writes out all the strings stored in pending and resets it.
416func (n *ninjaWriterWithWrap) writePending() {
417	if n.err != nil {
418		return
419	}
420	for _, pending := range n.pending {
421		_, n.err = n.writer.WriteString(pending)
422		if n.err != nil {
423			return
424		}
425	}
426	// Reset the length of pending back to 0 without reducing its capacity to avoid reallocating
427	// the backing array.
428	n.pending = n.pending[:0]
429	n.pendingLen = 0
430}
431
432// WriteStringWithSpace is a helper that calls Space and WriteString.
433func (n *ninjaWriterWithWrap) WriteStringWithSpace(s string) {
434	n.Space()
435	_, _ = n.WriteString(s)
436}
437
438// Flush writes out any pending space or strings and then a newline.  It also returns any errors
439// that have previously occurred.
440func (n *ninjaWriterWithWrap) Flush() error {
441	if n.space {
442		_, n.err = n.writer.WriteString(" ")
443	}
444	n.writePending()
445	if n.err != nil {
446		return n.err
447	}
448	_, err := n.writer.WriteString("\n")
449	return err
450}
451