Skip to content

Commit

Permalink
Allow individual directories to be passed on the command-line (#581)
Browse files Browse the repository at this point in the history
Closes #543
  • Loading branch information
nex3 authored Feb 1, 2019
1 parent cd3b82e commit b102dc4
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 12 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
2 changes: 1 addition & 1 deletion lib/src/executable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ main(List<String> args) async {
} on UsageException catch (error) {
print("${error.message}\n");
print("Usage: sass <input.scss> [output.css]\n"
" sass <input.scss>:<output.css> <input/>:<output/>\n");
" sass <input.scss>:<output.css> <input/>:<output/> <dir/>\n");
print(ExecutableOptions.usage);
exitCode = 64;
} catch (error, stackTrace) {
Expand Down
44 changes: 33 additions & 11 deletions lib/src/executable/options.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>();
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;
}
}

Expand All @@ -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;
Expand All @@ -286,6 +302,14 @@ class ExecutableOptions {
var sourcesToDestinations = p.PathMap<String>();
var sourceDirectoriesToDestinations = p.PathMap<String>();
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++) {
Expand All @@ -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;
Expand Down
40 changes: 40 additions & 0 deletions test/cli/shared/colon_args.dart
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,23 @@ void sharedTests(Future<TestProcess> runSass(Iterable<String> 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:"),
Expand Down Expand Up @@ -250,6 +267,29 @@ void sharedTests(Future<TestProcess> runSass(Iterable<String> 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 {
Expand Down

0 comments on commit b102dc4

Please sign in to comment.