Expose an API for defining custom Dart functions

This commit is contained in:
Natalie Weizenbaum 2018-01-05 17:53:51 -08:00
parent aa3c765b10
commit 8988c3c7fa
6 changed files with 93 additions and 23 deletions

View File

@ -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<Importer> importers,
Iterable<String> loadPaths,
SyncPackageResolver packageResolver}) {
SyncPackageResolver packageResolver,
Iterable<Callable> 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<Importer> importers,
Iterable<String> loadPaths,
SyncPackageResolver packageResolver,
Iterable<String> loadPaths,
Iterable<Callable> 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<String> compileAsync(String path,
{bool color: false,
Iterable<AsyncImporter> importers,
SyncPackageResolver packageResolver,
Iterable<String> loadPaths,
SyncPackageResolver packageResolver}) async {
Iterable<AsyncCallable> 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<String> compileStringAsync(String source,
{bool indented: false,
bool color: false,
Iterable<AsyncImporter> importers,
Iterable<String> loadPaths,
SyncPackageResolver packageResolver,
Iterable<String> loadPaths,
Iterable<AsyncCallable> 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;

View File

@ -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<Value> arguments)) = BuiltInCallable;
}

View File

@ -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<Value> callback(List<Value> arguments)) = AsyncBuiltInCallable;
}

View File

@ -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<String> loadPaths,
Iterable<Callable> 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<String> loadPaths,
Importer importer,
Iterable<Callable> 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<CompileResult> compileAsync(String path,
NodeImporter nodeImporter,
SyncPackageResolver packageResolver,
Iterable<String> loadPaths,
Iterable<AsyncCallable> functions,
OutputStyle style,
bool useSpaces: true,
int indentWidth,
@ -96,6 +102,7 @@ Future<CompileResult> compileAsync(String path,
packageResolver: packageResolver,
loadPaths: loadPaths,
importer: new FilesystemImporter('.'),
functions: functions,
style: style,
useSpaces: useSpaces,
indentWidth: indentWidth,
@ -112,6 +119,7 @@ Future<CompileResult> compileStringAsync(String source,
SyncPackageResolver packageResolver,
Iterable<String> loadPaths,
AsyncImporter importer,
Iterable<AsyncCallable> functions,
OutputStyle style,
bool useSpaces: true,
int indentWidth,
@ -126,6 +134,7 @@ Future<CompileResult> compileStringAsync(String source,
..addAll(_toImporters(loadPaths, packageResolver)),
nodeImporter: nodeImporter,
importer: importer,
functions: functions,
color: color);
var css = serialize(evaluateResult.stylesheet,
style: style,

View File

@ -56,13 +56,13 @@ Future<EvaluateResult> evaluateAsync(Stylesheet stylesheet,
{Iterable<AsyncImporter> importers,
NodeImporter nodeImporter,
AsyncImporter importer,
AsyncEnvironment environment,
Iterable<AsyncCallable> 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<AsyncImporter> importers,
NodeImporter nodeImporter,
AsyncImporter importer,
AsyncEnvironment environment,
Iterable<AsyncCallable> 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 <AsyncCallable>[]) {
_environment.setFunction(function);
}
}
Future<EvaluateResult> 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;

View File

@ -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<Importer> importers,
NodeImporter nodeImporter,
Importer importer,
Environment environment,
Iterable<Callable> 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<Importer> importers,
NodeImporter nodeImporter,
Importer importer,
Environment environment,
Iterable<Callable> 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 <Callable>[]) {
_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;