Add infrastructure for compiling multiple sources at once

This will allow us to use the same code path for --update as we do for
normal compilation.
This commit is contained in:
Natalie Weizenbaum 2018-05-20 18:32:55 +01:00
parent 454603d160
commit beff4a1011
2 changed files with 123 additions and 117 deletions

View File

@ -25,67 +25,9 @@ main(List<String> args) async {
return;
}
try {
SingleMapping sourceMap;
var sourceMapCallback =
options.emitSourceMap ? (SingleMapping map) => sourceMap = map : null;
var text =
options.readFromStdin ? await readStdin() : readFile(options.source);
var url = options.readFromStdin ? null : p.toUri(options.source);
var importer = new FilesystemImporter('.');
String css;
if (options.asynchronous) {
css = await compileStringAsync(text,
indented: options.indented,
logger: options.logger,
style: options.style,
importer: importer,
loadPaths: options.loadPaths,
url: url,
sourceMap: sourceMapCallback);
} else {
css = compileString(text,
indented: options.indented,
logger: options.logger,
style: options.style,
importer: importer,
loadPaths: options.loadPaths,
url: url,
sourceMap: sourceMapCallback);
}
css += _writeSourceMap(options, sourceMap);
if (options.writeToStdout) {
if (css.isNotEmpty) print(css);
} else {
ensureDir(p.dirname(options.destination));
writeFile(options.destination, css + "\n");
}
} on SassException catch (error, stackTrace) {
stderr.writeln(error.toString(color: options.color));
if (options.trace) {
stderr.writeln();
stderr.write(new Trace.from(stackTrace).terse.toString());
stderr.flush();
}
// Exit code 65 indicates invalid data per
// http://www.freebsd.org/cgi/man.cgi?query=sysexits.
exitCode = 65;
} on FileSystemException catch (error, stackTrace) {
stderr.writeln(
"Error reading ${p.relative(error.path)}: ${error.message}.");
// Error 66 indicates no input.
exitCode = 66;
if (options.trace) {
stderr.writeln();
stderr.write(new Trace.from(stackTrace).terse.toString());
stderr.flush();
}
for (var source in options.sourcesToDestinations.keys) {
var destination = options.sourcesToDestinations[source];
await _compileStylesheet(options, source, destination);
}
} on UsageException catch (error) {
print("${error.message}\n");
@ -125,14 +67,92 @@ Future<String> _loadVersion() async {
.last;
}
/// Writes the source map given by [mapping] to disk (if necessary) according to [options].
/// Compiles the stylesheet at [source] to [destination].
///
/// If [source] is `null`, that indicates that the stylesheet should be read
/// from stdin. If [destination] is `null`, that indicates that the stylesheet
/// should be emitted to stdout.
Future _compileStylesheet(
ExecutableOptions options, String source, String destination) async {
try {
SingleMapping sourceMap;
var sourceMapCallback =
options.emitSourceMap ? (SingleMapping map) => sourceMap = map : null;
var indented =
options.indented ?? (source != null && p.extension(source) == '.sass');
var text = source == null ? await readStdin() : readFile(source);
var url = source == null ? null : p.toUri(source);
var importer = new FilesystemImporter('.');
String css;
if (options.asynchronous) {
css = await compileStringAsync(text,
indented: indented,
logger: options.logger,
style: options.style,
importer: importer,
loadPaths: options.loadPaths,
url: url,
sourceMap: sourceMapCallback);
} else {
css = compileString(text,
indented: indented,
logger: options.logger,
style: options.style,
importer: importer,
loadPaths: options.loadPaths,
url: url,
sourceMap: sourceMapCallback);
}
css += _writeSourceMap(options, sourceMap, destination);
if (destination == null) {
if (css.isNotEmpty) print(css);
} else {
ensureDir(p.dirname(destination));
writeFile(destination, css + "\n");
}
} on SassException catch (error, stackTrace) {
stderr.writeln(error.toString(color: options.color));
if (options.trace) {
stderr.writeln();
stderr.write(new Trace.from(stackTrace).terse.toString());
stderr.flush();
}
// Exit code 65 indicates invalid data per
// http://www.freebsd.org/cgi/man.cgi?query=sysexits.
exitCode = 65;
} on FileSystemException catch (error, stackTrace) {
stderr
.writeln("Error reading ${p.relative(error.path)}: ${error.message}.");
// Error 66 indicates no input.
exitCode = 66;
if (options.trace) {
stderr.writeln();
stderr.write(new Trace.from(stackTrace).terse.toString());
stderr.flush();
}
}
}
/// Writes the source map given by [mapping] to disk (if necessary) according to
/// [options].
///
/// The [destination] is the path where the CSS file associated with this source
/// map will be written. If it's `null`, that indicates that the CSS will be
/// printed to stdout.
///
/// Returns the source map comment to add to the end of the CSS file.
String _writeSourceMap(ExecutableOptions options, SingleMapping sourceMap) {
String _writeSourceMap(
ExecutableOptions options, SingleMapping sourceMap, String destination) {
if (sourceMap == null) return "";
if (!options.writeToStdout) {
sourceMap.targetUrl = p.toUri(p.basename(options.destination)).toString();
if (destination != null) {
sourceMap.targetUrl = p.toUri(p.basename(destination)).toString();
}
for (var i = 0; i < sourceMap.urls.length; i++) {
@ -141,7 +161,8 @@ String _writeSourceMap(ExecutableOptions options, SingleMapping sourceMap) {
// The special URL "" indicates a file that came from stdin.
if (url == "") continue;
sourceMap.urls[i] = options.sourceMapUrl(Uri.parse(url)).toString();
sourceMap.urls[i] =
options.sourceMapUrl(Uri.parse(url), destination).toString();
}
var sourceMapText = convert.json
.encode(sourceMap.toJson(includeSourceContents: options.embedSources));
@ -150,7 +171,7 @@ String _writeSourceMap(ExecutableOptions options, SingleMapping sourceMap) {
if (options.embedSourceMap) {
url = new Uri.dataFromString(sourceMapText, mimeType: 'application/json');
} else {
var sourceMapPath = options.destination + '.map';
var sourceMapPath = destination + '.map';
ensureDir(p.dirname(sourceMapPath));
writeFile(sourceMapPath, sourceMapText);

View File

@ -101,9 +101,10 @@ class ExecutableOptions {
bool get version => _options['version'] as bool;
/// Whether to parse the source file with the indented syntax.
bool get indented =>
_ifParsed('indented') as bool ??
(source != null && p.extension(source) == '.sass');
///
/// This may be `null`, indicating that this should be determined by each
/// stylesheet's extension.
bool get indented => _ifParsed('indented') as bool;
/// Whether to use ANSI terminal colors.
bool get color =>
@ -128,32 +129,33 @@ class ExecutableOptions {
/// Whether to print the full Dart stack trace on exceptions.
bool get trace => _options['trace'] as bool;
/// The entrypoint Sass file, or `null` if the source should be read from
/// stdin.
String get source {
_ensureSourceAndDestination();
return _source;
/// A map from source paths to the destination paths where the compiled CSS
/// should be written.
///
/// A `null` source indicates that a stylesheet should be read from standard
/// input. A `null` destination indicates that a stylesheet should be written
/// to standard output.
Map<String, String> get sourcesToDestinations {
if (_sourcesToDestinations != null) return _sourcesToDestinations;
String source;
String destination;
if (_options['stdin'] as bool) {
if (_options.rest.length > 1) _fail("Compile Sass to CSS.");
if (_options.rest.isNotEmpty) destination = _options.rest.first;
} else if (_options.rest.isEmpty || _options.rest.length > 2) {
_fail("Compile Sass to CSS.");
} else if (_options.rest.first == '-') {
if (_options.rest.length > 1) destination = _options.rest.last;
} else {
source = _options.rest.first;
if (_options.rest.length > 1) destination = _options.rest.last;
}
_sourcesToDestinations = new Map.unmodifiable({source: destination});
return _sourcesToDestinations;
}
String _source;
/// Whether to read the source file from stdin rather than a file on disk.
bool get readFromStdin => source == null;
/// The path to which to write the CSS, or `null` if the CSS should be printed
/// to stdout.
String get destination {
_ensureSourceAndDestination();
return _destination;
}
String _destination;
/// Whether to write the output CSS to stdout rather than a file on disk.
bool get writeToStdout => destination == null;
/// Whether [_source] and [_destination] have been parsed from [_options] yet.
var _parsedSourceAndDestination = false;
Map<String, String> _sourcesToDestinations;
/// Whether to emit a source map file.
bool get emitSourceMap {
@ -167,7 +169,9 @@ class ExecutableOptions {
}
}
if (destination != null) return _options['source-map'] as bool;
var writeToStdout = sourcesToDestinations.length == 1 &&
sourcesToDestinations.values.single == null;
if (!writeToStdout) return _options['source-map'] as bool;
if (_ifParsed('source-map-urls') == 'relative') {
_fail(
@ -211,28 +215,9 @@ class ExecutableOptions {
ExecutableOptions._(this._options);
/// Parses [source] and [destination] from [_options] if they haven't been
/// parsed yet.
void _ensureSourceAndDestination() {
if (_parsedSourceAndDestination) return;
_parsedSourceAndDestination = true;
if (_options['stdin'] as bool) {
if (_options.rest.length > 1) _fail("Compile Sass to CSS.");
if (_options.rest.isNotEmpty) _destination = _options.rest.first;
} else if (_options.rest.isEmpty || _options.rest.length > 2) {
_fail("Compile Sass to CSS.");
} else if (_options.rest.first == '-') {
if (_options.rest.length > 1) _destination = _options.rest.last;
} else {
_source = _options.rest.first;
if (_options.rest.length > 1) _destination = _options.rest.last;
}
}
/// Makes [url] absolute or relative (to [dir]) according to the
/// `source-map-urls` option.
Uri sourceMapUrl(Uri url) {
/// Makes [url] absolute or relative (to the directory containing
/// [destination]) according to the `source-map-urls` option.
Uri sourceMapUrl(Uri url, String destination) {
var path = p.fromUri(url);
return p.toUri(_options['source-map-urls'] == 'relative'
? p.relative(path, from: p.dirname(destination))