mirror of
https://github.com/danog/dart-sass.git
synced 2024-11-27 12:44:42 +01:00
Support compiling entire directories at once (#324)
Partially addresses #264
This commit is contained in:
parent
0c9e3683c6
commit
d68acf9ac2
@ -6,6 +6,10 @@
|
|||||||
`sass input.scss:output.css`. Note that unlike Ruby Sass, this *always*
|
`sass input.scss:output.css`. Note that unlike Ruby Sass, this *always*
|
||||||
compiles files by default regardless of when they were modified.
|
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
|
## 1.3.2
|
||||||
|
|
||||||
* Add support for `@elseif` as an alias of `@else if`. This is not an
|
* Add support for `@elseif` as an alias of `@else if`. This is not an
|
||||||
|
@ -65,8 +65,8 @@ main(List<String> args) async {
|
|||||||
}
|
}
|
||||||
} on UsageException catch (error) {
|
} on UsageException catch (error) {
|
||||||
print("${error.message}\n");
|
print("${error.message}\n");
|
||||||
print("Usage: sass <input> [output]\n"
|
print("Usage: sass <input.scss> [output.css]\n"
|
||||||
" sass <input>:<output> <input>:<output>\n");
|
" sass <input.scss>:<output.css> <input/>:<output/>\n");
|
||||||
print(ExecutableOptions.usage);
|
print(ExecutableOptions.usage);
|
||||||
exitCode = 64;
|
exitCode = 64;
|
||||||
} catch (error, stackTrace) {
|
} catch (error, stackTrace) {
|
||||||
|
@ -178,6 +178,10 @@ class ExecutableOptions {
|
|||||||
|
|
||||||
if (stdin) _fail('--stdin may not be used with ":" arguments.');
|
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>{};
|
var sourcesToDestinations = <String, String>{};
|
||||||
for (var argument in _options.rest) {
|
for (var argument in _options.rest) {
|
||||||
var components = argument.split(":");
|
var components = argument.split(":");
|
||||||
@ -186,12 +190,19 @@ class ExecutableOptions {
|
|||||||
}
|
}
|
||||||
assert(components.length == 2);
|
assert(components.length == 2);
|
||||||
|
|
||||||
var source = components.first == '-' ? null : components.first;
|
var source = components.first;
|
||||||
if (sourcesToDestinations.containsKey(source)) {
|
var destination = components.last;
|
||||||
_fail('Duplicate source "${components.first}".');
|
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);
|
_sourcesToDestinations = new Map.unmodifiable(sourcesToDestinations);
|
||||||
return _sourcesToDestinations;
|
return _sourcesToDestinations;
|
||||||
@ -199,6 +210,23 @@ class ExecutableOptions {
|
|||||||
|
|
||||||
Map<String, String> _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.
|
/// Whether to emit a source map file.
|
||||||
bool get emitSourceMap {
|
bool get emitSourceMap {
|
||||||
if (!(_options['source-map'] as bool)) {
|
if (!(_options['source-map'] as bool)) {
|
||||||
|
@ -65,5 +65,9 @@ bool dirExists(String path) => null;
|
|||||||
/// necessary.
|
/// necessary.
|
||||||
void ensureDir(String path) => null;
|
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.
|
/// Gets and sets the exit code that the process will use when it exits.
|
||||||
int exitCode;
|
int exitCode;
|
||||||
|
@ -18,6 +18,14 @@ class _FS {
|
|||||||
external void writeFileSync(String path, String data);
|
external void writeFileSync(String path, String data);
|
||||||
external bool existsSync(String path);
|
external bool existsSync(String path);
|
||||||
external void mkdirSync(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()
|
@JS()
|
||||||
@ -135,9 +143,25 @@ String _cleanErrorMessage(_SystemError error) {
|
|||||||
error.message.length - ", ${error.syscall} '${error.path}'".length);
|
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) {
|
void ensureDir(String path) {
|
||||||
return _systemErrorToFileSystemException(() {
|
return _systemErrorToFileSystemException(() {
|
||||||
@ -153,6 +177,16 @@ void ensureDir(String path) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Iterable<String> listDir(String path) {
|
||||||
|
Iterable<String> list(String parent) =>
|
||||||
|
_fs.readdirSync(parent).expand((child) {
|
||||||
|
var path = p.join(parent, child);
|
||||||
|
return dirExists(path) ? listDir(path) : [path];
|
||||||
|
});
|
||||||
|
|
||||||
|
return _systemErrorToFileSystemException(() => list(path));
|
||||||
|
}
|
||||||
|
|
||||||
/// Runs callback and converts any [_SystemError]s it throws into
|
/// Runs callback and converts any [_SystemError]s it throws into
|
||||||
/// [FileSystemException]s.
|
/// [FileSystemException]s.
|
||||||
T _systemErrorToFileSystemException<T>(T callback()) {
|
T _systemErrorToFileSystemException<T>(T callback()) {
|
||||||
|
@ -56,3 +56,8 @@ bool dirExists(String path) => new io.Directory(path).existsSync();
|
|||||||
|
|
||||||
void ensureDir(String path) =>
|
void ensureDir(String path) =>
|
||||||
new io.Directory(path).createSync(recursive: true);
|
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);
|
||||||
|
@ -213,6 +213,72 @@ void sharedTests(Future<TestProcess> runSass(Iterable<String> arguments)) {
|
|||||||
await d.file("out2.css.map", contains("test2.scss")).validate();
|
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", () {
|
group("reports all", () {
|
||||||
test("file-not-found errors", () async {
|
test("file-not-found errors", () async {
|
||||||
var sass =
|
var sass =
|
||||||
|
Loading…
Reference in New Issue
Block a user