From b102dc496a0d5dd92f7395bd7badce7216d5a429 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum Date: Fri, 1 Feb 2019 14:59:58 -0800 Subject: [PATCH] Allow individual directories to be passed on the command-line (#581) Closes #543 --- CHANGELOG.md | 4 +++ lib/src/executable.dart | 2 +- lib/src/executable/options.dart | 44 ++++++++++++++++++++++++--------- test/cli/shared/colon_args.dart | 40 ++++++++++++++++++++++++++++++ 4 files changed, 78 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8968ab3fb..ed285c3f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ ### Command-Line Interface +* Passing a directory on the command line now compiles all Sass source files in + the directory to CSS files in the same directory, as though `dir:dir` were + passed instead of just `dir`. + * The new error output uses non-ASCII Unicode characters by default. Add a `--no-unicode` flag to disable this. diff --git a/lib/src/executable.dart b/lib/src/executable.dart index e81df75cf..341bb1432 100644 --- a/lib/src/executable.dart +++ b/lib/src/executable.dart @@ -96,7 +96,7 @@ main(List args) async { } on UsageException catch (error) { print("${error.message}\n"); print("Usage: sass [output.css]\n" - " sass : :\n"); + " sass : : \n"); print(ExecutableOptions.usage); exitCode = 64; } catch (error, stackTrace) { diff --git a/lib/src/executable/options.dart b/lib/src/executable/options.dart index 6345f9e0a..82ef17dac 100644 --- a/lib/src/executable/options.dart +++ b/lib/src/executable/options.dart @@ -227,21 +227,22 @@ class ExecutableOptions { var stdin = _options['stdin'] as bool; if (_options.rest.isEmpty && !stdin) _fail("Compile Sass to CSS."); + var directories = Set(); var colonArgs = false; var positionalArgs = false; for (var argument in _options.rest) { if (argument.isEmpty) _fail('Invalid argument "".'); - // If the colon appears at position 1, treat it as a Windows drive - // letter. - if (!argument.contains(":") || - (_isWindowsPath(argument, 0) && + if (argument.contains(":") && + (!_isWindowsPath(argument, 0) || // Look for colons after index 1, since that's where the drive // letter is on Windows paths. - argument.indexOf(":", 2) == -1)) { - positionalArgs = true; - } else { + argument.indexOf(":", 2) != -1)) { colonArgs = true; + } else if (dirExists(argument)) { + directories.add(argument); + } else { + positionalArgs = true; } } @@ -260,6 +261,21 @@ class ExecutableOptions { {null: _options.rest.isEmpty ? null : _options.rest.first}); } else if (_options.rest.length > 2) { _fail("Only two positional args may be passed."); + } else if (directories.isNotEmpty) { + var message = + 'Directory "${directories.first}" may not be a positional arg.'; + + // If it looks like the user called `sass in-dir out-dir`, suggest they + // call "sass in-dir:out-dir` instead. Don't do this if they wrote + // `sass dir file.scss` or `sass something dir`. + var target = _options.rest.last; + if (directories.first == _options.rest.first && !fileExists(target)) { + message += '\n' + 'To compile all CSS in "${directories.first}" to "$target", use ' + '`sass ${directories.first}:$target`.'; + } + + _fail(message); } else { var source = _options.rest.first == '-' ? null : _options.rest.first; var destination = _options.rest.length == 1 ? null : _options.rest.last; @@ -286,6 +302,14 @@ class ExecutableOptions { var sourcesToDestinations = p.PathMap(); var sourceDirectoriesToDestinations = p.PathMap(); for (var argument in _options.rest) { + if (directories.contains(argument)) { + if (!seen.add(argument)) _fail('Duplicate source "$argument".'); + + sourceDirectoriesToDestinations[argument] = argument; + sourcesToDestinations.addAll(_listSourceDirectory(argument, argument)); + continue; + } + String source; String destination; for (var i = 0; i < argument.length; i++) { @@ -299,16 +323,14 @@ class ExecutableOptions { destination = argument.substring(i + 1); } else if (i != source.length + 2 || !_isWindowsPath(argument, i - 1)) { - // A colon 2 character after the separator may also be a Windows + // A colon 2 characters after the separator may also be a Windows // drive letter. _fail('"$argument" may only contain one ":".'); } } } - if (!seen.add(source)) { - _fail('Duplicate source "${source}".'); - } + if (!seen.add(source)) _fail('Duplicate source "$source".'); if (source == '-') { sourcesToDestinations[null] = destination; diff --git a/test/cli/shared/colon_args.dart b/test/cli/shared/colon_args.dart index 592b18a49..806ae5157 100644 --- a/test/cli/shared/colon_args.dart +++ b/test/cli/shared/colon_args.dart @@ -135,6 +135,23 @@ void sharedTests(Future runSass(Iterable arguments)) { ]).validate(); }); + test("compiles files to the same directory if no output is given", + () async { + await d.dir("in", [ + d.file("test1.scss", "a {b: c}"), + d.file("test2.sass", "x\n y: z") + ]).create(); + + var sass = await runSass(["--no-source-map", "in"]); + expect(sass.stdout, emitsDone); + await sass.shouldExit(0); + + await d.dir("in", [ + d.file("test1.css", equalsIgnoringWhitespace("a { b: c; }")), + d.file("test2.css", equalsIgnoringWhitespace("x { y: z; }")) + ]).validate(); + }); + test("ignores partials", () async { await d.dir("in", [ d.file("_fake.scss", "a {b:"), @@ -250,6 +267,29 @@ void sharedTests(Future runSass(Iterable arguments)) { emits('Positional and ":" arguments may not both be used.')); await sass.shouldExit(64); }); + + test("before a directory", () async { + await d.dir("in").create(); + + var sass = await runSass(["positional", "in"]); + expect( + sass.stdout, emits('Directory "in" may not be a positional arg.')); + await sass.shouldExit(64); + }); + + test("after a directory", () async { + await d.dir("in").create(); + + var sass = await runSass(["in", "positional"]); + expect( + sass.stdout, + emitsInOrder([ + 'Directory "in" may not be a positional arg.', + 'To compile all CSS in "in" to "positional", use `sass ' + 'in:positional`.' + ])); + await sass.shouldExit(64); + }); }); test("--stdin", () async {