Pre-processor needs work
This commit is contained in:
parent
291563f93f
commit
2056d1eacc
11 changed files with 402 additions and 4 deletions
21
angel_jael/LICENSE
Normal file
21
angel_jael/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2017 The Angel Framework
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
6
jael.iml
6
jael.iml
|
@ -7,8 +7,12 @@
|
||||||
<excludeFolder url="file://$MODULE_DIR$/jael/.pub" />
|
<excludeFolder url="file://$MODULE_DIR$/jael/.pub" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/jael/build" />
|
<excludeFolder url="file://$MODULE_DIR$/jael/build" />
|
||||||
</content>
|
</content>
|
||||||
<content url="file://$MODULE_DIR$/jael_preprocessor" />
|
<content url="file://$MODULE_DIR$/jael_preprocessor">
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/jael_preprocessor/.pub" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/jael_preprocessor/build" />
|
||||||
|
</content>
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
<orderEntry type="library" name="Dart SDK" level="project" />
|
<orderEntry type="library" name="Dart SDK" level="project" />
|
||||||
|
<orderEntry type="library" name="Dart Packages" level="project" />
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
21
jael/LICENSE
Normal file
21
jael/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2017 The Angel Framework
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
|
@ -17,6 +17,7 @@ class TextNode extends ElementChild {
|
||||||
|
|
||||||
abstract class Element extends ElementChild {
|
abstract class Element extends ElementChild {
|
||||||
static const List<String> selfClosing = const [
|
static const List<String> selfClosing = const [
|
||||||
|
'include',
|
||||||
'base',
|
'base',
|
||||||
'basefont',
|
'basefont',
|
||||||
'frame',
|
'frame',
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
name: jael
|
name: jael
|
||||||
version: 1.0.0-alpha
|
version: 1.0.0-alpha+1
|
||||||
description: A simple server-side HTML templating engine for Dart.
|
description: A simple server-side HTML templating engine for Dart.
|
||||||
author: Tobe O <thosakwe@gmail.com>
|
author: Tobe O <thosakwe@gmail.com>
|
||||||
homepage:
|
homepage: https://github.com/angel-dart/jael/tree/master/jael
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=1.19.0"
|
sdk: ">=1.19.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
21
jael_preprocessor/LICENSE
Normal file
21
jael_preprocessor/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2017 The Angel Framework
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
201
jael_preprocessor/lib/jael_preprocessor.dart
Normal file
201
jael_preprocessor/lib/jael_preprocessor.dart
Normal file
|
@ -0,0 +1,201 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'package:file/file.dart';
|
||||||
|
import 'package:jael/jael.dart';
|
||||||
|
|
||||||
|
/// Expands all `block[name]` tags within the template, replacing them with the correct content.
|
||||||
|
Future<Document> resolve(Document document, Directory currentDirectory,
|
||||||
|
{void onError(JaelError error)}) async {
|
||||||
|
onError ?? (e) => throw e;
|
||||||
|
|
||||||
|
// Resolve all includes...
|
||||||
|
var includesResolved =
|
||||||
|
await resolveIncludes(document, currentDirectory, onError);
|
||||||
|
|
||||||
|
if (includesResolved.root.tagName.name != 'extend') return includesResolved;
|
||||||
|
|
||||||
|
var element = includesResolved.root;
|
||||||
|
var attr = element.attributes
|
||||||
|
.firstWhere((a) => a.name.name == 'src', orElse: () => null);
|
||||||
|
if (attr == null) {
|
||||||
|
onError(new JaelError(JaelErrorSeverity.warning,
|
||||||
|
'Missing "src" attribute in "extend" tag.', element.tagName.span));
|
||||||
|
return null;
|
||||||
|
} else if (attr.value is! StringLiteral) {
|
||||||
|
onError(new JaelError(
|
||||||
|
JaelErrorSeverity.warning,
|
||||||
|
'The "src" attribute in an "extend" tag must be a string literal.',
|
||||||
|
element.tagName.span));
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
var src = (attr.value as StringLiteral).value;
|
||||||
|
var file =
|
||||||
|
currentDirectory.fileSystem.file(currentDirectory.uri.resolve(src));
|
||||||
|
var contents = await file.readAsString();
|
||||||
|
var doc = parseDocument(contents, sourceUrl: file.uri, onError: onError);
|
||||||
|
var processed = await resolve(
|
||||||
|
doc, currentDirectory.fileSystem.directory(file.dirname),
|
||||||
|
onError: onError);
|
||||||
|
|
||||||
|
Map<String, Element> blocks = {};
|
||||||
|
var blockElements = element.children
|
||||||
|
.where((e) => e is Element && e.tagName.name == 'block');
|
||||||
|
|
||||||
|
for (Element blockElement in blockElements) {
|
||||||
|
var nameAttr = blockElement.attributes
|
||||||
|
.firstWhere((a) => a.name.name == 'name', orElse: () => null);
|
||||||
|
if (nameAttr == null) {
|
||||||
|
onError(new JaelError(JaelErrorSeverity.warning,
|
||||||
|
'Missing "name" attribute in "block" tag.', blockElement.span));
|
||||||
|
} else if (nameAttr.value is! StringLiteral) {
|
||||||
|
onError(new JaelError(
|
||||||
|
JaelErrorSeverity.warning,
|
||||||
|
'The "name" attribute in an "block" tag must be a string literal.',
|
||||||
|
nameAttr.span));
|
||||||
|
} else {
|
||||||
|
var name = (nameAttr.value as StringLiteral).value;
|
||||||
|
blocks[name] = blockElement;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var blocksExpanded =
|
||||||
|
await _expandBlocks(processed.root, blocks, currentDirectory, onError);
|
||||||
|
return new Document(document.doctype ?? processed.doctype, blocksExpanded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Element> _expandBlocks(Element element, Map<String, Element> blocks,
|
||||||
|
Directory currentDirectory, void onError(JaelError error)) async {
|
||||||
|
if (element is SelfClosingElement)
|
||||||
|
return element;
|
||||||
|
else if (element is RegularElement) {
|
||||||
|
if (element.children.isEmpty) return element;
|
||||||
|
|
||||||
|
List<ElementChild> expanded = [];
|
||||||
|
|
||||||
|
for (var child in element.children) {
|
||||||
|
if (child is Element) {
|
||||||
|
if (child is SelfClosingElement)
|
||||||
|
expanded.add(child);
|
||||||
|
else if (child is RegularElement) {
|
||||||
|
if (child.tagName.name != 'block') {
|
||||||
|
expanded.add(child);
|
||||||
|
} else {
|
||||||
|
var nameAttr =
|
||||||
|
child.attributes.firstWhere((a) => a.name.name == 'name', orElse: () => null);
|
||||||
|
if (nameAttr == null) {
|
||||||
|
onError(new JaelError(
|
||||||
|
JaelErrorSeverity.warning,
|
||||||
|
'Missing "name" attribute in "block" tag.',
|
||||||
|
child.span));
|
||||||
|
} else if (nameAttr.value is! StringLiteral) {
|
||||||
|
onError(new JaelError(
|
||||||
|
JaelErrorSeverity.warning,
|
||||||
|
'The "name" attribute in an "block" tag must be a string literal.',
|
||||||
|
nameAttr.span));
|
||||||
|
}
|
||||||
|
|
||||||
|
var name = (nameAttr.value as StringLiteral).value;
|
||||||
|
Iterable<ElementChild> children;
|
||||||
|
|
||||||
|
if (!blocks.containsKey(name)) {
|
||||||
|
children = child.children;
|
||||||
|
} else {
|
||||||
|
children = blocks[name].children;
|
||||||
|
}
|
||||||
|
|
||||||
|
expanded.addAll(children);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new UnsupportedError(
|
||||||
|
'Unsupported element type: ${element.runtimeType}');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
expanded.add(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve all includes...
|
||||||
|
expanded = await Future.wait(expanded.map((c) {
|
||||||
|
if (c is! Element) return new Future.value(c);
|
||||||
|
return _expandIncludes(c, currentDirectory, onError);
|
||||||
|
}));
|
||||||
|
|
||||||
|
return new RegularElement(
|
||||||
|
element.lt,
|
||||||
|
element.tagName,
|
||||||
|
element.attributes,
|
||||||
|
element.gt,
|
||||||
|
expanded,
|
||||||
|
element.lt2,
|
||||||
|
element.slash,
|
||||||
|
element.tagName2,
|
||||||
|
element.gt2);
|
||||||
|
} else {
|
||||||
|
throw new UnsupportedError(
|
||||||
|
'Unsupported element type: ${element.runtimeType}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Expands all `include[src]` tags within the template, and fills in the content of referenced files.
|
||||||
|
Future<Document> resolveIncludes(Document document, Directory currentDirectory,
|
||||||
|
void onError(JaelError error)) async {
|
||||||
|
return new Document(document.doctype,
|
||||||
|
await _expandIncludes(document.root, currentDirectory, onError));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Element> _expandIncludes(Element element, Directory currentDirectory,
|
||||||
|
void onError(JaelError error)) async {
|
||||||
|
if (element.tagName.name != 'include') {
|
||||||
|
if (element is SelfClosingElement)
|
||||||
|
return element;
|
||||||
|
else if (element is RegularElement) {
|
||||||
|
List<ElementChild> expanded = [];
|
||||||
|
|
||||||
|
for (var child in element.children) {
|
||||||
|
if (child is Element) {
|
||||||
|
expanded.add(await _expandIncludes(child, currentDirectory, onError));
|
||||||
|
} else {
|
||||||
|
expanded.add(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new RegularElement(
|
||||||
|
element.lt,
|
||||||
|
element.tagName,
|
||||||
|
element.attributes,
|
||||||
|
element.gt,
|
||||||
|
expanded,
|
||||||
|
element.lt2,
|
||||||
|
element.slash,
|
||||||
|
element.tagName2,
|
||||||
|
element.gt2);
|
||||||
|
} else {
|
||||||
|
throw new UnsupportedError(
|
||||||
|
'Unsupported element type: ${element.runtimeType}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var attr = element.attributes
|
||||||
|
.firstWhere((a) => a.name.name == 'src', orElse: () => null);
|
||||||
|
if (attr == null) {
|
||||||
|
onError(new JaelError(JaelErrorSeverity.warning,
|
||||||
|
'Missing "src" attribute in "include" tag.', element.tagName.span));
|
||||||
|
return null;
|
||||||
|
} else if (attr.value is! StringLiteral) {
|
||||||
|
onError(new JaelError(
|
||||||
|
JaelErrorSeverity.warning,
|
||||||
|
'The "src" attribute in an "include" tag must be a string literal.',
|
||||||
|
element.tagName.span));
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
var src = (attr.value as StringLiteral).value;
|
||||||
|
var file =
|
||||||
|
currentDirectory.fileSystem.file(currentDirectory.uri.resolve(src));
|
||||||
|
var contents = await file.readAsString();
|
||||||
|
var doc = parseDocument(contents, sourceUrl: file.uri, onError: onError);
|
||||||
|
var processed = await resolve(
|
||||||
|
doc, currentDirectory.fileSystem.directory(file.dirname),
|
||||||
|
onError: onError);
|
||||||
|
return processed.root;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,10 @@
|
||||||
name: jael_preprocessor
|
name: jael_preprocessor
|
||||||
|
version: 1.0.0-alpha
|
||||||
|
description: A pre-processor for resolving blocks and includes within Jael templates.
|
||||||
|
author: Tobe O <thosakwe@gmail.com>
|
||||||
|
homepage: https://github.com/angel-dart/jael/tree/master/jael_processor
|
||||||
|
environment:
|
||||||
|
sdk: ">=1.19.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
file: ^2.0.0
|
file: ^2.0.0
|
||||||
jael: ^1.0.0-alpha
|
jael: ^1.0.0-alpha
|
||||||
|
|
74
jael_preprocessor/test/block_test.dart
Normal file
74
jael_preprocessor/test/block_test.dart
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
import 'package:code_buffer/code_buffer.dart';
|
||||||
|
import 'package:file/file.dart';
|
||||||
|
import 'package:file/memory.dart';
|
||||||
|
import 'package:jael/jael.dart' as jael;
|
||||||
|
import 'package:jael_preprocessor/jael_preprocessor.dart' as jael;
|
||||||
|
import 'package:symbol_table/symbol_table.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
main() {
|
||||||
|
FileSystem fileSystem;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
fileSystem = new MemoryFileSystem();
|
||||||
|
|
||||||
|
// a.jl
|
||||||
|
fileSystem.file('a.jl').writeAsStringSync('<b>a.jl</b>');
|
||||||
|
|
||||||
|
// b.jl
|
||||||
|
fileSystem.file('b.jl').writeAsStringSync(
|
||||||
|
'<i><include src="a.jl"><block name="greeting"><p>Hello</p></block></i>');
|
||||||
|
|
||||||
|
// c.jl
|
||||||
|
fileSystem.file('c.jl').writeAsStringSync(
|
||||||
|
'<extend src="b.jl"><block name="greeting">Goodbye</block></extend>');
|
||||||
|
|
||||||
|
// d.jl
|
||||||
|
fileSystem.file('d.jl').writeAsStringSync(
|
||||||
|
'<extend src="c.jl"><block name="greeting">Saluton!</block></extend>');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('blocks are replaced or kept', () async {
|
||||||
|
var file = fileSystem.file('c.jl');
|
||||||
|
var original = jael.parseDocument(await file.readAsString(),
|
||||||
|
sourceUrl: file.uri, onError: (e) => throw e);
|
||||||
|
var processed = await jael.resolve(
|
||||||
|
original, fileSystem.directory(fileSystem.currentDirectory),
|
||||||
|
onError: (e) => throw e);
|
||||||
|
var buf = new CodeBuffer();
|
||||||
|
var scope = new SymbolTable();
|
||||||
|
const jael.Renderer().render(processed, buf, scope);
|
||||||
|
print(buf);
|
||||||
|
|
||||||
|
expect(buf.toString(), '''
|
||||||
|
<i>
|
||||||
|
<b>
|
||||||
|
a.jl
|
||||||
|
</b>
|
||||||
|
Goodbye
|
||||||
|
</i>
|
||||||
|
'''.trim());
|
||||||
|
});
|
||||||
|
|
||||||
|
test('blocks can be overwritten', () async {
|
||||||
|
var file = fileSystem.file('d.jl');
|
||||||
|
var original = jael.parseDocument(await file.readAsString(),
|
||||||
|
sourceUrl: file.uri, onError: (e) => throw e);
|
||||||
|
var processed = await jael.resolve(
|
||||||
|
original, fileSystem.directory(fileSystem.currentDirectory),
|
||||||
|
onError: (e) => throw e);
|
||||||
|
var buf = new CodeBuffer();
|
||||||
|
var scope = new SymbolTable();
|
||||||
|
const jael.Renderer().render(processed, buf, scope);
|
||||||
|
print(buf);
|
||||||
|
|
||||||
|
expect(buf.toString(), '''
|
||||||
|
<i>
|
||||||
|
<b>
|
||||||
|
a.jl
|
||||||
|
</b>
|
||||||
|
Saluton!
|
||||||
|
</i>
|
||||||
|
'''.trim());
|
||||||
|
});
|
||||||
|
}
|
49
jael_preprocessor/test/include_test.dart
Normal file
49
jael_preprocessor/test/include_test.dart
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
import 'package:code_buffer/code_buffer.dart';
|
||||||
|
import 'package:file/file.dart';
|
||||||
|
import 'package:file/memory.dart';
|
||||||
|
import 'package:jael/jael.dart' as jael;
|
||||||
|
import 'package:jael_preprocessor/jael_preprocessor.dart' as jael;
|
||||||
|
import 'package:symbol_table/symbol_table.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
main() {
|
||||||
|
FileSystem fileSystem;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
fileSystem = new MemoryFileSystem();
|
||||||
|
|
||||||
|
// a.jl
|
||||||
|
fileSystem.file('a.jl').writeAsStringSync('<b>a.jl</b>');
|
||||||
|
|
||||||
|
// b.jl
|
||||||
|
fileSystem.file('b.jl').writeAsStringSync('<i><include src="a.jl"></i>');
|
||||||
|
|
||||||
|
// c.jl
|
||||||
|
fileSystem.file('c.jl').writeAsStringSync('<u><include src="b.jl"></u>');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('includes are expanded', () async {
|
||||||
|
var file = fileSystem.file('c.jl');
|
||||||
|
var original = jael.parseDocument(await file.readAsString(),
|
||||||
|
sourceUrl: file.uri, onError: (e) => throw e);
|
||||||
|
var processed = await jael.resolveIncludes(original,
|
||||||
|
fileSystem.directory(fileSystem.currentDirectory), (e) => throw e);
|
||||||
|
var buf = new CodeBuffer();
|
||||||
|
var scope = new SymbolTable();
|
||||||
|
const jael.Renderer().render(processed, buf, scope);
|
||||||
|
print(buf);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
buf.toString(),
|
||||||
|
'''
|
||||||
|
<u>
|
||||||
|
<i>
|
||||||
|
<b>
|
||||||
|
a.jl
|
||||||
|
</b>
|
||||||
|
</i>
|
||||||
|
</u>
|
||||||
|
'''
|
||||||
|
.trim());
|
||||||
|
});
|
||||||
|
}
|
|
@ -1,3 +1,3 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
cd jael && pub get && pub run test
|
cd jael && pub get && pub run test
|
||||||
cd ../angel_jael && pub get && pub run test
|
cd ../jael_preprocessor/ && pub get && pub run test
|
||||||
|
|
Loading…
Reference in a new issue