From 880c91444e6208571619782fcfc76a5baeefeaaa Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Tue, 27 Mar 2018 13:45:03 -0700 Subject: [PATCH] Add support for "sass input.scss output.css" (#275) Closes #274 --- CHANGELOG.md | 5 +++ lib/src/executable.dart | 24 +++++++--- lib/src/io/interface.dart | 5 +++ lib/src/io/node.dart | 12 ++++- lib/src/io/vm.dart | 3 ++ pubspec.yaml | 2 +- test/cli_shared.dart | 95 +++++++++++++++++++++------------------ 7 files changed, 94 insertions(+), 52 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d8eacf9..a3af75ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 1.1.0 + +* The command-line executable can now be used to write an output file to disk + using `sass input.scss output.css`. + ## 1.0.0 **Initial stable release.** diff --git a/lib/src/executable.dart b/lib/src/executable.dart index d8d0c717..cf88cc18 100644 --- a/lib/src/executable.dart +++ b/lib/src/executable.dart @@ -61,7 +61,10 @@ main(List args) async { } var stdinFlag = options['stdin'] as bool; - if (options['help'] as bool || (options.rest.isEmpty && !stdinFlag)) { + if (options['help'] as bool || + (stdinFlag + ? options.rest.length > 1 + : options.rest.isEmpty || options.rest.length > 2)) { _printUsage(argParser, "Compile Sass to CSS."); exitCode = 64; return; @@ -80,7 +83,9 @@ main(List args) async { var asynchronous = options['async'] as bool; try { String css; + String destination; if (stdinFlag) { + if (options.rest.isNotEmpty) destination = options.rest.first; css = await _compileStdin( indented: indented, logger: logger, @@ -88,8 +93,9 @@ main(List args) async { loadPaths: loadPaths, asynchronous: asynchronous); } else { - var input = options.rest.first; - if (input == '-') { + var source = options.rest.first; + if (options.rest.length > 1) destination = options.rest.last; + if (source == '-') { css = await _compileStdin( indented: indented, logger: logger, @@ -97,15 +103,19 @@ main(List args) async { loadPaths: loadPaths, asynchronous: asynchronous); } else if (asynchronous) { - css = await compileAsync(input, + css = await compileAsync(source, logger: logger, style: style, loadPaths: loadPaths); } else { css = - compile(input, logger: logger, style: style, loadPaths: loadPaths); + compile(source, logger: logger, style: style, loadPaths: loadPaths); } } - if (css.isNotEmpty) print(css); + if (destination != null) { + writeFile(destination, css + "\n"); + } else if (css.isNotEmpty) { + print(css); + } } on SassException catch (error, stackTrace) { stderr.writeln(error.toString(color: color)); @@ -192,6 +202,6 @@ Future _compileStdin( /// Print the usage information for Sass, with [message] as a header. void _printUsage(ArgParser parser, String message) { print("$message\n"); - print("Usage: dart-sass \n"); + print("Usage: sass [output]\n"); print(parser.usage); } diff --git a/lib/src/io/interface.dart b/lib/src/io/interface.dart index 8b6fa925..bf949431 100644 --- a/lib/src/io/interface.dart +++ b/lib/src/io/interface.dart @@ -46,6 +46,11 @@ String get currentPath => null; /// the file isn't valid UTF-8. String readFile(String path) => null; +/// Writes [contents] to the file at [path], encoded as UTF-8. +/// +/// Throws a [FileSystemException] if writing fails. +void writeFile(String path, String contents) => null; + /// Reads from the standard input for the current process until it closes, /// returning the contents. Future readStdin() async => null; diff --git a/lib/src/io/node.dart b/lib/src/io/node.dart index faebc4ed..14d2a476 100644 --- a/lib/src/io/node.dart +++ b/lib/src/io/node.dart @@ -14,7 +14,7 @@ import '../util/path.dart'; @JS() class _FS { external readFileSync(String path, [String encoding]); - + external void writeFileSync(String path, String data); external bool existsSync(String path); } @@ -102,6 +102,16 @@ _readFile(String path, [String encoding]) { } } +void writeFile(String path, String contents) { + try { + return _fs.writeFileSync(path, contents); + } catch (error) { + var systemError = error as _SystemError; + throw new FileSystemException._( + _cleanErrorMessage(systemError), systemError.path); + } +} + Future readStdin() async { var completer = new Completer(); String contents; diff --git a/lib/src/io/vm.dart b/lib/src/io/vm.dart index 06381fad..bc577c75 100644 --- a/lib/src/io/vm.dart +++ b/lib/src/io/vm.dart @@ -41,6 +41,9 @@ String readFile(String path) { } } +void writeFile(String path, String contents) => + new io.File(path).writeAsStringSync(contents); + Future readStdin() async { var completer = new Completer(); completer.complete(await io.SYSTEM_ENCODING.decodeStream(io.stdin)); diff --git a/pubspec.yaml b/pubspec.yaml index cbbf07f2..e84c67a6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: sass -version: 1.0.0 +version: 1.1.0-dev description: A Sass implementation in Dart. author: Dart Team homepage: https://github.com/sass/dart-sass diff --git a/test/cli_shared.dart b/test/cli_shared.dart index 28a62adf..3dcd7216 100644 --- a/test/cli_shared.dart +++ b/test/cli_shared.dart @@ -10,6 +10,14 @@ import 'package:test_process/test_process.dart'; /// Defines test that are shared between the Dart and Node.js CLI test suites. void sharedTests(Future runSass(Iterable arguments)) { + /// Runs the executable on [arguments] plus an output file, then verifies that + /// the contents of the output file match [expected]. + Future expectCompiles(List arguments, expected) async { + var sass = await runSass(arguments.toList()..add("out.css")); + await sass.shouldExit(0); + await d.file("out.css", expected).validate(); + } + test("--help prints the usage documentation", () async { // Checking the entire output is brittle, so just do a sanity check to make // sure it's not totally busted. @@ -34,6 +42,15 @@ void sharedTests(Future runSass(Iterable arguments)) { await sass.shouldExit(0); }); + test("writes a CSS file to disk", () async { + await d.file("test.scss", "a {b: 1 + 2}").create(); + + var sass = await runSass(["test.scss", "out.css"]); + expect(sass.stdout, emitsDone); + await sass.shouldExit(0); + await d.file("out.css", equalsIgnoringWhitespace("a { b: 3; }")).validate(); + }); + test("compiles from stdin with the magic path -", () async { var sass = await runSass(["-"]); sass.stdin.writeln("a {b: 1 + 2}"); @@ -54,15 +71,8 @@ void sharedTests(Future runSass(Iterable arguments)) { await d.dir("dir", [d.file("test.scss", "a {b: 1 + 2}")]).create(); - var sass = await runSass(["test.scss", "test.css"]); - expect( - sass.stdout, - emitsInOrder([ - "a {", - " b: 3;", - "}", - ])); - await sass.shouldExit(0); + await expectCompiles( + ["test.scss"], equalsIgnoringWhitespace("a { b: 3; }")); }); test("from the load path", () async { @@ -70,15 +80,8 @@ void sharedTests(Future runSass(Iterable arguments)) { await d.dir("dir", [d.file("test2.scss", "a {b: c}")]).create(); - var sass = await runSass(["--load-path", "dir", "test.scss", "test.css"]); - expect( - sass.stdout, - emitsInOrder([ - "a {", - " b: c;", - "}", - ])); - await sass.shouldExit(0); + await expectCompiles(["--load-path", "dir", "test.scss"], + equalsIgnoringWhitespace("a { b: c; }")); }); test("relative in preference to from the load path", () async { @@ -87,15 +90,8 @@ void sharedTests(Future runSass(Iterable arguments)) { await d.dir("dir", [d.file("test2.scss", "a {b: c}")]).create(); - var sass = await runSass(["--load-path", "dir", "test.scss", "test.css"]); - expect( - sass.stdout, - emitsInOrder([ - "x {", - " y: z;", - "}", - ])); - await sass.shouldExit(0); + await expectCompiles(["--load-path", "dir", "test.scss"], + equalsIgnoringWhitespace("x { y: z; }")); }); test("in load path order", () async { @@ -104,22 +100,9 @@ void sharedTests(Future runSass(Iterable arguments)) { await d.dir("dir1", [d.file("test2.scss", "a {b: c}")]).create(); await d.dir("dir2", [d.file("test2.scss", "x {y: z}")]).create(); - var sass = await runSass([ - "--load-path", - "dir2", - "--load-path", - "dir1", - "test.scss", - "test.css" - ]); - expect( - sass.stdout, - emitsInOrder([ - "x {", - " y: z;", - "}", - ])); - await sass.shouldExit(0); + await expectCompiles( + ["--load-path", "dir2", "--load-path", "dir1", "test.scss"], + equalsIgnoringWhitespace("x { y: z; }")); }); }); @@ -138,6 +121,18 @@ void sharedTests(Future runSass(Iterable arguments)) { await sass.shouldExit(0); }); + test("writes a CSS file to disk", () async { + var sass = await runSass(["--stdin", "out.css"]); + sass.stdin.writeln("a {b: 1 + 2}"); + sass.stdin.close(); + expect(sass.stdout, emitsDone); + + await sass.shouldExit(0); + await d + .file("out.css", equalsIgnoringWhitespace("a { b: 3; }")) + .validate(); + }); + test("uses the indented syntax with --indented", () async { var sass = await runSass(["--stdin", "--indented"]); sass.stdin.writeln("a\n b: 1 + 2"); @@ -226,6 +221,20 @@ void sharedTests(Future runSass(Iterable arguments)) { await sass.shouldExit(64); }); + test("from too many positional arguments", () async { + var sass = await runSass(["abc", "def", "ghi"]); + expect( + sass.stdout, emitsThrough(contains("Print this usage information."))); + await sass.shouldExit(64); + }); + + test("from too many positional arguments with --stdin", () async { + var sass = await runSass(["--stdin", "abc", "def"]); + expect( + sass.stdout, emitsThrough(contains("Print this usage information."))); + await sass.shouldExit(64); + }); + test("from a file that doesn't exist", () async { var sass = await runSass(["asdf"]); expect(sass.stderr, emits(startsWith("Error reading asdf:")));