diff --git a/lib/sass.dart b/lib/sass.dart index 17703b23..f490d105 100644 --- a/lib/sass.dart +++ b/lib/sass.dart @@ -4,12 +4,15 @@ import 'dart:async'; +import 'src/callable.dart'; import 'src/compile.dart' as c; import 'src/exception.dart'; import 'src/importer.dart'; import 'src/sync_package_resolver.dart'; +export 'src/callable.dart' show Callable, AsyncCallable; export 'src/importer.dart'; +export 'src/value.dart' hide SassNull; /// Loads the Sass file at [path], compiles it to CSS, and returns the result. /// @@ -30,17 +33,23 @@ export 'src/importer.dart'; /// /// [SyncPackageResolver]: https://www.dartdocs.org/documentation/package_resolver/latest/package_resolver/SyncPackageResolver-class.html /// +/// Dart functions that can be called from Sass may be passed using [functions]. +/// Each [Callable] defines a top-level function that will be invoked when the +/// given name is called from Sass. +/// /// Throws a [SassException] if conversion fails. String compile(String path, {bool color: false, Iterable importers, Iterable loadPaths, - SyncPackageResolver packageResolver}) { + SyncPackageResolver packageResolver, + Iterable functions}) { var result = c.compile(path, color: color, importers: importers, loadPaths: loadPaths, - packageResolver: packageResolver); + packageResolver: packageResolver, + functions: functions); return result.css; } @@ -65,6 +74,10 @@ String compile(String path, /// /// [SyncPackageResolver]: https://www.dartdocs.org/documentation/package_resolver/latest/package_resolver/SyncPackageResolver-class.html /// +/// Dart functions that can be called from Sass may be passed using [functions]. +/// Each [Callable] defines a top-level function that will be invoked when the +/// given name is called from Sass. +/// /// The [url] indicates the location from which [source] was loaded. It may be a /// [String] or a [Uri]. If [importer] is passed, [url] must be passed as well /// and `importer.load(url)` should return `source`. @@ -74,16 +87,18 @@ String compileString(String source, {bool indented: false, bool color: false, Iterable importers, - Iterable loadPaths, SyncPackageResolver packageResolver, + Iterable loadPaths, + Iterable functions, Importer importer, url}) { var result = c.compileString(source, indented: indented, color: color, importers: importers, - loadPaths: loadPaths, packageResolver: packageResolver, + loadPaths: loadPaths, + functions: functions, importer: importer, url: url); return result.css; @@ -97,13 +112,15 @@ String compileString(String source, Future compileAsync(String path, {bool color: false, Iterable importers, + SyncPackageResolver packageResolver, Iterable loadPaths, - SyncPackageResolver packageResolver}) async { + Iterable functions}) async { var result = await c.compileAsync(path, color: color, importers: importers, loadPaths: loadPaths, - packageResolver: packageResolver); + packageResolver: packageResolver, + functions: functions); return result.css; } @@ -116,16 +133,18 @@ Future compileStringAsync(String source, {bool indented: false, bool color: false, Iterable importers, - Iterable loadPaths, SyncPackageResolver packageResolver, + Iterable loadPaths, + Iterable functions, AsyncImporter importer, url}) async { var result = await c.compileStringAsync(source, indented: indented, color: color, importers: importers, - loadPaths: loadPaths, packageResolver: packageResolver, + loadPaths: loadPaths, + functions: functions, importer: importer, url: url); return result.css; diff --git a/lib/src/callable.dart b/lib/src/callable.dart index 6b1d31c1..c1dd177a 100644 --- a/lib/src/callable.dart +++ b/lib/src/callable.dart @@ -3,6 +3,8 @@ // https://opensource.org/licenses/MIT. import 'callable/async.dart'; +import 'callable/built_in.dart'; +import 'value.dart'; export 'callable/async.dart'; export 'callable/async_built_in.dart'; @@ -17,4 +19,7 @@ export 'callable/user_defined.dart'; /// usable in asynchronous contexts. [Callable]s are usable with both the /// synchronous and asynchronous `compile()` functions, and as such should be /// used in preference to [AsyncCallable]s if possible. -abstract class Callable extends AsyncCallable {} +abstract class Callable extends AsyncCallable { + factory Callable(String name, String arguments, + Value callback(List arguments)) = BuiltInCallable; +} diff --git a/lib/src/callable/async.dart b/lib/src/callable/async.dart index 49a9c7ee..b91e00c6 100644 --- a/lib/src/callable/async.dart +++ b/lib/src/callable/async.dart @@ -2,6 +2,11 @@ // MIT-style license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. +import 'dart:async'; + +import '../value.dart'; +import 'async_built_in.dart'; + /// An interface for objects, such as functions and mixins, that can be invoked /// from Sass by passing in arguments. /// @@ -11,4 +16,7 @@ abstract class AsyncCallable { /// The callable's name. String get name; + + factory AsyncCallable(String name, String arguments, + FutureOr callback(List arguments)) = AsyncBuiltInCallable; } diff --git a/lib/src/compile.dart b/lib/src/compile.dart index 6b8247c3..68eb723d 100644 --- a/lib/src/compile.dart +++ b/lib/src/compile.dart @@ -5,6 +5,7 @@ import 'dart:async'; import 'ast/sass.dart'; +import 'callable.dart'; import 'importer.dart'; import 'importer/node.dart'; import 'io.dart'; @@ -23,6 +24,7 @@ CompileResult compile(String path, NodeImporter nodeImporter, SyncPackageResolver packageResolver, Iterable loadPaths, + Iterable functions, OutputStyle style, bool useSpaces: true, int indentWidth, @@ -30,6 +32,7 @@ CompileResult compile(String path, compileString(readFile(path), indented: indented ?? p.extension(path) == '.sass', color: color, + functions: functions, importers: importers, nodeImporter: nodeImporter, packageResolver: packageResolver, @@ -51,6 +54,7 @@ CompileResult compileString(String source, SyncPackageResolver packageResolver, Iterable loadPaths, Importer importer, + Iterable functions, OutputStyle style, bool useSpaces: true, int indentWidth, @@ -65,6 +69,7 @@ CompileResult compileString(String source, ..addAll(_toImporters(loadPaths, packageResolver)), nodeImporter: nodeImporter, importer: importer, + functions: functions, color: color); var css = serialize(evaluateResult.stylesheet, style: style, @@ -84,6 +89,7 @@ Future compileAsync(String path, NodeImporter nodeImporter, SyncPackageResolver packageResolver, Iterable loadPaths, + Iterable functions, OutputStyle style, bool useSpaces: true, int indentWidth, @@ -96,6 +102,7 @@ Future compileAsync(String path, packageResolver: packageResolver, loadPaths: loadPaths, importer: new FilesystemImporter('.'), + functions: functions, style: style, useSpaces: useSpaces, indentWidth: indentWidth, @@ -112,6 +119,7 @@ Future compileStringAsync(String source, SyncPackageResolver packageResolver, Iterable loadPaths, AsyncImporter importer, + Iterable functions, OutputStyle style, bool useSpaces: true, int indentWidth, @@ -126,6 +134,7 @@ Future compileStringAsync(String source, ..addAll(_toImporters(loadPaths, packageResolver)), nodeImporter: nodeImporter, importer: importer, + functions: functions, color: color); var css = serialize(evaluateResult.stylesheet, style: style, diff --git a/lib/src/visitor/async_evaluate.dart b/lib/src/visitor/async_evaluate.dart index bb2274e5..d046291d 100644 --- a/lib/src/visitor/async_evaluate.dart +++ b/lib/src/visitor/async_evaluate.dart @@ -56,13 +56,13 @@ Future evaluateAsync(Stylesheet stylesheet, {Iterable importers, NodeImporter nodeImporter, AsyncImporter importer, - AsyncEnvironment environment, + Iterable functions, bool color: false}) => new _EvaluateVisitor( importers: importers, nodeImporter: nodeImporter, importer: importer, - environment: environment, + functions: functions, color: color) .run(stylesheet); @@ -82,7 +82,7 @@ class _EvaluateVisitor final bool _color; /// The current lexical environment. - AsyncEnvironment _environment; + var _environment = new AsyncEnvironment(); /// The importer that's currently being used to resolve relative imports. /// @@ -168,12 +168,11 @@ class _EvaluateVisitor {Iterable importers, NodeImporter nodeImporter, AsyncImporter importer, - AsyncEnvironment environment, + Iterable functions, bool color: false}) : _importers = importers == null ? const [] : importers.toList(), _importer = importer ?? Importer.noOp, _nodeImporter = nodeImporter, - _environment = environment ?? new AsyncEnvironment(), _color = color { _environment.setFunction( new BuiltInCallable("global-variable-exists", r"$name", (arguments) { @@ -258,6 +257,10 @@ class _EvaluateVisitor "This is probably caused by a bug in a Sass plugin."); } })); + + for (var function in functions ?? const []) { + _environment.setFunction(function); + } } Future run(Stylesheet node) async { @@ -1394,8 +1397,19 @@ class _EvaluateVisitor positional.add(argumentList); } - var result = await _addExceptionSpanAsync( - span, () async => await callback(positional)); + Value result; + try { + result = await callback(positional); + if (result == null) throw "Custom functions may not return Dart's null."; + } catch (error, stackTrace) { + String message; + try { + message = error.message as String; + } catch (_) { + message = error.toString(); + } + throw _exception(message, span); + } _callableSpan = oldCallableSpan; if (argumentList == null) return result; diff --git a/lib/src/visitor/evaluate.dart b/lib/src/visitor/evaluate.dart index fd273661..9bf9703b 100644 --- a/lib/src/visitor/evaluate.dart +++ b/lib/src/visitor/evaluate.dart @@ -5,7 +5,7 @@ // DO NOT EDIT. This file was generated from async_evaluate.dart. // See tool/synchronize.dart for details. // -// Checksum: d443af264c677b3e99b5b32511898d92b9b41291 +// Checksum: 4d48952f8cd737f998fc1f67e801f4ea0ab69671 import 'dart:math' as math; @@ -60,13 +60,13 @@ EvaluateResult evaluate(Stylesheet stylesheet, {Iterable importers, NodeImporter nodeImporter, Importer importer, - Environment environment, + Iterable functions, bool color: false}) => new _EvaluateVisitor( importers: importers, nodeImporter: nodeImporter, importer: importer, - environment: environment, + functions: functions, color: color) .run(stylesheet); @@ -84,7 +84,7 @@ class _EvaluateVisitor final bool _color; /// The current lexical environment. - Environment _environment; + var _environment = new Environment(); /// The importer that's currently being used to resolve relative imports. /// @@ -170,12 +170,11 @@ class _EvaluateVisitor {Iterable importers, NodeImporter nodeImporter, Importer importer, - Environment environment, + Iterable functions, bool color: false}) : _importers = importers == null ? const [] : importers.toList(), _importer = importer ?? Importer.noOp, _nodeImporter = nodeImporter, - _environment = environment ?? new Environment(), _color = color { _environment.setFunction( new BuiltInCallable("global-variable-exists", r"$name", (arguments) { @@ -260,6 +259,10 @@ class _EvaluateVisitor "This is probably caused by a bug in a Sass plugin."); } })); + + for (var function in functions ?? const []) { + _environment.setFunction(function); + } } EvaluateResult run(Stylesheet node) { @@ -1372,7 +1375,19 @@ class _EvaluateVisitor positional.add(argumentList); } - var result = _addExceptionSpan(span, () => callback(positional)); + Value result; + try { + result = callback(positional); + if (result == null) throw "Custom functions may not return Dart's null."; + } catch (error, stackTrace) { + String message; + try { + message = error.message as String; + } catch (_) { + message = error.toString(); + } + throw _exception(message, span); + } _callableSpan = oldCallableSpan; if (argumentList == null) return result;