OLD | NEW |
---|---|
1 // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 import 'dart:io'; | 5 import 'dart:io'; |
6 | 6 |
7 import 'package:path/path.dart' as p; | 7 import 'package:path/path.dart' as p; |
8 | 8 |
9 import 'environment.dart'; | 9 import 'environment.dart'; |
10 import 'expectation.dart'; | 10 import 'expectation.dart'; |
(...skipping 27 matching lines...) Expand all Loading... | |
38 /// | 38 /// |
39 /// Each entry is a glob-like file path followed by a colon and then a | 39 /// Each entry is a glob-like file path followed by a colon and then a |
40 /// comma-separated list of [Expectation]s. The path may point to an individual | 40 /// comma-separated list of [Expectation]s. The path may point to an individual |
41 /// file, or a directory, in which case it applies to all files under that path. | 41 /// file, or a directory, in which case it applies to all files under that path. |
42 /// | 42 /// |
43 /// Entries may also appear before any section header, in which case they | 43 /// Entries may also appear before any section header, in which case they |
44 /// always apply. | 44 /// always apply. |
45 class StatusFile { | 45 class StatusFile { |
46 final String path; | 46 final String path; |
47 final List<StatusSection> sections = []; | 47 final List<StatusSection> sections = []; |
48 final List<String> _comments = []; | |
49 | |
50 int _lineCount = 0; | |
48 | 51 |
49 StatusFile(this.path); | 52 StatusFile(this.path); |
50 | 53 |
51 /// Parses the status file at [path]. | 54 /// Parses the status file at [path]. |
52 /// | 55 /// |
53 /// Throws a [SyntaxError] if the file could not be parsed. | 56 /// Throws a [SyntaxError] if the file could not be parsed. |
54 StatusFile.read(this.path) { | 57 StatusFile.read(this.path) { |
55 var lines = new File(path).readAsLinesSync(); | 58 var lines = new File(path).readAsLinesSync(); |
59 _comments.length = lines.length + 1; | |
56 | 60 |
57 // The current section whose rules are being parsed. | 61 // The current section whose rules are being parsed. |
58 StatusSection section; | 62 StatusSection section; |
59 | 63 |
60 var lineNumber = 0; | |
61 | |
62 for (var line in lines) { | 64 for (var line in lines) { |
63 lineNumber++; | 65 _lineCount++; |
64 | 66 |
65 fail(String message, [List<String> errors]) { | 67 fail(String message, [List<String> errors]) { |
66 throw new SyntaxError(_shortPath, lineNumber, line, message, errors); | 68 throw new SyntaxError(_shortPath, _lineCount, line, message, errors); |
67 } | 69 } |
68 | 70 |
69 // Strip off the comment and whitespace. | 71 // Strip off the comment and whitespace. |
70 var source = line; | 72 var source = line; |
71 var comment = ""; | 73 var comment = ""; |
72 var hashIndex = line.indexOf('#'); | 74 var hashIndex = line.indexOf('#'); |
73 if (hashIndex >= 0) { | 75 if (hashIndex >= 0) { |
74 source = line.substring(0, hashIndex); | 76 source = line.substring(0, hashIndex); |
75 comment = line.substring(hashIndex); | 77 comment = line.substring(hashIndex + 1); |
78 _comments[_lineCount] = comment; | |
76 } | 79 } |
77 source = source.trim(); | 80 source = source.trim(); |
78 | 81 |
79 // Ignore empty (or comment-only) lines. | 82 // Ignore empty (or comment-only) lines. |
80 if (source.isEmpty) continue; | 83 if (source.isEmpty) continue; |
81 | 84 |
82 // See if we are starting a new section. | 85 // See if we are starting a new section. |
83 var match = _sectionPattern.firstMatch(source); | 86 var match = _sectionPattern.firstMatch(source); |
84 if (match != null) { | 87 if (match != null) { |
85 try { | 88 try { |
86 var condition = Expression.parse(match[1].trim()); | 89 var condition = Expression.parse(match[1].trim()); |
87 section = new StatusSection(condition, lineNumber); | 90 section = new StatusSection(condition, _lineCount); |
88 sections.add(section); | 91 sections.add(section); |
89 } on FormatException { | 92 } on FormatException { |
90 fail("Status expression syntax error"); | 93 fail("Status expression syntax error"); |
91 } | 94 } |
92 continue; | 95 continue; |
93 } | 96 } |
94 | 97 |
95 // Otherwise, it should be a new entry under the current section. | 98 // Otherwise, it should be a new entry under the current section. |
96 match = _entryPattern.firstMatch(source); | 99 match = _entryPattern.firstMatch(source); |
97 if (match != null) { | 100 if (match != null) { |
(...skipping 12 matching lines...) Expand all Loading... | |
110 var issue = _issueNumber(comment); | 113 var issue = _issueNumber(comment); |
111 | 114 |
112 // If we haven't found a section header yet, create an implicit section | 115 // If we haven't found a section header yet, create an implicit section |
113 // that matches everything. | 116 // that matches everything. |
114 if (section == null) { | 117 if (section == null) { |
115 section = new StatusSection(null, -1); | 118 section = new StatusSection(null, -1); |
116 sections.add(section); | 119 sections.add(section); |
117 } | 120 } |
118 | 121 |
119 section.entries | 122 section.entries |
120 .add(new StatusEntry(path, lineNumber, expectations, issue)); | 123 .add(new StatusEntry(path, _lineCount, expectations, issue)); |
121 continue; | 124 continue; |
122 } | 125 } |
123 | 126 |
124 fail("Unrecognized input"); | 127 fail("Unrecognized input"); |
125 } | 128 } |
126 } | 129 } |
127 | 130 |
128 bool get isEmpty => sections.isEmpty; | 131 bool get isEmpty => sections.isEmpty; |
129 | 132 |
130 /// Validates that the variables and values used in all of the section | 133 /// Validates that the variables and values used in all of the section |
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
171 buffer.write("${entry.path}: ${entry.expectations.join(', ')}"); | 174 buffer.write("${entry.path}: ${entry.expectations.join(', ')}"); |
172 if (entry.issue != null) buffer.write(" # Issue ${entry.issue}"); | 175 if (entry.issue != null) buffer.write(" # Issue ${entry.issue}"); |
173 buffer.writeln(); | 176 buffer.writeln(); |
174 } | 177 } |
175 | 178 |
176 buffer.writeln(); | 179 buffer.writeln(); |
177 } | 180 } |
178 | 181 |
179 return buffer.toString(); | 182 return buffer.toString(); |
180 } | 183 } |
184 | |
185 /// Serialize the status file to a string. | |
186 /// | |
187 /// Unlike [toString()], this preserves comments and gives a "canonical" | |
188 /// rendering of the status file that can be saved back to disc. | |
189 String serialize() { | |
190 var buffer = new StringBuffer(); | |
191 | |
192 var lastLine = 0; | |
193 var needBlankLine = false; | |
194 | |
195 void writeLine(String text, int line) { | |
196 var comment = _comments[line]; | |
197 if (text == null && comment == null) { | |
198 // There's no comment on this line, so it's blank. | |
199 needBlankLine = true; | |
200 return; | |
201 } | |
202 | |
203 if (needBlankLine) buffer.writeln(); | |
204 needBlankLine = false; | |
205 | |
206 if (text != null) { | |
207 buffer.write(text); | |
208 } | |
209 | |
210 if (comment != null) { | |
211 if (text != null) buffer.write(" "); | |
212 buffer.write("#$comment"); | |
213 } | |
214 | |
215 buffer.writeln(); | |
216 } | |
217 | |
218 void writeText(String text, int line) { | |
219 if (line != null) { | |
220 while (++lastLine < line) { | |
221 writeLine(null, lastLine); | |
222 } | |
223 } | |
224 | |
225 writeLine(text, line); | |
226 } | |
227 | |
228 for (var section in sections) { | |
229 if (section.condition != null) { | |
230 writeText("[ ${section.condition} ]", section.lineNumber); | |
231 } | |
232 | |
233 for (var entry in section.entries) { | |
Bill Hesse
2017/08/03 14:24:07
Not in the printing, but as a separate stage, I th
Bob Nystrom
2017/08/03 20:28:49
Yeah, I thought of that after I sent this out. I m
| |
234 writeText("${entry.path}: ${entry.expectations.join(', ')}", | |
235 entry.lineNumber); | |
236 } | |
237 | |
238 needBlankLine = true; | |
239 } | |
240 | |
241 // Write any trailing comments. | |
242 while (++lastLine <= _lineCount) { | |
243 writeLine(null, lastLine); | |
244 } | |
245 | |
246 return buffer.toString(); | |
247 } | |
181 } | 248 } |
182 | 249 |
183 /// One section in a status file. | 250 /// One section in a status file. |
184 /// | 251 /// |
185 /// Contains the condition from the header that begins the section, then all of | 252 /// Contains the condition from the header that begins the section, then all of |
186 /// the entries within the section. | 253 /// the entries within the section. |
187 class StatusSection { | 254 class StatusSection { |
188 /// The expression that determines when this section is applied. | 255 /// The expression that determines when this section is applied. |
189 /// | 256 /// |
190 /// May be `null` for paths that appear before any section header in the file. | 257 /// May be `null` for paths that appear before any section header in the file. |
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
233 | 300 |
234 if (errors != null) { | 301 if (errors != null) { |
235 for (var error in errors) { | 302 for (var error in errors) { |
236 buffer.writeln("- ${error.replaceAll('\n', '\n ')}"); | 303 buffer.writeln("- ${error.replaceAll('\n', '\n ')}"); |
237 } | 304 } |
238 } | 305 } |
239 | 306 |
240 return buffer.toString().trimRight(); | 307 return buffer.toString().trimRight(); |
241 } | 308 } |
242 } | 309 } |
OLD | NEW |