Examples + basic builder

This commit is contained in:
Tobe O 2019-03-23 14:49:37 -04:00
parent 4cbfd3c251
commit f34f28bb7b
14 changed files with 2706 additions and 1 deletions

10
jael_web/build.yaml Normal file
View file

@ -0,0 +1,10 @@
builders:
jael_web:
import: "package:jael_web/builder.dart"
builder_factories:
- jaelComponentBuilder
build_extensions:
.dart:
- .jael_web_cmp.g.part
auto_apply: root_package
applies_builders: ["source_gen|combining_builder", "source_gen|part_cleanup"]

View file

@ -1,3 +1,30 @@
import 'package:jael_web/jael_web.dart'; import 'package:jael_web/jael_web.dart';
import 'package:jael_web/elements.dart';
part 'main.g.dart';
void main() {} @Dsx(template: '''
<div>
<h1>Hello, Jael!</h1>
<i>Current time: {now}</i>
</div>
''')
class Hello extends Component with _HelloJaelTemplate {
DateTime get now => DateTime.now();
}
// Could also have been:
class Hello2 extends Component {
DateTime get now => DateTime.now();
@override
DomNode render() {
return div(c: [
h1(c: [
text('Hello, Jael!'),
]),
i(c: [
text('Current time: $now'),
]),
]);
}
}

View file

@ -0,0 +1,18 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'main.dart';
// **************************************************************************
// JaelComponentGenerator
// **************************************************************************
abstract class _HelloJaelTemplate implements Component<dynamic> {
DateTime get now;
@override
DomNode render() {
return h('div', {}, [
h('h1', {}, [text('Hello, Jael!')]),
h('i', {}, [text('Current time: '), text(now.toString())])
]);
}
}

View file

@ -0,0 +1,32 @@
import 'dart:async';
import 'package:jael_web/jael_web.dart';
part 'stateful.g.dart';
void main() {}
class _AppState {
final int ticks;
_AppState({this.ticks});
_AppState copyWith({int ticks}) {
return _AppState(ticks: ticks ?? this.ticks);
}
}
@Dsx(template: '<div>Tick count: {state.ticks}</div>')
class StatefulApp extends Component<_AppState> with _StatefulAppJaelTemplate {
Timer _timer;
StatefulApp() {
state =_AppState(ticks: 0);
_timer = Timer.periodic(Duration(seconds: 1), (t) {
setState(state.copyWith(ticks: t.tick));
});
}
@override
void beforeDestroy() {
_timer.cancel();
}
}

View file

@ -0,0 +1,15 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'stateful.dart';
// **************************************************************************
// JaelComponentGenerator
// **************************************************************************
abstract class _StatefulAppJaelTemplate implements Component<_AppState> {
Timer get _timer;
@override
DomNode render() {
return h('div', {}, [text('Tick count: '), text(state.ticks.toString())]);
}
}

View file

@ -0,0 +1 @@
export 'src/builder/builder.dart';

View file

@ -0,0 +1 @@
export 'src/elements.dart';

View file

@ -3,3 +3,4 @@ export 'src/component.dart';
export 'src/dom_builder.dart'; export 'src/dom_builder.dart';
export 'src/dom_node.dart'; export 'src/dom_node.dart';
export 'src/fn.dart'; export 'src/fn.dart';
export 'src/jael_component.dart';

View file

@ -0,0 +1,123 @@
import 'dart:async';
import 'package:analyzer/dart/element/element.dart';
import 'package:build/build.dart';
import 'package:code_builder/code_builder.dart';
import 'package:jael/jael.dart' as jael;
import 'package:jael_preprocessor/jael_preprocessor.dart' as jael;
import 'package:jael_web/jael_web.dart';
import 'package:path/path.dart' as p;
import 'package:source_gen/source_gen.dart';
import 'util.dart';
Builder jaelComponentBuilder(_) {
return SharedPartBuilder([JaelComponentGenerator()], 'jael_web_cmp');
}
class JaelComponentGenerator extends GeneratorForAnnotation<Jael> {
@override
Future<String> generateForAnnotatedElement(
Element element, ConstantReader annotation, BuildStep buildStep) async {
if (element is ClassElement) {
// Load the template
String templateString;
var inputId = buildStep.inputId;
var ann = Jael(
template: annotation.peek('template')?.stringValue,
templateUrl: annotation.peek('templateUrl')?.stringValue,
asDsx: annotation.peek('asDsx')?.boolValue ?? false,
);
if (ann.template == null && ann.templateUrl == null) {
throw 'Both `template` and `templateUrl` cannot be null.';
}
if (ann.template != null)
templateString = ann.template;
else {
var dir = p.dirname(inputId.path);
var assetId = AssetId(inputId.package, p.join(dir, ann.templateUrl));
if (!await buildStep.canRead(assetId)) {
throw 'Cannot find template "${assetId.uri}"';
} else {
templateString = await buildStep.readAsString(assetId);
}
}
var fs = BuildFileSystem(buildStep, inputId.package);
var errors = <jael.JaelError>[];
var doc = await jael.parseDocument(templateString,
sourceUrl: inputId.uri, asDSX: ann.asDsx, onError: errors.add);
if (errors.isEmpty) {
doc = await jael.resolve(doc, fs.file(inputId.uri).parent,
onError: errors.add);
}
if (errors.isNotEmpty) {
errors.forEach(log.severe);
throw 'Jael processing finished with ${errors.length} error(s).';
}
// Generate a _XJaelTemplate mixin class
var clazz = Class((b) {
b
..abstract = true
..name = '_${element.name}JaelTemplate'
..implements.add(convertTypeReference(element.supertype));
// Add fields corresponding to each of the class's fields.
for (var field in element.fields) {
b.methods.add(Method((b) {
b
..name = field.name
..type = MethodType.getter
..returns = convertTypeReference(field.type);
}));
}
// Add a render() stub
b.methods.add(Method((b) {
b
..name = 'render'
..returns = refer('DomNode')
..annotations.add(refer('override'))
..body = Block((b) {
var result = compileElementChild(doc.root);
b.addExpression(result.returned);
});
}));
});
return clazz.accept(DartEmitter()).toString();
} else {
throw '@Jael() is only supported for classes.';
}
}
Expression compileElementChild(jael.ElementChild child) {
if (child is jael.TextNode || child is jael.Text) {
return refer('text').call([literalString(child.span.text)]);
} else if (child is jael.Interpolation) {
Expression expr = CodeExpression(Code(child.expression.span.text));
expr = expr.property('toString').call([]);
return refer('text').call([expr]);
} else if (child is jael.Element) {
// TODO: Handle strict resolution
var attrs = <String, Expression>{};
for (var attr in child.attributes) {
attrs[attr.name] = attr.value == null
? literalTrue
: CodeExpression(Code(attr.value.span.text));
}
return refer('h').call([
literalString(child.tagName.name),
literalMap(attrs),
literalList(child.children.map(compileElementChild)),
]);
// return refer(child.tagName.name).newInstance([]);
} else {
throw 'Unsupported: $child';
}
}
}

View file

@ -0,0 +1,375 @@
import 'dart:async';
import 'dart:convert';
import 'package:analyzer/dart/element/type.dart';
import 'package:build/build.dart';
import 'package:code_builder/code_builder.dart';
import 'package:file/file.dart';
import 'package:path/src/context.dart';
/// Converts a [DartType] to a [TypeReference].
TypeReference convertTypeReference(DartType t) {
return new TypeReference((b) {
b..symbol = t.name;
if (t is InterfaceType) {
b.types.addAll(t.typeArguments.map(convertTypeReference));
}
});
}
UnsupportedError _unsupported() =>
UnsupportedError('Not support in R/O build file system.');
class BuildFileSystem extends FileSystem {
final AssetReader reader;
final String package;
Context _path = Context();
BuildFileSystem(this.reader, this.package);
Context get path => _path;
@override
Directory get currentDirectory {
return BuildSystemDirectory(this, reader, package, _path.current);
}
set currentDirectory(value) {
if (value is Directory) {
_path = Context(current: value.path);
} else if (value is String) {
_path = Context(current: value);
} else {
throw ArgumentError();
}
}
@override
Directory directory(path) {
String p;
if (path is String)
p = path;
else if (path is Uri)
p = p.toString();
else if (path is FileSystemEntity)
p = path.path;
else
throw ArgumentError();
return BuildSystemDirectory(this, reader, package, p);
}
@override
File file(path) {
String p;
if (path is String)
p = path;
else if (path is Uri)
p = p.toString();
else if (path is FileSystemEntity)
p = path.path;
else
throw ArgumentError();
return BuildSystemFile(this, reader, package, p);
}
@override
Future<bool> identical(String path1, String path2) => throw _unsupported();
@override
bool identicalSync(String path1, String path2) => throw _unsupported();
@override
bool get isWatchSupported => false;
@override
Link link(path) => throw _unsupported();
@override
Future<FileStat> stat(String path) => throw _unsupported();
@override
FileStat statSync(String path) => throw _unsupported();
@override
Directory get systemTempDirectory => throw _unsupported();
@override
Future<FileSystemEntityType> type(String path, {bool followLinks = true}) =>
throw _unsupported();
@override
FileSystemEntityType typeSync(String path, {bool followLinks = true}) =>
throw _unsupported();
}
class BuildSystemFile extends File {
final BuildFileSystem fileSystem;
final AssetReader reader;
final String package;
final String path;
BuildSystemFile(this.fileSystem, this.reader, this.package, this.path);
Uri get uri => fileSystem.path.toUri(path);
@override
File get absolute => this;
@override
String get basename => fileSystem.path.basename(path);
@override
Future<File> copy(String newPath) => throw _unsupported();
@override
File copySync(String newPath) => throw _unsupported();
@override
Future<File> create({bool recursive = false}) => throw _unsupported();
@override
void createSync({bool recursive = false}) => throw _unsupported();
@override
Future<FileSystemEntity> delete({bool recursive = false}) =>
throw _unsupported();
@override
void deleteSync({bool recursive = false}) => throw _unsupported();
@override
String get dirname => fileSystem.path.dirname(path);
@override
Future<bool> exists() => throw _unsupported();
@override
bool existsSync() => throw _unsupported();
@override
bool get isAbsolute => true;
@override
Future<DateTime> lastAccessed() => throw _unsupported();
@override
DateTime lastAccessedSync() => throw _unsupported();
@override
Future<DateTime> lastModified() => throw _unsupported();
@override
DateTime lastModifiedSync() => throw _unsupported();
@override
Future<int> length() => throw _unsupported();
@override
int lengthSync() => throw _unsupported();
@override
Future<RandomAccessFile> open({FileMode mode = FileMode.read}) =>
throw _unsupported();
@override
Stream<List<int>> openRead([int start, int end]) => throw _unsupported();
@override
RandomAccessFile openSync({FileMode mode = FileMode.read}) =>
throw _unsupported();
@override
IOSink openWrite(
{FileMode mode = FileMode.write, Encoding encoding = utf8}) =>
throw _unsupported();
@override
Directory get parent =>
BuildSystemDirectory(fileSystem, reader, package, fileSystem.path.dirname(path));
@override
Future<List<int>> readAsBytes() {
var assetId = AssetId(package, path);
return reader.readAsBytes(assetId);
}
@override
List<int> readAsBytesSync() => throw _unsupported();
@override
Future<List<String>> readAsLines({Encoding encoding = utf8}) =>
throw _unsupported();
@override
List<String> readAsLinesSync({Encoding encoding = utf8}) =>
throw _unsupported();
@override
Future<String> readAsString({Encoding encoding = utf8}) {
var assetId = AssetId(package, path);
return reader.readAsString(assetId);
}
@override
String readAsStringSync({Encoding encoding = utf8}) => throw _unsupported();
@override
Future<File> rename(String newPath) => throw _unsupported();
@override
File renameSync(String newPath) => throw _unsupported();
@override
Future<String> resolveSymbolicLinks() => throw _unsupported();
@override
String resolveSymbolicLinksSync() => throw _unsupported();
@override
Future setLastAccessed(DateTime time) => throw _unsupported();
@override
void setLastAccessedSync(DateTime time) => throw _unsupported();
@override
Future setLastModified(DateTime time) => throw _unsupported();
@override
void setLastModifiedSync(DateTime time) => throw _unsupported();
@override
Future<FileStat> stat() => throw _unsupported();
@override
FileStat statSync() => throw _unsupported();
@override
Stream<FileSystemEvent> watch(
{int events = FileSystemEvent.all, bool recursive = false}) =>
throw _unsupported();
@override
Future<File> writeAsBytes(List<int> bytes,
{FileMode mode = FileMode.write, bool flush = false}) =>
throw _unsupported();
@override
void writeAsBytesSync(List<int> bytes,
{FileMode mode = FileMode.write, bool flush = false}) =>
throw _unsupported();
@override
Future<File> writeAsString(String contents,
{FileMode mode = FileMode.write,
Encoding encoding = utf8,
bool flush = false}) =>
throw _unsupported();
@override
void writeAsStringSync(String contents,
{FileMode mode = FileMode.write,
Encoding encoding = utf8,
bool flush = false}) =>
throw _unsupported();
}
class BuildSystemDirectory extends Directory {
final BuildFileSystem fileSystem;
final AssetReader reader;
final String package;
final String path;
BuildSystemDirectory(this.fileSystem, this.reader, this.package, this.path);
@override
Directory get absolute => this;
@override
String get basename => fileSystem.path.basename(path);
@override
Directory childDirectory(String basename) {
return BuildSystemDirectory(
fileSystem, reader, package, fileSystem.path.join(path, basename));
}
@override
File childFile(String basename) {
return BuildSystemFile(
fileSystem, reader, package, fileSystem.path.join(path, basename));
}
@override
Link childLink(String basename) => throw _unsupported();
@override
Future<Directory> create({bool recursive = false}) => throw _unsupported();
@override
void createSync({bool recursive = false}) => throw _unsupported();
@override
Future<Directory> createTemp([String prefix]) => throw _unsupported();
@override
Directory createTempSync([String prefix]) => throw _unsupported();
@override
Future<FileSystemEntity> delete({bool recursive = false}) =>
throw _unsupported();
@override
void deleteSync({bool recursive = false}) => throw _unsupported();
@override
String get dirname => fileSystem.path.dirname(path);
@override
Future<bool> exists() => throw _unsupported();
@override
bool existsSync() => throw _unsupported();
@override
bool get isAbsolute => true;
@override
Stream<FileSystemEntity> list(
{bool recursive = false, bool followLinks = true}) =>
throw _unsupported();
@override
List<FileSystemEntity> listSync(
{bool recursive = false, bool followLinks = true}) =>
throw _unsupported();
@override
Directory get parent {
return BuildSystemDirectory(
fileSystem, reader, package, fileSystem.path.dirname(path));
}
@override
Future<Directory> rename(String newPath) => throw _unsupported();
@override
Directory renameSync(String newPath) => throw _unsupported();
@override
Future<String> resolveSymbolicLinks() => throw _unsupported();
@override
String resolveSymbolicLinksSync() => throw _unsupported();
@override
Future<FileStat> stat() => throw _unsupported();
@override
FileStat statSync() => throw _unsupported();
@override
Uri get uri => fileSystem.path.toUri(path);
@override
Stream<FileSystemEvent> watch(
{int events = FileSystemEvent.all, bool recursive = false}) =>
throw _unsupported();
}

View file

@ -6,3 +6,48 @@ abstract class BuilderNode extends DomNode {
void destroy<T>(DomBuilderElement<T> el); void destroy<T>(DomBuilderElement<T> el);
} }
DomNode h(String tagName,
[Map<String, dynamic> props = const {},
Iterable<DomNode> children = const []]) {
return _H(tagName, props, children);
}
DomNode text(String value) => _Text(value);
class _Text extends BuilderNode {
final String text;
_Text(this.text);
@override
DomBuilderElement<T> build<T>(DomBuilder<T> dom) {
dom.text(text);
// TODO: implement build
return null;
}
@override
void destroy<T>(DomBuilderElement<T> el) {
// TODO: implement destroy
}
}
class _H extends BuilderNode {
final String tagName;
final Map<String, dynamic> props;
final Iterable<DomNode> children;
_H(this.tagName, this.props, this.children);
@override
DomBuilderElement<T> build<T>(DomBuilder<T> dom) {
// TODO: implement build
return null;
}
@override
void destroy<T>(DomBuilderElement<T> el) {
// TODO: implement destroy
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,19 @@
/// A annotation for components that source-gen their `render()` methods.
class Jael {
/// The raw template.
final String template;
/// The path to a [template].
final String templateUrl;
/// Whether to parse the [template] as `DSX`.
final bool asDsx;
const Jael({this.template, this.templateUrl, this.asDsx});
}
/// Shorthand for enabling `DSX` syntax when using a [Jael] annotation.
class Dsx extends Jael {
const Dsx({String template, String templateUrl})
: super(template: template, templateUrl: templateUrl, asDsx: true);
}

View file

@ -7,6 +7,8 @@ dependencies:
build: ^1.0.0 build: ^1.0.0
build_config: ^0.3.0 build_config: ^0.3.0
code_builder: ^3.0.0 code_builder: ^3.0.0
jael: ^2.0.0
jael_preprocessor: ^2.0.0
source_gen: ^0.9.0 source_gen: ^0.9.0
dev_dependencies: dev_dependencies:
build_runner: ^1.0.0 build_runner: ^1.0.0