UNPKG

6.21 kBJavaScriptView Raw
1"use strict";
2// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
3// See LICENSE in the project root for license information.
4Object.defineProperty(exports, "__esModule", { value: true });
5exports.IndentedWriter = void 0;
6const node_core_library_1 = require("@rushstack/node-core-library");
7/**
8 * A utility for writing indented text.
9 *
10 * @remarks
11 *
12 * Note that the indentation is inserted at the last possible opportunity.
13 * For example, this code...
14 *
15 * ```ts
16 * writer.write('begin\n');
17 * writer.increaseIndent();
18 * writer.write('one\ntwo\n');
19 * writer.decreaseIndent();
20 * writer.increaseIndent();
21 * writer.decreaseIndent();
22 * writer.write('end');
23 * ```
24 *
25 * ...would produce this output:
26 *
27 * ```
28 * begin
29 * one
30 * two
31 * end
32 * ```
33 */
34class IndentedWriter {
35 constructor(builder) {
36 /**
37 * The text characters used to create one level of indentation.
38 * Two spaces by default.
39 */
40 this.defaultIndentPrefix = ' ';
41 this._builder = builder === undefined ? new node_core_library_1.StringBuilder() : builder;
42 this._latestChunk = undefined;
43 this._previousChunk = undefined;
44 this._atStartOfLine = true;
45 this._indentStack = [];
46 this._indentText = '';
47 }
48 /**
49 * Retrieves the output that was built so far.
50 */
51 getText() {
52 return this._builder.toString();
53 }
54 toString() {
55 return this.getText();
56 }
57 /**
58 * Increases the indentation. Normally the indentation is two spaces,
59 * however an arbitrary prefix can optional be specified. (For example,
60 * the prefix could be "// " to indent and comment simultaneously.)
61 * Each call to IndentedWriter.increaseIndent() must be followed by a
62 * corresponding call to IndentedWriter.decreaseIndent().
63 */
64 increaseIndent(indentPrefix) {
65 this._indentStack.push(indentPrefix !== undefined ? indentPrefix : this.defaultIndentPrefix);
66 this._updateIndentText();
67 }
68 /**
69 * Decreases the indentation, reverting the effect of the corresponding call
70 * to IndentedWriter.increaseIndent().
71 */
72 decreaseIndent() {
73 this._indentStack.pop();
74 this._updateIndentText();
75 }
76 /**
77 * A shorthand for ensuring that increaseIndent()/decreaseIndent() occur
78 * in pairs.
79 */
80 indentScope(scope, indentPrefix) {
81 this.increaseIndent(indentPrefix);
82 scope();
83 this.decreaseIndent();
84 }
85 /**
86 * Adds a newline if the file pointer is not already at the start of the line (or start of the stream).
87 */
88 ensureNewLine() {
89 const lastCharacter = this.peekLastCharacter();
90 if (lastCharacter !== '\n' && lastCharacter !== '') {
91 this._writeNewLine();
92 }
93 }
94 /**
95 * Adds up to two newlines to ensure that there is a blank line above the current line.
96 */
97 ensureSkippedLine() {
98 if (this.peekLastCharacter() !== '\n') {
99 this._writeNewLine();
100 }
101 const secondLastCharacter = this.peekSecondLastCharacter();
102 if (secondLastCharacter !== '\n' && secondLastCharacter !== '') {
103 this._writeNewLine();
104 }
105 }
106 /**
107 * Returns the last character that was written, or an empty string if no characters have been written yet.
108 */
109 peekLastCharacter() {
110 if (this._latestChunk !== undefined) {
111 return this._latestChunk.substr(-1, 1);
112 }
113 return '';
114 }
115 /**
116 * Returns the second to last character that was written, or an empty string if less than one characters
117 * have been written yet.
118 */
119 peekSecondLastCharacter() {
120 if (this._latestChunk !== undefined) {
121 if (this._latestChunk.length > 1) {
122 return this._latestChunk.substr(-2, 1);
123 }
124 if (this._previousChunk !== undefined) {
125 return this._previousChunk.substr(-1, 1);
126 }
127 }
128 return '';
129 }
130 /**
131 * Writes some text to the internal string buffer, applying indentation according
132 * to the current indentation level. If the string contains multiple newlines,
133 * each line will be indented separately.
134 */
135 write(message) {
136 if (message.length === 0) {
137 return;
138 }
139 // If there are no newline characters, then append the string verbatim
140 if (!/[\r\n]/.test(message)) {
141 this._writeLinePart(message);
142 return;
143 }
144 // Otherwise split the lines and write each one individually
145 let first = true;
146 for (const linePart of message.split('\n')) {
147 if (!first) {
148 this._writeNewLine();
149 }
150 else {
151 first = false;
152 }
153 if (linePart) {
154 this._writeLinePart(linePart.replace(/[\r]/g, ''));
155 }
156 }
157 }
158 /**
159 * A shorthand for writing an optional message, followed by a newline.
160 * Indentation is applied following the semantics of IndentedWriter.write().
161 */
162 writeLine(message = '') {
163 if (message.length > 0) {
164 this.write(message);
165 }
166 this._writeNewLine();
167 }
168 /**
169 * Writes a string that does not contain any newline characters.
170 */
171 _writeLinePart(message) {
172 if (message.length > 0) {
173 if (this._atStartOfLine && this._indentText.length > 0) {
174 this._write(this._indentText);
175 }
176 this._write(message);
177 this._atStartOfLine = false;
178 }
179 }
180 _writeNewLine() {
181 if (this._atStartOfLine && this._indentText.length > 0) {
182 this._write(this._indentText);
183 }
184 this._write('\n');
185 this._atStartOfLine = true;
186 }
187 _write(s) {
188 this._previousChunk = this._latestChunk;
189 this._latestChunk = s;
190 this._builder.append(s);
191 }
192 _updateIndentText() {
193 this._indentText = this._indentStack.join('');
194 }
195}
196exports.IndentedWriter = IndentedWriter;
197//# sourceMappingURL=IndentedWriter.js.map
\No newline at end of file