// Copyright 2016 Google Inc. Use of this source code is governed by an // MIT-style license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. import 'dart:isolate'; import 'package:path/path.dart' as p; import 'package:stack_trace/stack_trace.dart'; import 'package:term_glyph/term_glyph.dart' as term_glyph; import 'package:sass/src/exception.dart'; import 'package:sass/src/executable/compile_stylesheet.dart'; import 'package:sass/src/executable/options.dart'; import 'package:sass/src/executable/repl.dart'; import 'package:sass/src/executable/watch.dart'; import 'package:sass/src/import_cache.dart'; import 'package:sass/src/io.dart'; import 'package:sass/src/io.dart' as io; import 'package:sass/src/logger/deprecation_handling.dart'; import 'package:sass/src/stylesheet_graph.dart'; import 'package:sass/src/util/map.dart'; import 'package:sass/src/utils.dart'; import 'package:sass/src/embedded/executable.dart' // Never load the embedded protocol when compiling to JS. if (dart.library.js) 'package:sass/src/embedded/unavailable.dart' as embedded; Future main(List args) async { var printedError = false; // Prints [error] to stderr, along with a preceding newline if anything else // has been printed to stderr. // // If [trace] is passed, its terse representation is printed after the error. void printError(String error, StackTrace? stackTrace) { var buffer = StringBuffer(); if (printedError) buffer.writeln(); printedError = true; buffer.write(error); if (stackTrace != null) { buffer.writeln(); buffer.writeln(); buffer.write(Trace.from(stackTrace).terse.toString().trimRight()); } io.printError(buffer); } if (args case ['--embedded', ...var rest]) { embedded.main(rest); return; } ExecutableOptions? options; try { options = ExecutableOptions.parse(args); term_glyph.ascii = !options.unicode; if (options.version) { print(await _loadVersion()); exitCode = 0; return; } if (options.interactive) { await repl(options); return; } var graph = StylesheetGraph(ImportCache( loadPaths: options.loadPaths, // This logger is only used for handling fatal/future deprecations // during parsing, and is re-used across parses, so we don't want to // limit repetition. A separate DeprecationHandlingLogger is created for // each compilation, which will limit repetition if verbose is not // passed in addition to handling fatal/future deprecations. logger: DeprecationHandlingLogger(options.logger, fatalDeprecations: options.fatalDeprecations, futureDeprecations: options.futureDeprecations, limitRepetition: false))); if (options.watch) { await watch(options, graph); return; } for (var (source, destination) in options.sourcesToDestinations.pairs) { try { await compileStylesheet(options, graph, source, destination, ifModified: options.update); } on SassException catch (error, stackTrace) { if (destination != null && !options.emitErrorCss) { _tryDelete(destination); } printError(error.toString(color: options.color), options.trace ? getTrace(error) ?? stackTrace : null); // Exit code 65 indicates invalid data per // https://www.freebsd.org/cgi/man.cgi?query=sysexits. // // We let exitCode 66 take precedence for deterministic behavior. if (exitCode != 66) exitCode = 65; if (options.stopOnError) return; } on FileSystemException catch (error, stackTrace) { var path = error.path; printError( path == null ? error.message : "Error reading ${p.relative(path)}: ${error.message}.", options.trace ? getTrace(error) ?? stackTrace : null); // Error 66 indicates no input. exitCode = 66; if (options.stopOnError) return; } } } on UsageException catch (error) { print("${error.message}\n"); print("Usage: sass [output.css]\n" " sass : : \n"); print(ExecutableOptions.usage); exitCode = 64; } catch (error, stackTrace) { var buffer = StringBuffer(); if (options?.color ?? false) buffer.write('\u001b[31m\u001b[1m'); buffer.write('Unexpected exception:'); if (options?.color ?? false) buffer.write('\u001b[0m'); buffer.writeln(); buffer.writeln(error); printError(buffer.toString(), getTrace(error) ?? stackTrace); exitCode = 255; } } /// Loads and returns the current version of Sass. Future _loadVersion() async { if (const bool.hasEnvironment('version')) { var version = const String.fromEnvironment('version'); if (const bool.fromEnvironment('node')) { version += " compiled with dart2js " "${const String.fromEnvironment('dart-version')}"; } return version; } var libDir = p.fromUri(await Isolate.resolvePackageUri(Uri.parse('package:sass/'))); var pubspec = readFile(p.join(libDir, '..', 'pubspec.yaml')); return pubspec .split("\n") .firstWhere((line) => line.startsWith('version: ')) .split(" ") .last; } /// Delete [path] if it exists and do nothing otherwise. /// /// This is a separate function to work around dart-lang/sdk#53082. void _tryDelete(String path) { try { deleteFile(path); } on FileSystemException { // If the file doesn't exist, that's fine. } }