Skip to content

Commit

Permalink
Support compiling entire directories at once
Browse files Browse the repository at this point in the history
Partially addresses #264
  • Loading branch information
nex3 committed May 20, 2018
1 parent 1450c24 commit cfe650b
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 8 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
`sass input.scss:output.css`. Note that unlike Ruby Sass, this *always*
compiles files by default regardless of when they were modified.

This syntax also supports compiling entire directories at once. For example,
`sass templates/stylesheets:public/css` compiles all non-partial Sass files
in `templates/stylesheets` to CSS files in `public/css`.

## 1.3.2

* Add support for `@elseif` as an alias of `@else if`. This is not an
Expand Down
4 changes: 2 additions & 2 deletions lib/src/executable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ main(List<String> args) async {
}
} on UsageException catch (error) {
print("${error.message}\n");
print("Usage: sass <input> [output]\n"
" sass <input>:<output> <input>:<output>\n");
print("Usage: sass <input.scss> [output.css]\n"
" sass <input.scss>:<output.css> <input/>:<output/>\n");
print(ExecutableOptions.usage);
exitCode = 64;
} catch (error, stackTrace) {
Expand Down
36 changes: 32 additions & 4 deletions lib/src/executable_options.dart
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,10 @@ class ExecutableOptions {

if (stdin) _fail('--stdin may not be used with ":" arguments.');

// Track [seen] separately from `sourcesToDestinations.keys` because we want
// to report errors for sources as users entered them, rather than after
// directories have been resolved.
var seen = new Set<String>();
var sourcesToDestinations = <String, String>{};
for (var argument in _options.rest) {
var components = argument.split(":");
Expand All @@ -186,19 +190,43 @@ class ExecutableOptions {
}
assert(components.length == 2);

var source = components.first == '-' ? null : components.first;
if (sourcesToDestinations.containsKey(source)) {
_fail('Duplicate source "${components.first}".');
var source = components.first;
var destination = components.last;
if (!seen.add(source)) {
_fail('Duplicate source "${source}".');
}

sourcesToDestinations[source] = components.last;
if (source == '-') {
sourcesToDestinations[null] = destination;
} else if (dirExists(source)) {
sourcesToDestinations.addAll(_listSourceDirectory(source, destination));
} else {
sourcesToDestinations[source] = destination;
}
}
_sourcesToDestinations = new Map.unmodifiable(sourcesToDestinations);
return _sourcesToDestinations;
}

Map<String, String> _sourcesToDestinations;

/// Returns the sub-map of [sourcesToDestinations] for the given [source] and
/// [destination] directories.
Map<String, String> _listSourceDirectory(String source, String destination) {
var map = <String, String>{};
for (var path in listDir(source)) {
var basename = p.basename(path);
if (basename.startsWith("_")) continue;

var extension = p.extension(path);
if (extension != ".scss" && extension != ".sass") continue;

map[path] = p.join(
destination, p.setExtension(p.relative(path, from: source), '.css'));
}
return map;
}

/// Whether to emit a source map file.
bool get emitSourceMap {
if (!(_options['source-map'] as bool)) {
Expand Down
4 changes: 4 additions & 0 deletions lib/src/io/interface.dart
Original file line number Diff line number Diff line change
Expand Up @@ -65,5 +65,9 @@ bool dirExists(String path) => null;
/// necessary.
void ensureDir(String path) => null;

/// Recursively lists the files (not sub-directories) of the directory at
/// [path].
Iterable<String> listDir(String path) => null;

/// Gets and sets the exit code that the process will use when it exits.
int exitCode;
36 changes: 34 additions & 2 deletions lib/src/io/node.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ class _FS {
external void writeFileSync(String path, String data);
external bool existsSync(String path);
external void mkdirSync(String path);
external _Stat statSync(String path);
external List<String> readdirSync(String path);
}

@JS()
class _Stat {
external bool isFile();
external bool isDirectory();
}

@JS()
Expand Down Expand Up @@ -135,9 +143,25 @@ String _cleanErrorMessage(_SystemError error) {
error.message.length - ", ${error.syscall} '${error.path}'".length);
}

bool fileExists(String path) => _fs.existsSync(path);
bool fileExists(String path) {
try {
return _fs.statSync(path).isFile();
} catch (error) {
var systemError = error as _SystemError;
if (systemError.code == 'ENOENT') return false;
rethrow;
}
}

bool dirExists(String path) => _fs.existsSync(path);
bool dirExists(String path) {
try {
return _fs.statSync(path).isDirectory();
} catch (error) {
var systemError = error as _SystemError;
if (systemError.code == 'ENOENT') return false;
rethrow;
}
}

void ensureDir(String path) {
return _systemErrorToFileSystemException(() {
Expand All @@ -153,6 +177,14 @@ void ensureDir(String path) {
});
}

Iterable<String> listDir(String path) {
Iterable<String> list(String path) => _fs
.readdirSync(path)
.expand((path) => dirExists(path) ? listDir(path) : [path]);

return _systemErrorToFileSystemException(() => list(path));
}

/// Runs callback and converts any [_SystemError]s it throws into
/// [FileSystemException]s.
T _systemErrorToFileSystemException<T>(T callback()) {
Expand Down
5 changes: 5 additions & 0 deletions lib/src/io/vm.dart
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,8 @@ bool dirExists(String path) => new io.Directory(path).existsSync();

void ensureDir(String path) =>
new io.Directory(path).createSync(recursive: true);

Iterable<String> listDir(String path) => new io.Directory(path)
.listSync(recursive: true)
.where((entity) => entity is io.File)
.map((entity) => entity.path);
66 changes: 66 additions & 0 deletions test/cli_shared.dart
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,72 @@ void sharedTests(Future<TestProcess> runSass(Iterable<String> arguments)) {
await d.file("out2.css.map", contains("test2.scss")).validate();
});

group("with a directory argument", () {
test("compiles all the stylesheets in the directory", () 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:out"]);
expect(sass.stdout, emitsDone);
await sass.shouldExit(0);

await d.dir("out", [
d.file("test1.css", equalsIgnoringWhitespace("a { b: c; }")),
d.file("test2.css", equalsIgnoringWhitespace("x { y: z; }"))
]).validate();
});

test("creates subdirectories in the destination", () async {
await d.dir("in", [
d.dir("sub", [d.file("test.scss", "a {b: c}")])
]).create();

var sass = await runSass(["--no-source-map", "in:out"]);
expect(sass.stdout, emitsDone);
await sass.shouldExit(0);

await d.dir("out", [
d.dir("sub",
[d.file("test.css", equalsIgnoringWhitespace("a { b: c; }"))])
]).validate();
});

test("ignores partials", () async {
await d.dir("in", [
d.file("_fake.scss", "a {b:"),
d.file("real.scss", "x {y: z}")
]).create();

var sass = await runSass(["--no-source-map", "in:out"]);
expect(sass.stdout, emitsDone);
await sass.shouldExit(0);

await d.dir("out", [
d.file("real.css", equalsIgnoringWhitespace("x { y: z; }")),
d.nothing("fake.css"),
d.nothing("_fake.css")
]).validate();
});

test("ignores files without a Sass extension", () async {
await d.dir("in", [
d.file("fake.szss", "a {b:"),
d.file("real.scss", "x {y: z}")
]).create();

var sass = await runSass(["--no-source-map", "in:out"]);
expect(sass.stdout, emitsDone);
await sass.shouldExit(0);

await d.dir("out", [
d.file("real.css", equalsIgnoringWhitespace("x { y: z; }")),
d.nothing("fake.css")
]).validate();
});
});

group("reports all", () {
test("file-not-found errors", () async {
var sass =
Expand Down

0 comments on commit cfe650b

Please sign in to comment.