refactor: refactoring common utils, routes > routing, db drivers
This commit is contained in:
parent
2258f301e8
commit
e25f672a40
336 changed files with 33106 additions and 49 deletions
|
@ -1,8 +1,11 @@
|
||||||
name: protevus_platform
|
name: protevus_platform
|
||||||
repository: https://github.com/protevus/platform
|
repository: https://github.com/protevus/platform
|
||||||
packages:
|
packages:
|
||||||
- apps/**
|
- common/**
|
||||||
|
- drivers/**
|
||||||
- packages/**
|
- packages/**
|
||||||
|
- incubation/**
|
||||||
|
- apps/**
|
||||||
- helpers/tools/**
|
- helpers/tools/**
|
||||||
- examples/**
|
- examples/**
|
||||||
|
|
||||||
|
|
69
common/body_parser/CHANGELOG.md
Normal file
69
common/body_parser/CHANGELOG.md
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
# Change Log
|
||||||
|
|
||||||
|
## 5.3.0
|
||||||
|
|
||||||
|
* Require Dart >= 3.3
|
||||||
|
* Updated `platform_http_server` to 4.4.0
|
||||||
|
* Updated `lints` to 4.0.0
|
||||||
|
|
||||||
|
## 5.2.0
|
||||||
|
|
||||||
|
* Updated `lints` to 3.0.0
|
||||||
|
* Updated `platform_http_server` to 4.2.0
|
||||||
|
|
||||||
|
## 5.1.0
|
||||||
|
|
||||||
|
* Updated `platform_http_server` to 4.1.1
|
||||||
|
* Updated `http` to 1.0.0
|
||||||
|
|
||||||
|
## 5.0.0
|
||||||
|
|
||||||
|
* Require Dart >= 3.0
|
||||||
|
|
||||||
|
## 5.0.0-beta.1
|
||||||
|
|
||||||
|
* Require Dart >= 3.0
|
||||||
|
* Updated `platform_http_server` to 4.0.0
|
||||||
|
|
||||||
|
## 4.0.1
|
||||||
|
|
||||||
|
* Updated `platform_http_server` to 3.0.0
|
||||||
|
|
||||||
|
## 4.0.0
|
||||||
|
|
||||||
|
* Require Dart >= 2.17
|
||||||
|
|
||||||
|
## 3.0.1
|
||||||
|
|
||||||
|
* Fixed boken license link
|
||||||
|
|
||||||
|
## 3.0.0
|
||||||
|
|
||||||
|
* Upgraded from `pendantic` to `lints` linter
|
||||||
|
* Published as `platform_body_parser` package
|
||||||
|
* Fixed linter warnings
|
||||||
|
|
||||||
|
## 2.1.1
|
||||||
|
|
||||||
|
* Fixed calling deprecated methods in unit test
|
||||||
|
|
||||||
|
## 2.1.0
|
||||||
|
|
||||||
|
* Replaced `http_server` with `platform_http_server`
|
||||||
|
|
||||||
|
## 2.0.1
|
||||||
|
|
||||||
|
* Fixed source code formating warning
|
||||||
|
* Updated README
|
||||||
|
|
||||||
|
## 2.0.0
|
||||||
|
|
||||||
|
* Migrated to support Dart SDK 2.12.x NNBD
|
||||||
|
|
||||||
|
## 1.1.1
|
||||||
|
|
||||||
|
* Dart 2 updates; should fix Angel in Travis.
|
||||||
|
|
||||||
|
## 1.1.0
|
||||||
|
|
||||||
|
* Add `parseBodyFromStream`
|
29
common/body_parser/LICENSE
Normal file
29
common/body_parser/LICENSE
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
BSD 3-Clause License
|
||||||
|
|
||||||
|
Copyright (c) 2021, dukefirehawk.com
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
3. Neither the name of the copyright holder nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
73
common/body_parser/README.md
Normal file
73
common/body_parser/README.md
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
# Belatuk Body Parser
|
||||||
|
|
||||||
|
![Pub Version (including pre-releases)](https://img.shields.io/pub/v/platform_body_parser?include_prereleases)
|
||||||
|
[![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety)
|
||||||
|
[![License](https://img.shields.io/github/license/dart-backend/belatuk-common-utilities)](https://github.com/dart-backend/belatuk-common-utilities/blob/main/packages/body_parser/LICENSE)
|
||||||
|
|
||||||
|
**Replacement of `package:body_parser` with breaking changes to support NNBD.**
|
||||||
|
|
||||||
|
Parse request bodies and query strings in Dart, as well multipart/form-data uploads. No external dependencies required.
|
||||||
|
|
||||||
|
This is the request body parser powering the [Angel3 framework](https://pub.dev/packages/angel3_framework). If you are looking for a server-side solution with dependency injection, WebSockets, and more, then I highly recommend it as your first choice. Bam!
|
||||||
|
|
||||||
|
## Contents
|
||||||
|
|
||||||
|
- [Belatuk Body Parser](#belatuk-body-parser)
|
||||||
|
- [Contents](#contents)
|
||||||
|
- [About](#about)
|
||||||
|
- [Installation](#installation)
|
||||||
|
- [Usage](#usage)
|
||||||
|
- [Custom Body Parsing](#custom-body-parsing)
|
||||||
|
|
||||||
|
### About
|
||||||
|
|
||||||
|
This package is similar to Express.js's `body-parser` module. It fully supports JSON, x-www-form-urlencoded as well as query strings requests. You can also include arrays in your query, in the same way you would for a PHP application. A benefit of this is that primitive types are automatically deserialized correctly. As in, if you have a `hello=1.5` request, then `body['hello']` will equal `1.5` and not `'1.5'`.
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
To install Body Parser for your Dart project, simply add body_parser to your pub dependencies.
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
platform_body_parser: ^5.2.0
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
Body Parser exposes a simple class called `BodyParseResult`. You can easily parse the query string and request body for a request by calling `Future<BodyParseResult> parseBody`.
|
||||||
|
|
||||||
|
```dart
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'package:platform_body_parser/platform_body_parser.dart';
|
||||||
|
|
||||||
|
main() async {
|
||||||
|
// ...
|
||||||
|
await for (HttpRequest request in server) {
|
||||||
|
request.response.write(JSON.encode(await parseBody(request).body));
|
||||||
|
await request.response.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also use `buildMapFromUri(Map, String)` to populate a map from a URL encoded string.
|
||||||
|
|
||||||
|
This can easily be used with a library like [Angel3 JSON God](https://pub.dev/packages/angel3_json_god) to build structured JSON/REST APIs. Add validation and you've got an instant backend.
|
||||||
|
|
||||||
|
```dart
|
||||||
|
MyClass create(HttpRequest request) async {
|
||||||
|
return god.deserialize(await parseBody(request).body, MyClass);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom Body Parsing
|
||||||
|
|
||||||
|
In cases where you need to parse unrecognized content types, `body_parser` won't be of any help to you on its own. However, you can use the `originalBuffer` property of a `BodyParseResult` to see the original request buffer. To get this functionality, pass `storeOriginalBuffer` as `true` when calling `parseBody`.
|
||||||
|
|
||||||
|
For example, if you wanted to [parse GraphQL queries within your server](https://github.com/dukefirehawk/graphql_dart)...
|
||||||
|
|
||||||
|
```dart
|
||||||
|
app.get('/graphql', (req, res) async {
|
||||||
|
if (req.headers.contentType.mimeType == 'application/graphql') {
|
||||||
|
var graphQlString = String.fromCharCodes(req.originalBuffer);
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
1
common/body_parser/analysis_options.yaml
Normal file
1
common/body_parser/analysis_options.yaml
Normal file
|
@ -0,0 +1 @@
|
||||||
|
include: package:lints/recommended.yaml
|
61
common/body_parser/example/main.dart
Normal file
61
common/body_parser/example/main.dart
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'dart:isolate';
|
||||||
|
import 'package:http_parser/http_parser.dart';
|
||||||
|
import 'package:platform_body_parser/body_parser.dart';
|
||||||
|
|
||||||
|
void main() async {
|
||||||
|
var address = '127.0.0.1';
|
||||||
|
var port = 3000;
|
||||||
|
var futures = <Future>[];
|
||||||
|
|
||||||
|
for (var i = 1; i < Platform.numberOfProcessors; i++) {
|
||||||
|
futures.add(Isolate.spawn(start, [address, port, i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
await Future.wait(futures).then((_) {
|
||||||
|
print('All instances started.');
|
||||||
|
print(
|
||||||
|
'Test with "wrk -t12 -c400 -d30s -s ./example/post.lua http://localhost:3000" or similar');
|
||||||
|
start([address, port, 0]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void start(List args) {
|
||||||
|
var address = InternetAddress(args[0] as String);
|
||||||
|
var port = 8080;
|
||||||
|
if (args[1] is int) {
|
||||||
|
args[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
var id = 0;
|
||||||
|
if (args[2] is int) {
|
||||||
|
args[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpServer.bind(address, port, shared: true).then((server) {
|
||||||
|
server.listen((request) async {
|
||||||
|
// ignore: deprecated_member_use
|
||||||
|
var body = await defaultParseBody(request);
|
||||||
|
request.response
|
||||||
|
..headers.contentType = ContentType('application', 'json')
|
||||||
|
..write(json.encode(body.body));
|
||||||
|
await request.response.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
print(
|
||||||
|
'Server #$id listening at http://${server.address.address}:${server.port}');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<BodyParseResult> defaultParseBody(HttpRequest request,
|
||||||
|
{bool storeOriginalBuffer = false}) {
|
||||||
|
return parseBodyFromStream(
|
||||||
|
request,
|
||||||
|
request.headers.contentType != null
|
||||||
|
? MediaType.parse(request.headers.contentType.toString())
|
||||||
|
: null,
|
||||||
|
request.uri,
|
||||||
|
storeOriginalBuffer: storeOriginalBuffer);
|
||||||
|
}
|
6
common/body_parser/example/post.lua
Normal file
6
common/body_parser/example/post.lua
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
-- example HTTP POST script which demonstrates setting the
|
||||||
|
-- HTTP method, body, and adding a header
|
||||||
|
|
||||||
|
wrk.method = "POST"
|
||||||
|
wrk.body = "foo=bar&baz=quux"
|
||||||
|
wrk.headers["Content-Type"] = "application/x-www-form-urlencoded"
|
6
common/body_parser/lib/body_parser.dart
Normal file
6
common/body_parser/lib/body_parser.dart
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
/// A library for parsing HTTP request bodies and queries.
|
||||||
|
library platform_body_parser;
|
||||||
|
|
||||||
|
export 'src/body_parse_result.dart';
|
||||||
|
export 'src/file_upload_info.dart';
|
||||||
|
export 'src/parse_body.dart';
|
28
common/body_parser/lib/src/body_parse_result.dart
Normal file
28
common/body_parser/lib/src/body_parse_result.dart
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import 'file_upload_info.dart';
|
||||||
|
|
||||||
|
/// A representation of data from an incoming request.
|
||||||
|
abstract class BodyParseResult {
|
||||||
|
/// The parsed body.
|
||||||
|
Map<String?, dynamic> get body;
|
||||||
|
|
||||||
|
/// The parsed query string.
|
||||||
|
Map<String?, dynamic> get query;
|
||||||
|
|
||||||
|
/// All files uploaded within this request.
|
||||||
|
List<FileUploadInfo> get files;
|
||||||
|
|
||||||
|
/// The original body bytes sent with this request.
|
||||||
|
///
|
||||||
|
/// You must set [storeOriginalBuffer] to `true` to see this.
|
||||||
|
List<int>? get originalBuffer;
|
||||||
|
|
||||||
|
/// If an error was encountered while parsing the body, it will appear here.
|
||||||
|
///
|
||||||
|
/// Otherwise, this is `null`.
|
||||||
|
dynamic get error;
|
||||||
|
|
||||||
|
/// If an error was encountered while parsing the body, the call stack will appear here.
|
||||||
|
///
|
||||||
|
/// Otherwise, this is `null`.
|
||||||
|
StackTrace? get stack;
|
||||||
|
}
|
7
common/body_parser/lib/src/chunk.dart
Normal file
7
common/body_parser/lib/src/chunk.dart
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import 'file_upload_info.dart';
|
||||||
|
|
||||||
|
List<FileUploadInfo> getFileDataFromChunk(
|
||||||
|
String chunk, String boundary, String fileUploadName, Map body) {
|
||||||
|
var result = <FileUploadInfo>[];
|
||||||
|
return result;
|
||||||
|
}
|
17
common/body_parser/lib/src/file_upload_info.dart
Normal file
17
common/body_parser/lib/src/file_upload_info.dart
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
/// Represents a file uploaded to the server.
|
||||||
|
class FileUploadInfo {
|
||||||
|
/// The MIME type of the uploaded file.
|
||||||
|
String? mimeType;
|
||||||
|
|
||||||
|
/// The name of the file field from the request.
|
||||||
|
String? name;
|
||||||
|
|
||||||
|
/// The filename of the file.
|
||||||
|
String? filename;
|
||||||
|
|
||||||
|
/// The bytes that make up this file.
|
||||||
|
List<int> data;
|
||||||
|
|
||||||
|
FileUploadInfo(
|
||||||
|
{this.mimeType, this.name, this.filename, this.data = const []});
|
||||||
|
}
|
22
common/body_parser/lib/src/get_value.dart
Normal file
22
common/body_parser/lib/src/get_value.dart
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
dynamic getValue(String value) {
|
||||||
|
try {
|
||||||
|
var numValue = num.parse(value);
|
||||||
|
if (!numValue.isNaN) {
|
||||||
|
return numValue;
|
||||||
|
} else {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
} on FormatException {
|
||||||
|
if (value.startsWith('[') && value.endsWith(']')) {
|
||||||
|
return json.decode(value);
|
||||||
|
} else if (value.startsWith('{') && value.endsWith('}')) {
|
||||||
|
return json.decode(value);
|
||||||
|
} else if (value.trim().toLowerCase() == 'null') {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
44
common/body_parser/lib/src/map_from_uri.dart
Normal file
44
common/body_parser/lib/src/map_from_uri.dart
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
import 'get_value.dart';
|
||||||
|
|
||||||
|
/// Parses a URI-encoded string into real data! **Wow!**
|
||||||
|
///
|
||||||
|
/// Whichever map you provide will be automatically populated from the urlencoded body string you provide.
|
||||||
|
void buildMapFromUri(Map map, String body) {
|
||||||
|
var parseArrayRgx = RegExp(r'^(.+)\[\]$');
|
||||||
|
|
||||||
|
for (var keyValuePair in body.split('&')) {
|
||||||
|
if (keyValuePair.contains('=')) {
|
||||||
|
var equals = keyValuePair.indexOf('=');
|
||||||
|
var key = Uri.decodeQueryComponent(keyValuePair.substring(0, equals));
|
||||||
|
var value = Uri.decodeQueryComponent(keyValuePair.substring(equals + 1));
|
||||||
|
|
||||||
|
if (parseArrayRgx.hasMatch(key)) {
|
||||||
|
Match queryMatch = parseArrayRgx.firstMatch(key)!;
|
||||||
|
key = queryMatch.group(1)!;
|
||||||
|
if (map[key] is! List) {
|
||||||
|
map[key] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
map[key].add(getValue(value));
|
||||||
|
} else if (key.contains('.')) {
|
||||||
|
// i.e. map.foo.bar => [map, foo, bar]
|
||||||
|
var keys = key.split('.');
|
||||||
|
|
||||||
|
var targetMap = map[keys[0]] != null ? map[keys[0]] as Map? : {};
|
||||||
|
map[keys[0]] = targetMap;
|
||||||
|
for (var i = 1; i < keys.length; i++) {
|
||||||
|
if (i < keys.length - 1) {
|
||||||
|
targetMap![keys[i]] = targetMap[keys[i]] ?? {};
|
||||||
|
targetMap = targetMap[keys[i]] as Map?;
|
||||||
|
} else {
|
||||||
|
targetMap![keys[i]] = getValue(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
map[key] = getValue(value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
map[Uri.decodeQueryComponent(keyValuePair)] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
147
common/body_parser/lib/src/parse_body.dart
Normal file
147
common/body_parser/lib/src/parse_body.dart
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:http_parser/http_parser.dart';
|
||||||
|
import 'package:platform_http_server/http_server.dart';
|
||||||
|
import 'package:mime/mime.dart';
|
||||||
|
|
||||||
|
import 'body_parse_result.dart';
|
||||||
|
import 'file_upload_info.dart';
|
||||||
|
import 'map_from_uri.dart';
|
||||||
|
|
||||||
|
/// Forwards to [parseBodyFromStream].
|
||||||
|
@Deprecated("parseBodyFromStream")
|
||||||
|
Future<BodyParseResult> parseBody(HttpRequest request,
|
||||||
|
{bool storeOriginalBuffer = false}) {
|
||||||
|
return parseBodyFromStream(
|
||||||
|
request,
|
||||||
|
request.headers.contentType != null
|
||||||
|
? MediaType.parse(request.headers.contentType.toString())
|
||||||
|
: null,
|
||||||
|
request.uri,
|
||||||
|
storeOriginalBuffer: storeOriginalBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Grabs data from an incoming request.
|
||||||
|
///
|
||||||
|
/// Supports URL-encoded and JSON, as well as multipart/* forms.
|
||||||
|
/// On a file upload request, only fields with the name **'file'** are processed
|
||||||
|
/// as files. Anything else is put in the body. You can change the upload file name
|
||||||
|
/// via the *fileUploadName* parameter. :)
|
||||||
|
///
|
||||||
|
/// Use [storeOriginalBuffer] to add the original request bytes to the result.
|
||||||
|
Future<BodyParseResult> parseBodyFromStream(
|
||||||
|
Stream<Uint8List> data, MediaType? contentType, Uri requestUri,
|
||||||
|
{bool storeOriginalBuffer = false}) async {
|
||||||
|
var result = _BodyParseResultImpl();
|
||||||
|
|
||||||
|
Future<Uint8List> getBytes() {
|
||||||
|
return data
|
||||||
|
.fold<BytesBuilder>(BytesBuilder(copy: false), (a, b) => a..add(b))
|
||||||
|
.then((b) => b.takeBytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> getBody() {
|
||||||
|
if (storeOriginalBuffer) {
|
||||||
|
return getBytes().then((bytes) {
|
||||||
|
result.originalBuffer = bytes;
|
||||||
|
return utf8.decode(bytes);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return utf8.decoder.bind(data).join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (contentType != null) {
|
||||||
|
if (contentType.type == 'multipart' &&
|
||||||
|
contentType.parameters.containsKey('boundary')) {
|
||||||
|
Stream<Uint8List> stream;
|
||||||
|
|
||||||
|
if (storeOriginalBuffer) {
|
||||||
|
var bytes = result.originalBuffer = await getBytes();
|
||||||
|
var ctrl = StreamController<Uint8List>()..add(bytes);
|
||||||
|
await ctrl.close();
|
||||||
|
stream = ctrl.stream;
|
||||||
|
} else {
|
||||||
|
stream = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
var parts =
|
||||||
|
MimeMultipartTransformer(contentType.parameters['boundary']!)
|
||||||
|
.bind(stream)
|
||||||
|
.map((part) =>
|
||||||
|
HttpMultipartFormData.parse(part, defaultEncoding: utf8));
|
||||||
|
|
||||||
|
await for (HttpMultipartFormData part in parts) {
|
||||||
|
if (part.isBinary ||
|
||||||
|
part.contentDisposition.parameters.containsKey('filename')) {
|
||||||
|
var builder = await part.fold(
|
||||||
|
BytesBuilder(copy: false),
|
||||||
|
(BytesBuilder b, d) =>
|
||||||
|
b..add(d is! String ? (d as List<int>?)! : d.codeUnits));
|
||||||
|
var upload = FileUploadInfo(
|
||||||
|
mimeType: part.contentType!.mimeType,
|
||||||
|
name: part.contentDisposition.parameters['name'],
|
||||||
|
filename:
|
||||||
|
part.contentDisposition.parameters['filename'] ?? 'file',
|
||||||
|
data: builder.takeBytes());
|
||||||
|
result.files.add(upload);
|
||||||
|
} else if (part.isText) {
|
||||||
|
var text = await part.join();
|
||||||
|
buildMapFromUri(result.body,
|
||||||
|
'${part.contentDisposition.parameters["name"]}=$text');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (contentType.mimeType == 'application/json') {
|
||||||
|
result.body.addAll(
|
||||||
|
_foldToStringDynamic(json.decode(await getBody()) as Map?)!);
|
||||||
|
} else if (contentType.mimeType == 'application/x-www-form-urlencoded') {
|
||||||
|
var body = await getBody();
|
||||||
|
buildMapFromUri(result.body, body);
|
||||||
|
} else if (storeOriginalBuffer == true) {
|
||||||
|
result.originalBuffer = await getBytes();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (requestUri.hasQuery) {
|
||||||
|
buildMapFromUri(result.query, requestUri.query);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (storeOriginalBuffer == true) {
|
||||||
|
result.originalBuffer = await getBytes();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e, st) {
|
||||||
|
result.error = e;
|
||||||
|
result.stack = st;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BodyParseResultImpl implements BodyParseResult {
|
||||||
|
@override
|
||||||
|
Map<String?, dynamic> body = {};
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<FileUploadInfo> files = [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<int>? originalBuffer;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String?, dynamic> query = {};
|
||||||
|
|
||||||
|
@override
|
||||||
|
dynamic error;
|
||||||
|
|
||||||
|
@override
|
||||||
|
StackTrace? stack;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic>? _foldToStringDynamic(Map? map) {
|
||||||
|
return map?.keys.fold<Map<String, dynamic>>(
|
||||||
|
<String, dynamic>{}, (out, k) => out..[k.toString()] = map[k]);
|
||||||
|
}
|
417
common/body_parser/pubspec.lock
Normal file
417
common/body_parser/pubspec.lock
Normal file
|
@ -0,0 +1,417 @@
|
||||||
|
# Generated by pub
|
||||||
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
|
packages:
|
||||||
|
_fe_analyzer_shared:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: _fe_analyzer_shared
|
||||||
|
sha256: "45cfa8471b89fb6643fe9bf51bd7931a76b8f5ec2d65de4fb176dba8d4f22c77"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "73.0.0"
|
||||||
|
_macros:
|
||||||
|
dependency: transitive
|
||||||
|
description: dart
|
||||||
|
source: sdk
|
||||||
|
version: "0.3.2"
|
||||||
|
analyzer:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: analyzer
|
||||||
|
sha256: "4959fec185fe70cce007c57e9ab6983101dbe593d2bf8bbfb4453aaec0cf470a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.8.0"
|
||||||
|
args:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: args
|
||||||
|
sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.6.0"
|
||||||
|
async:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: async
|
||||||
|
sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.12.0"
|
||||||
|
boolean_selector:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: boolean_selector
|
||||||
|
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.2"
|
||||||
|
collection:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: collection
|
||||||
|
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.19.1"
|
||||||
|
convert:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: convert
|
||||||
|
sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.2"
|
||||||
|
coverage:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: coverage
|
||||||
|
sha256: e3493833ea012784c740e341952298f1cc77f1f01b1bbc3eb4eecf6984fb7f43
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.11.1"
|
||||||
|
crypto:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: crypto
|
||||||
|
sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.6"
|
||||||
|
file:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file
|
||||||
|
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "7.0.1"
|
||||||
|
frontend_server_client:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: frontend_server_client
|
||||||
|
sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.0"
|
||||||
|
glob:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: glob
|
||||||
|
sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.2"
|
||||||
|
http:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: http
|
||||||
|
sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.2"
|
||||||
|
http_multi_server:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http_multi_server
|
||||||
|
sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.2.1"
|
||||||
|
http_parser:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: http_parser
|
||||||
|
sha256: "76d306a1c3afb33fe82e2bbacad62a61f409b5634c915fceb0d799de1a913360"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.1.1"
|
||||||
|
io:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: io
|
||||||
|
sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.5"
|
||||||
|
js:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: js
|
||||||
|
sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.7.1"
|
||||||
|
lints:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: lints
|
||||||
|
sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.0"
|
||||||
|
logging:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: logging
|
||||||
|
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.0"
|
||||||
|
macros:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: macros
|
||||||
|
sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.2-main.4"
|
||||||
|
matcher:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: matcher
|
||||||
|
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.12.16+1"
|
||||||
|
meta:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: meta
|
||||||
|
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.16.0"
|
||||||
|
mime:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: mime
|
||||||
|
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.0"
|
||||||
|
node_preamble:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: node_preamble
|
||||||
|
sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.2"
|
||||||
|
package_config:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: package_config
|
||||||
|
sha256: "92d4488434b520a62570293fbd33bb556c7d49230791c1b4bbd973baf6d2dc67"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.1"
|
||||||
|
path:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path
|
||||||
|
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.9.1"
|
||||||
|
platform_http_server:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
path: "../http_server"
|
||||||
|
relative: true
|
||||||
|
source: path
|
||||||
|
version: "4.5.1"
|
||||||
|
pool:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pool
|
||||||
|
sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.5.1"
|
||||||
|
pub_semver:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pub_semver
|
||||||
|
sha256: "7b3cfbf654f3edd0c6298ecd5be782ce997ddf0e00531b9464b55245185bbbbd"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.5"
|
||||||
|
shelf:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf
|
||||||
|
sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.2"
|
||||||
|
shelf_packages_handler:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf_packages_handler
|
||||||
|
sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.2"
|
||||||
|
shelf_static:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf_static
|
||||||
|
sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.3"
|
||||||
|
shelf_web_socket:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf_web_socket
|
||||||
|
sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.1"
|
||||||
|
source_map_stack_trace:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_map_stack_trace
|
||||||
|
sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.2"
|
||||||
|
source_maps:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_maps
|
||||||
|
sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.10.13"
|
||||||
|
source_span:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_span
|
||||||
|
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.10.1"
|
||||||
|
stack_trace:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stack_trace
|
||||||
|
sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.12.0"
|
||||||
|
stream_channel:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stream_channel
|
||||||
|
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.2"
|
||||||
|
string_scanner:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: string_scanner
|
||||||
|
sha256: "0bd04f5bb74fcd6ff0606a888a30e917af9bd52820b178eaa464beb11dca84b6"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.0"
|
||||||
|
term_glyph:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: term_glyph
|
||||||
|
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.1"
|
||||||
|
test:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: test
|
||||||
|
sha256: "22eb7769bee38c7e032d532e8daa2e1cc901b799f603550a4db8f3a5f5173ea2"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.25.12"
|
||||||
|
test_api:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: test_api
|
||||||
|
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.7.4"
|
||||||
|
test_core:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: test_core
|
||||||
|
sha256: "84d17c3486c8dfdbe5e12a50c8ae176d15e2a771b96909a9442b40173649ccaa"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.6.8"
|
||||||
|
typed_data:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: typed_data
|
||||||
|
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.0"
|
||||||
|
vm_service:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: vm_service
|
||||||
|
sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "15.0.0"
|
||||||
|
watcher:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: watcher
|
||||||
|
sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0"
|
||||||
|
web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web
|
||||||
|
sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0"
|
||||||
|
web_socket:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web_socket
|
||||||
|
sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.6"
|
||||||
|
web_socket_channel:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web_socket_channel
|
||||||
|
sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.1"
|
||||||
|
webkit_inspection_protocol:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: webkit_inspection_protocol
|
||||||
|
sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.1"
|
||||||
|
yaml:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: yaml
|
||||||
|
sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.2"
|
||||||
|
sdks:
|
||||||
|
dart: ">=3.5.0 <4.0.0"
|
14
common/body_parser/pubspec.yaml
Normal file
14
common/body_parser/pubspec.yaml
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
name: platform_body_parser
|
||||||
|
version: 5.3.0
|
||||||
|
description: Parse request bodies and query strings in Dart. Supports JSON, URL-encoded, and multi-part bodies.
|
||||||
|
homepage: https://github.com/dart-backend/belatuk-common-utilities/tree/main/packages/body_parser
|
||||||
|
environment:
|
||||||
|
sdk: '>=3.3.0 <4.0.0'
|
||||||
|
dependencies:
|
||||||
|
http_parser: ^4.0.0
|
||||||
|
platform_http_server: ^4.4.0
|
||||||
|
mime: ^2.0.0
|
||||||
|
dev_dependencies:
|
||||||
|
http: ^1.0.0
|
||||||
|
test: ^1.24.0
|
||||||
|
lints: ^4.0.0
|
154
common/body_parser/test/form_data_test.dart
Normal file
154
common/body_parser/test/form_data_test.dart
Normal file
|
@ -0,0 +1,154 @@
|
||||||
|
import 'dart:io';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'package:platform_body_parser/body_parser.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:http_parser/http_parser.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
import 'server_test.dart';
|
||||||
|
|
||||||
|
Future<BodyParseResult> _parseBody(HttpRequest request) {
|
||||||
|
return parseBodyFromStream(
|
||||||
|
request,
|
||||||
|
request.headers.contentType != null
|
||||||
|
? MediaType.parse(request.headers.contentType.toString())
|
||||||
|
: null,
|
||||||
|
request.uri,
|
||||||
|
storeOriginalBuffer: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
HttpServer? server;
|
||||||
|
String? url;
|
||||||
|
http.Client? client;
|
||||||
|
|
||||||
|
setUp(() async {
|
||||||
|
server = await HttpServer.bind('127.0.0.1', 0);
|
||||||
|
server!.listen((HttpRequest request) async {
|
||||||
|
//Server will simply return a JSON representation of the parsed body
|
||||||
|
// ignore: deprecated_member_use
|
||||||
|
request.response.write(jsonEncodeBody(await _parseBody(request)));
|
||||||
|
await request.response.close();
|
||||||
|
});
|
||||||
|
url = 'http://localhost:${server!.port}';
|
||||||
|
print('Test server listening on $url');
|
||||||
|
client = http.Client();
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDown(() async {
|
||||||
|
await server!.close(force: true);
|
||||||
|
client!.close();
|
||||||
|
server = null;
|
||||||
|
url = null;
|
||||||
|
client = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('No upload', () async {
|
||||||
|
var boundary = 'myBoundary';
|
||||||
|
var headers = <String, String>{
|
||||||
|
'content-type': 'multipart/form-data; boundary=$boundary'
|
||||||
|
};
|
||||||
|
var postData = '''
|
||||||
|
--$boundary
|
||||||
|
Content-Disposition: form-data; name="hello"
|
||||||
|
|
||||||
|
world
|
||||||
|
--$boundary--
|
||||||
|
'''
|
||||||
|
.replaceAll('\n', '\r\n');
|
||||||
|
|
||||||
|
print(
|
||||||
|
'Form Data: \n${postData.replaceAll("\r", "\\r").replaceAll("\n", "\\n")}');
|
||||||
|
var response =
|
||||||
|
await client!.post(Uri.parse(url!), headers: headers, body: postData);
|
||||||
|
print('Response: ${response.body}');
|
||||||
|
var jsons = json.decode(response.body);
|
||||||
|
var files = jsons['files'].map((map) {
|
||||||
|
return map.keys.fold<Map<String, dynamic>>(
|
||||||
|
<String, dynamic>{}, (out, k) => out..[k.toString()] = map[k]);
|
||||||
|
});
|
||||||
|
expect(files.length, equals(0));
|
||||||
|
expect(jsons['body']['hello'], equals('world'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Single upload', () async {
|
||||||
|
var boundary = 'myBoundary';
|
||||||
|
var headers = <String, String>{
|
||||||
|
'content-type': ContentType('multipart', 'form-data',
|
||||||
|
parameters: {'boundary': boundary}).toString()
|
||||||
|
};
|
||||||
|
var postData = '''
|
||||||
|
--$boundary
|
||||||
|
Content-Disposition: form-data; name="hello"
|
||||||
|
|
||||||
|
world
|
||||||
|
--$boundary
|
||||||
|
Content-Disposition: form-data; name="file"; filename="app.dart"
|
||||||
|
Content-Type: application/dart
|
||||||
|
|
||||||
|
Hello world
|
||||||
|
--$boundary--
|
||||||
|
'''
|
||||||
|
.replaceAll('\n', '\r\n');
|
||||||
|
|
||||||
|
print(
|
||||||
|
'Form Data: \n${postData.replaceAll("\r", "\\r").replaceAll("\n", "\\n")}');
|
||||||
|
var response =
|
||||||
|
await client!.post(Uri.parse(url!), headers: headers, body: postData);
|
||||||
|
print('Response: ${response.body}');
|
||||||
|
var jsons = json.decode(response.body);
|
||||||
|
var files = jsons['files'];
|
||||||
|
expect(files.length, equals(1));
|
||||||
|
expect(files[0]['name'], equals('file'));
|
||||||
|
expect(files[0]['mimeType'], equals('application/dart'));
|
||||||
|
expect(files[0]['data'].length, equals(11));
|
||||||
|
expect(files[0]['filename'], equals('app.dart'));
|
||||||
|
expect(jsons['body']['hello'], equals('world'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Multiple upload', () async {
|
||||||
|
var boundary = 'myBoundary';
|
||||||
|
var headers = <String, String>{
|
||||||
|
'content-type': 'multipart/form-data; boundary=$boundary'
|
||||||
|
};
|
||||||
|
var postData = '''
|
||||||
|
--$boundary
|
||||||
|
Content-Disposition: form-data; name="json"
|
||||||
|
|
||||||
|
god
|
||||||
|
--$boundary
|
||||||
|
Content-Disposition: form-data; name="num"
|
||||||
|
|
||||||
|
14.50000
|
||||||
|
--$boundary
|
||||||
|
Content-Disposition: form-data; name="file"; filename="app.dart"
|
||||||
|
Content-Type: text/plain
|
||||||
|
|
||||||
|
Hello world
|
||||||
|
--$boundary
|
||||||
|
Content-Disposition: form-data; name="entry-point"; filename="main.js"
|
||||||
|
Content-Type: text/javascript
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
console.log("Hello, world!");
|
||||||
|
}
|
||||||
|
--$boundary--
|
||||||
|
'''
|
||||||
|
.replaceAll('\n', '\r\n');
|
||||||
|
|
||||||
|
print(
|
||||||
|
'Form Data: \n${postData.replaceAll("\r", "\\r").replaceAll("\n", "\\n")}');
|
||||||
|
var response =
|
||||||
|
await client!.post(Uri.parse(url!), headers: headers, body: postData);
|
||||||
|
print('Response: ${response.body}');
|
||||||
|
var jsons = json.decode(response.body);
|
||||||
|
var files = jsons['files'];
|
||||||
|
expect(files.length, equals(2));
|
||||||
|
expect(files[0]['name'], equals('file'));
|
||||||
|
expect(files[0]['mimeType'], equals('text/plain'));
|
||||||
|
expect(files[0]['data'].length, equals(11));
|
||||||
|
expect(files[1]['name'], equals('entry-point'));
|
||||||
|
expect(files[1]['mimeType'], equals('text/javascript'));
|
||||||
|
expect(jsons['body']['json'], equals('god'));
|
||||||
|
expect(jsons['body']['num'], equals(14.5));
|
||||||
|
});
|
||||||
|
}
|
174
common/body_parser/test/server_test.dart
Normal file
174
common/body_parser/test/server_test.dart
Normal file
|
@ -0,0 +1,174 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io' show HttpRequest, HttpServer;
|
||||||
|
|
||||||
|
import 'package:platform_body_parser/body_parser.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:http_parser/http_parser.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
const token =
|
||||||
|
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiIxMjcuMC4wLjEiLCJleHAiOi0xLCJpYXQiOiIyMDE2LTEyLTIyVDEyOjQ5OjUwLjM2MTQ0NiIsImlzcyI6ImFuZ2VsX2F1dGgiLCJzdWIiOiIxMDY2OTQ4Mzk2MDIwMjg5ODM2NTYifQ==.PYw7yUb-cFWD7N0sSLztP7eeRvO44nu1J2OgDNyT060=';
|
||||||
|
|
||||||
|
String jsonEncodeBody(BodyParseResult result) {
|
||||||
|
return json.encode({
|
||||||
|
'query': result.query,
|
||||||
|
'body': result.body,
|
||||||
|
'error': result.error?.toString(),
|
||||||
|
'files': result.files.map((f) {
|
||||||
|
return {
|
||||||
|
'name': f.name,
|
||||||
|
'mimeType': f.mimeType,
|
||||||
|
'filename': f.filename,
|
||||||
|
'data': f.data,
|
||||||
|
};
|
||||||
|
}).toList(),
|
||||||
|
'originalBuffer': result.originalBuffer,
|
||||||
|
'stack': null, //result.stack.toString(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<BodyParseResult> _parseBody(HttpRequest request) {
|
||||||
|
return parseBodyFromStream(
|
||||||
|
request,
|
||||||
|
request.headers.contentType != null
|
||||||
|
? MediaType.parse(request.headers.contentType.toString())
|
||||||
|
: null,
|
||||||
|
request.uri,
|
||||||
|
storeOriginalBuffer: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
HttpServer? server;
|
||||||
|
String? url;
|
||||||
|
http.Client? client;
|
||||||
|
|
||||||
|
setUp(() async {
|
||||||
|
server = await HttpServer.bind('127.0.0.1', 0);
|
||||||
|
server!.listen((HttpRequest request) async {
|
||||||
|
//Server will simply return a JSON representation of the parsed body
|
||||||
|
request.response.write(
|
||||||
|
// ignore: deprecated_member_use
|
||||||
|
jsonEncodeBody(await _parseBody(request)));
|
||||||
|
await request.response.close();
|
||||||
|
});
|
||||||
|
url = 'http://localhost:${server!.port}';
|
||||||
|
print('Test server listening on $url');
|
||||||
|
client = http.Client();
|
||||||
|
});
|
||||||
|
tearDown(() async {
|
||||||
|
await server!.close(force: true);
|
||||||
|
client!.close();
|
||||||
|
server = null;
|
||||||
|
url = null;
|
||||||
|
client = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
group('query string', () {
|
||||||
|
test('GET Simple', () async {
|
||||||
|
print('GET $url/?hello=world');
|
||||||
|
var response = await client!.get(Uri.parse('$url/?hello=world'));
|
||||||
|
print('Response: ${response.body}');
|
||||||
|
var result = json.decode(response.body);
|
||||||
|
expect(result['body'], equals({}));
|
||||||
|
expect(result['query'], equals({'hello': 'world'}));
|
||||||
|
expect(result['files'], equals([]));
|
||||||
|
//expect(result['originalBuffer'], isNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GET Complex', () async {
|
||||||
|
var postData =
|
||||||
|
'hello=world&nums%5B%5D=1&nums%5B%5D=2.0&nums%5B%5D=${3 - 1}&map.foo.bar=baz';
|
||||||
|
print('Body: $postData');
|
||||||
|
var response = await client!.get(Uri.parse('$url/?$postData'));
|
||||||
|
print('Response: ${response.body}');
|
||||||
|
var query = json.decode(response.body)['query'];
|
||||||
|
expect(query['hello'], equals('world'));
|
||||||
|
expect(query['nums'][2], equals(2));
|
||||||
|
expect(query['map'] is Map, equals(true));
|
||||||
|
expect(query['map']['foo'], equals({'bar': 'baz'}));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('JWT', () async {
|
||||||
|
var postData = 'token=$token';
|
||||||
|
print('Body: $postData');
|
||||||
|
var response = await client!.get(Uri.parse('$url/?$postData'));
|
||||||
|
print('Response: ${response.body}');
|
||||||
|
var query = json.decode(response.body)['query'];
|
||||||
|
expect(query['token'], equals(token));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('urlencoded', () {
|
||||||
|
var headers = <String, String>{
|
||||||
|
'content-type': 'application/x-www-form-urlencoded'
|
||||||
|
};
|
||||||
|
test('POST Simple', () async {
|
||||||
|
print('Body: hello=world');
|
||||||
|
var response = await client!
|
||||||
|
.post(Uri.parse(url!), headers: headers, body: 'hello=world');
|
||||||
|
print('Response: ${response.body}');
|
||||||
|
var result = json.decode(response.body);
|
||||||
|
expect(result['query'], equals({}));
|
||||||
|
expect(result['body'], equals({'hello': 'world'}));
|
||||||
|
expect(result['files'], equals([]));
|
||||||
|
expect(result['originalBuffer'], isList);
|
||||||
|
expect(result['originalBuffer'], isNotEmpty);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Post Complex', () async {
|
||||||
|
var postData =
|
||||||
|
'hello=world&nums%5B%5D=1&nums%5B%5D=2.0&nums%5B%5D=${3 - 1}&map.foo.bar=baz';
|
||||||
|
var response =
|
||||||
|
await client!.post(Uri.parse(url!), headers: headers, body: postData);
|
||||||
|
print('Response: ${response.body}');
|
||||||
|
var body = json.decode(response.body)['body'];
|
||||||
|
expect(body['hello'], equals('world'));
|
||||||
|
expect(body['nums'][2], equals(2));
|
||||||
|
expect(body['map'] is Map, equals(true));
|
||||||
|
expect(body['map']['foo'], equals({'bar': 'baz'}));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('JWT', () async {
|
||||||
|
var postData = 'token=$token';
|
||||||
|
var response =
|
||||||
|
await client!.post(Uri.parse(url!), headers: headers, body: postData);
|
||||||
|
var body = json.decode(response.body)['body'];
|
||||||
|
expect(body['token'], equals(token));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('json', () {
|
||||||
|
var headers = <String, String>{'content-type': 'application/json'};
|
||||||
|
test('Post Simple', () async {
|
||||||
|
var postData = json.encode({'hello': 'world'});
|
||||||
|
print('Body: $postData');
|
||||||
|
var response =
|
||||||
|
await client!.post(Uri.parse(url!), headers: headers, body: postData);
|
||||||
|
print('Response: ${response.body}');
|
||||||
|
var result = json.decode(response.body);
|
||||||
|
expect(result['body'], equals({'hello': 'world'}));
|
||||||
|
expect(result['query'], equals({}));
|
||||||
|
expect(result['files'], equals([]));
|
||||||
|
expect(result['originalBuffer'], allOf(isList, isNotEmpty));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Post Complex', () async {
|
||||||
|
var postData = json.encode({
|
||||||
|
'hello': 'world',
|
||||||
|
'nums': [1, 2.0, 3 - 1],
|
||||||
|
'map': {
|
||||||
|
'foo': {'bar': 'baz'}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
print('Body: $postData');
|
||||||
|
var response =
|
||||||
|
await client!.post(Uri.parse(url!), headers: headers, body: postData);
|
||||||
|
print('Response: ${response.body}');
|
||||||
|
var body = json.decode(response.body)['body'];
|
||||||
|
expect(body['hello'], equals('world'));
|
||||||
|
expect(body['nums'][2], equals(2));
|
||||||
|
expect(body['map'] is Map, equals(true));
|
||||||
|
expect(body['map']['foo'], equals({'bar': 'baz'}));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
12
common/code_buffer/AUTHORS.md
Normal file
12
common/code_buffer/AUTHORS.md
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
Primary Authors
|
||||||
|
===============
|
||||||
|
|
||||||
|
* __[Thomas Hii](dukefirehawk.apps@gmail.com)__
|
||||||
|
|
||||||
|
Thomas is the current maintainer of the code base. He has refactored and migrated the
|
||||||
|
code base to support NNBD.
|
||||||
|
|
||||||
|
* __[Tobe O](thosakwe@gmail.com)__
|
||||||
|
|
||||||
|
Tobe has written much of the original code prior to NNBD migration. He has moved on and
|
||||||
|
is no longer involved with the project.
|
55
common/code_buffer/CHANGELOG.md
Normal file
55
common/code_buffer/CHANGELOG.md
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
# Change Log
|
||||||
|
|
||||||
|
## 5.2.0
|
||||||
|
|
||||||
|
* Require Dart >= 3.3
|
||||||
|
* Updated `lints` to 4.0.0
|
||||||
|
|
||||||
|
## 5.1.0
|
||||||
|
|
||||||
|
* Updated `lints` to 3.0.0
|
||||||
|
|
||||||
|
## 5.0.0
|
||||||
|
|
||||||
|
* Require Dart >= 3.0
|
||||||
|
|
||||||
|
## 5.0.0-beta.1
|
||||||
|
|
||||||
|
* Require Dart >= 3.0
|
||||||
|
|
||||||
|
## 4.0.0
|
||||||
|
|
||||||
|
* Require Dart >= 2.17
|
||||||
|
|
||||||
|
## 3.0.2
|
||||||
|
|
||||||
|
* Fixed license link
|
||||||
|
|
||||||
|
## 3.0.1
|
||||||
|
|
||||||
|
* Updated README
|
||||||
|
|
||||||
|
## 3.0.0
|
||||||
|
|
||||||
|
* Upgraded from `pendantic` to `lints` linter
|
||||||
|
* Published as `platform_code_buffer` package
|
||||||
|
|
||||||
|
## 2.0.3
|
||||||
|
|
||||||
|
* Resolved static analysis warnings
|
||||||
|
|
||||||
|
## 2.0.2
|
||||||
|
|
||||||
|
* Updated README
|
||||||
|
|
||||||
|
## 2.0.1
|
||||||
|
|
||||||
|
* Fixed invalid homepage url in pubspec.yaml
|
||||||
|
|
||||||
|
## 2.0.0
|
||||||
|
|
||||||
|
* Migrated to support Dart SDK 2.12.x NNBD
|
||||||
|
|
||||||
|
## 1.0.1
|
||||||
|
|
||||||
|
* Added `CodeBuffer.noWhitespace()`.
|
29
common/code_buffer/LICENSE
Normal file
29
common/code_buffer/LICENSE
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
BSD 3-Clause License
|
||||||
|
|
||||||
|
Copyright (c) 2021, dukefirehawk.com
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
3. Neither the name of the copyright holder nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
69
common/code_buffer/README.md
Normal file
69
common/code_buffer/README.md
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
# Belatuk Code Buffer
|
||||||
|
|
||||||
|
![Pub Version (including pre-releases)](https://img.shields.io/pub/v/platform_code_buffer?include_prereleases)
|
||||||
|
[![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety)
|
||||||
|
[![License](https://img.shields.io/github/license/dart-backend/belatuk-common-utilities)](https://github.com/dart-backend/belatuk-common-utilities/blob/main/packages/code_buffer/LICENSE)
|
||||||
|
|
||||||
|
**Replacement of `package:code_buffer` with breaking changes to support NNBD.**
|
||||||
|
|
||||||
|
An advanced StringBuffer geared toward generating code, and source maps.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
In your `pubspec.yaml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
dependencies:
|
||||||
|
platform_code_buffer: ^5.1.0
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Use a `CodeBuffer` just like any regular `StringBuffer`:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
String someFunc() {
|
||||||
|
var buf = CodeBuffer();
|
||||||
|
buf
|
||||||
|
..write('hello ')
|
||||||
|
..writeln('world!');
|
||||||
|
return buf.toString();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
However, a `CodeBuffer` supports indentation.
|
||||||
|
|
||||||
|
```dart
|
||||||
|
void someOtherFunc() {
|
||||||
|
var buf = CodeBuffer();
|
||||||
|
// Custom options...
|
||||||
|
var buf = CodeBuffer(newline: '\r\n', space: '\t', trailingNewline: true);
|
||||||
|
|
||||||
|
// Any following lines will have an incremented indentation level...
|
||||||
|
buf.indent();
|
||||||
|
|
||||||
|
// And vice-versa:
|
||||||
|
buf.outdent();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`CodeBuffer` instances keep track of every `SourceSpan` they create.
|
||||||
|
This makes them useful for codegen tools, or to-JS compilers.
|
||||||
|
|
||||||
|
```dart
|
||||||
|
void someFunc(CodeBuffer buf) {
|
||||||
|
buf.write('hello');
|
||||||
|
expect(buf.lastLine.text, 'hello');
|
||||||
|
|
||||||
|
buf.writeln('world');
|
||||||
|
expect(buf.lastLine.lastSpan.start.column, 5);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You can copy a `CodeBuffer` into another, heeding indentation rules:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
void yetAnotherFunc(CodeBuffer a, CodeBuffer b) {
|
||||||
|
b.copyInto(a);
|
||||||
|
}
|
||||||
|
```
|
1
common/code_buffer/analysis_options.yaml
Normal file
1
common/code_buffer/analysis_options.yaml
Normal file
|
@ -0,0 +1 @@
|
||||||
|
include: package:lints/recommended.yaml
|
46
common/code_buffer/example/main.dart
Normal file
46
common/code_buffer/example/main.dart
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
import 'package:platform_code_buffer/code_buffer.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
/// Use a `CodeBuffer` just like any regular `StringBuffer`:
|
||||||
|
String someFunc() {
|
||||||
|
var buf = CodeBuffer();
|
||||||
|
buf
|
||||||
|
..write('hello ')
|
||||||
|
..writeln('world!');
|
||||||
|
return buf.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// However, a `CodeBuffer` supports indentation.
|
||||||
|
void someOtherFunc() {
|
||||||
|
var buf = CodeBuffer();
|
||||||
|
|
||||||
|
// Custom options...
|
||||||
|
// ignore: unused_local_variable
|
||||||
|
var customBuf =
|
||||||
|
CodeBuffer(newline: '\r\n', space: '\t', trailingNewline: true);
|
||||||
|
|
||||||
|
// Without whitespace..
|
||||||
|
// ignore: unused_local_variable
|
||||||
|
var minifyingBuf = CodeBuffer.noWhitespace();
|
||||||
|
|
||||||
|
// Any following lines will have an incremented indentation level...
|
||||||
|
buf.indent();
|
||||||
|
|
||||||
|
// And vice-versa:
|
||||||
|
buf.outdent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `CodeBuffer` instances keep track of every `SourceSpan` they create.
|
||||||
|
//This makes them useful for codegen tools, or to-JS compilers.
|
||||||
|
void yetAnotherOtherFunc(CodeBuffer buf) {
|
||||||
|
buf.write('hello');
|
||||||
|
expect(buf.lastLine!.text, 'hello');
|
||||||
|
|
||||||
|
buf.writeln('world');
|
||||||
|
expect(buf.lastLine!.lastSpan!.start.column, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// You can copy a `CodeBuffer` into another, heeding indentation rules:
|
||||||
|
void yetEvenAnotherFunc(CodeBuffer a, CodeBuffer b) {
|
||||||
|
b.copyInto(a);
|
||||||
|
}
|
231
common/code_buffer/lib/code_buffer.dart
Normal file
231
common/code_buffer/lib/code_buffer.dart
Normal file
|
@ -0,0 +1,231 @@
|
||||||
|
import 'package:source_span/source_span.dart';
|
||||||
|
|
||||||
|
/// An advanced StringBuffer geared toward generating code, and source maps.
|
||||||
|
class CodeBuffer implements StringBuffer {
|
||||||
|
/// The character sequence used to represent a line break.
|
||||||
|
final String newline;
|
||||||
|
|
||||||
|
/// The character sequence used to represent a space/tab.
|
||||||
|
final String space;
|
||||||
|
|
||||||
|
/// The source URL to be applied to all generated [SourceSpan] instances.
|
||||||
|
final dynamic sourceUrl;
|
||||||
|
|
||||||
|
/// If `true` (default: `false`), then an additional [newline] will be inserted at the end of the generated string.
|
||||||
|
final bool trailingNewline;
|
||||||
|
|
||||||
|
final List<CodeBufferLine> _lines = [];
|
||||||
|
CodeBufferLine? _currentLine, _lastLine;
|
||||||
|
int _indentationLevel = 0;
|
||||||
|
int _length = 0;
|
||||||
|
|
||||||
|
CodeBuffer(
|
||||||
|
{this.space = ' ',
|
||||||
|
this.newline = '\n',
|
||||||
|
this.trailingNewline = false,
|
||||||
|
this.sourceUrl});
|
||||||
|
|
||||||
|
/// Creates a [CodeBuffer] that does not emit additional whitespace.
|
||||||
|
factory CodeBuffer.noWhitespace({sourceUrl}) => CodeBuffer(
|
||||||
|
space: '', newline: '', trailingNewline: false, sourceUrl: sourceUrl);
|
||||||
|
|
||||||
|
/// The last line created within this buffer.
|
||||||
|
CodeBufferLine? get lastLine => _lastLine;
|
||||||
|
|
||||||
|
/// Returns an immutable collection of the [CodeBufferLine]s within this instance.
|
||||||
|
List<CodeBufferLine> get lines => List<CodeBufferLine>.unmodifiable(_lines);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get isEmpty => _lines.isEmpty;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get isNotEmpty => _lines.isNotEmpty;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get length => _length;
|
||||||
|
|
||||||
|
CodeBufferLine _createLine() {
|
||||||
|
var start = SourceLocation(
|
||||||
|
_length,
|
||||||
|
sourceUrl: sourceUrl,
|
||||||
|
line: _lines.length,
|
||||||
|
column: _indentationLevel * space.length,
|
||||||
|
);
|
||||||
|
var line = CodeBufferLine._(_indentationLevel, start).._end = start;
|
||||||
|
_lines.add(_lastLine = line);
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Increments the indentation level.
|
||||||
|
void indent() {
|
||||||
|
_indentationLevel++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decrements the indentation level, if it is greater than `0`.
|
||||||
|
void outdent() {
|
||||||
|
if (_indentationLevel > 0) _indentationLevel--;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Copies the contents of this [CodeBuffer] into another, preserving indentation and source mapping information.
|
||||||
|
void copyInto(CodeBuffer other) {
|
||||||
|
if (_lines.isEmpty) return;
|
||||||
|
var i = 0;
|
||||||
|
|
||||||
|
for (var line in _lines) {
|
||||||
|
// To compute offset:
|
||||||
|
// 1. Find current length of other
|
||||||
|
// 2. Add length of its newline
|
||||||
|
// 3. Add indentation
|
||||||
|
var column = (other._indentationLevel + line.indentationLevel) *
|
||||||
|
other.space.length;
|
||||||
|
var offset = other._length + other.newline.length + column;
|
||||||
|
|
||||||
|
// Re-compute start + end
|
||||||
|
var start = SourceLocation(
|
||||||
|
offset,
|
||||||
|
sourceUrl: other.sourceUrl,
|
||||||
|
line: other._lines.length + i,
|
||||||
|
column: column,
|
||||||
|
);
|
||||||
|
|
||||||
|
var end = SourceLocation(
|
||||||
|
offset + line.span.length,
|
||||||
|
sourceUrl: other.sourceUrl,
|
||||||
|
line: start.line,
|
||||||
|
column: column + line._buf.length,
|
||||||
|
);
|
||||||
|
|
||||||
|
var clone = CodeBufferLine._(
|
||||||
|
line.indentationLevel + other._indentationLevel, start)
|
||||||
|
.._end = end
|
||||||
|
.._buf.write(line._buf.toString());
|
||||||
|
|
||||||
|
// Adjust lastSpan
|
||||||
|
if (line._lastSpan != null) {
|
||||||
|
var s = line._lastSpan!.start;
|
||||||
|
var lastSpanColumn =
|
||||||
|
((line.indentationLevel + other._indentationLevel) *
|
||||||
|
other.space.length) +
|
||||||
|
line.text.indexOf(line._lastSpan!.text);
|
||||||
|
clone._lastSpan = SourceSpan(
|
||||||
|
SourceLocation(
|
||||||
|
offset + s.offset,
|
||||||
|
sourceUrl: other.sourceUrl,
|
||||||
|
line: clone.span.start.line,
|
||||||
|
column: lastSpanColumn,
|
||||||
|
),
|
||||||
|
SourceLocation(
|
||||||
|
offset + s.offset + line._lastSpan!.length,
|
||||||
|
sourceUrl: other.sourceUrl,
|
||||||
|
line: clone.span.end.line,
|
||||||
|
column: lastSpanColumn + line._lastSpan!.length,
|
||||||
|
),
|
||||||
|
line._lastSpan!.text,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
other._lines.add(other._currentLine = other._lastLine = clone);
|
||||||
|
|
||||||
|
// Adjust length accordingly...
|
||||||
|
other._length = offset + clone.span.length;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
other.writeln();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void clear() {
|
||||||
|
_lines.clear();
|
||||||
|
_length = _indentationLevel = 0;
|
||||||
|
_currentLine = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void writeCharCode(int charCode) {
|
||||||
|
_currentLine ??= _createLine();
|
||||||
|
|
||||||
|
_currentLine!._buf.writeCharCode(charCode);
|
||||||
|
var end = _currentLine!._end;
|
||||||
|
_currentLine!._end = SourceLocation(
|
||||||
|
end.offset + 1,
|
||||||
|
sourceUrl: end.sourceUrl,
|
||||||
|
line: end.line,
|
||||||
|
column: end.column + 1,
|
||||||
|
);
|
||||||
|
_length++;
|
||||||
|
_currentLine!._lastSpan =
|
||||||
|
SourceSpan(end, _currentLine!._end, String.fromCharCode(charCode));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void write(Object? obj) {
|
||||||
|
var msg = obj.toString();
|
||||||
|
_currentLine ??= _createLine();
|
||||||
|
_currentLine!._buf.write(msg);
|
||||||
|
var end = _currentLine!._end;
|
||||||
|
_currentLine!._end = SourceLocation(
|
||||||
|
end.offset + msg.length,
|
||||||
|
sourceUrl: end.sourceUrl,
|
||||||
|
line: end.line,
|
||||||
|
column: end.column + msg.length,
|
||||||
|
);
|
||||||
|
_length += msg.length;
|
||||||
|
_currentLine!._lastSpan = SourceSpan(end, _currentLine!._end, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void writeln([Object? obj = '']) {
|
||||||
|
if (obj != null && obj != '') write(obj);
|
||||||
|
_currentLine = null;
|
||||||
|
_length++;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void writeAll(Iterable objects, [String separator = '']) {
|
||||||
|
write(objects.join(separator));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
var buf = StringBuffer();
|
||||||
|
var i = 0;
|
||||||
|
|
||||||
|
for (var line in lines) {
|
||||||
|
if (i++ > 0) buf.write(newline);
|
||||||
|
for (var j = 0; j < line.indentationLevel; j++) {
|
||||||
|
buf.write(space);
|
||||||
|
}
|
||||||
|
buf.write(line._buf.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trailingNewline == true) buf.write(newline);
|
||||||
|
|
||||||
|
return buf.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a line of text within a [CodeBuffer].
|
||||||
|
class CodeBufferLine {
|
||||||
|
/// Mappings from one [SourceSpan] to another, to aid with generating dynamic source maps.
|
||||||
|
final Map<SourceSpan, SourceSpan> sourceMappings = {};
|
||||||
|
|
||||||
|
/// The level of indentation preceding this line.
|
||||||
|
final int indentationLevel;
|
||||||
|
|
||||||
|
final SourceLocation _start;
|
||||||
|
final StringBuffer _buf = StringBuffer();
|
||||||
|
late SourceLocation _end;
|
||||||
|
SourceSpan? _lastSpan;
|
||||||
|
|
||||||
|
CodeBufferLine._(this.indentationLevel, this._start);
|
||||||
|
|
||||||
|
/// The [SourceSpan] corresponding to the last text written to this line.
|
||||||
|
SourceSpan? get lastSpan => _lastSpan;
|
||||||
|
|
||||||
|
/// The [SourceSpan] corresponding to this entire line.
|
||||||
|
SourceSpan get span => SourceSpan(_start, _end, _buf.toString());
|
||||||
|
|
||||||
|
/// The text within this line.
|
||||||
|
String get text => _buf.toString();
|
||||||
|
}
|
410
common/code_buffer/pubspec.lock
Normal file
410
common/code_buffer/pubspec.lock
Normal file
|
@ -0,0 +1,410 @@
|
||||||
|
# Generated by pub
|
||||||
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
|
packages:
|
||||||
|
_fe_analyzer_shared:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: _fe_analyzer_shared
|
||||||
|
sha256: "45cfa8471b89fb6643fe9bf51bd7931a76b8f5ec2d65de4fb176dba8d4f22c77"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "73.0.0"
|
||||||
|
_macros:
|
||||||
|
dependency: transitive
|
||||||
|
description: dart
|
||||||
|
source: sdk
|
||||||
|
version: "0.3.2"
|
||||||
|
analyzer:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: analyzer
|
||||||
|
sha256: "4959fec185fe70cce007c57e9ab6983101dbe593d2bf8bbfb4453aaec0cf470a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.8.0"
|
||||||
|
args:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: args
|
||||||
|
sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.6.0"
|
||||||
|
async:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: async
|
||||||
|
sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.12.0"
|
||||||
|
boolean_selector:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: boolean_selector
|
||||||
|
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.2"
|
||||||
|
charcode:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: charcode
|
||||||
|
sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.0"
|
||||||
|
collection:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: collection
|
||||||
|
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.19.1"
|
||||||
|
convert:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: convert
|
||||||
|
sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.2"
|
||||||
|
coverage:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: coverage
|
||||||
|
sha256: e3493833ea012784c740e341952298f1cc77f1f01b1bbc3eb4eecf6984fb7f43
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.11.1"
|
||||||
|
crypto:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: crypto
|
||||||
|
sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.6"
|
||||||
|
file:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file
|
||||||
|
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "7.0.1"
|
||||||
|
frontend_server_client:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: frontend_server_client
|
||||||
|
sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.0"
|
||||||
|
glob:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: glob
|
||||||
|
sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.2"
|
||||||
|
http_multi_server:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http_multi_server
|
||||||
|
sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.2.1"
|
||||||
|
http_parser:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http_parser
|
||||||
|
sha256: "76d306a1c3afb33fe82e2bbacad62a61f409b5634c915fceb0d799de1a913360"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.1.1"
|
||||||
|
io:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: io
|
||||||
|
sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.5"
|
||||||
|
js:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: js
|
||||||
|
sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.7.1"
|
||||||
|
lints:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: lints
|
||||||
|
sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.0"
|
||||||
|
logging:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: logging
|
||||||
|
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.0"
|
||||||
|
macros:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: macros
|
||||||
|
sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.2-main.4"
|
||||||
|
matcher:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: matcher
|
||||||
|
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.12.16+1"
|
||||||
|
meta:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: meta
|
||||||
|
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.16.0"
|
||||||
|
mime:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: mime
|
||||||
|
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.0"
|
||||||
|
node_preamble:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: node_preamble
|
||||||
|
sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.2"
|
||||||
|
package_config:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: package_config
|
||||||
|
sha256: "92d4488434b520a62570293fbd33bb556c7d49230791c1b4bbd973baf6d2dc67"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.1"
|
||||||
|
path:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path
|
||||||
|
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.9.1"
|
||||||
|
pool:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pool
|
||||||
|
sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.5.1"
|
||||||
|
pub_semver:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pub_semver
|
||||||
|
sha256: "7b3cfbf654f3edd0c6298ecd5be782ce997ddf0e00531b9464b55245185bbbbd"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.5"
|
||||||
|
shelf:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf
|
||||||
|
sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.2"
|
||||||
|
shelf_packages_handler:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf_packages_handler
|
||||||
|
sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.2"
|
||||||
|
shelf_static:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf_static
|
||||||
|
sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.3"
|
||||||
|
shelf_web_socket:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf_web_socket
|
||||||
|
sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.1"
|
||||||
|
source_map_stack_trace:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_map_stack_trace
|
||||||
|
sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.2"
|
||||||
|
source_maps:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_maps
|
||||||
|
sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.10.13"
|
||||||
|
source_span:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: source_span
|
||||||
|
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.10.1"
|
||||||
|
stack_trace:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stack_trace
|
||||||
|
sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.12.0"
|
||||||
|
stream_channel:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stream_channel
|
||||||
|
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.2"
|
||||||
|
string_scanner:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: string_scanner
|
||||||
|
sha256: "0bd04f5bb74fcd6ff0606a888a30e917af9bd52820b178eaa464beb11dca84b6"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.0"
|
||||||
|
term_glyph:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: term_glyph
|
||||||
|
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.1"
|
||||||
|
test:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: test
|
||||||
|
sha256: "22eb7769bee38c7e032d532e8daa2e1cc901b799f603550a4db8f3a5f5173ea2"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.25.12"
|
||||||
|
test_api:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: test_api
|
||||||
|
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.7.4"
|
||||||
|
test_core:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: test_core
|
||||||
|
sha256: "84d17c3486c8dfdbe5e12a50c8ae176d15e2a771b96909a9442b40173649ccaa"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.6.8"
|
||||||
|
typed_data:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: typed_data
|
||||||
|
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.0"
|
||||||
|
vm_service:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: vm_service
|
||||||
|
sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "15.0.0"
|
||||||
|
watcher:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: watcher
|
||||||
|
sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0"
|
||||||
|
web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web
|
||||||
|
sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0"
|
||||||
|
web_socket:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web_socket
|
||||||
|
sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.6"
|
||||||
|
web_socket_channel:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web_socket_channel
|
||||||
|
sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.1"
|
||||||
|
webkit_inspection_protocol:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: webkit_inspection_protocol
|
||||||
|
sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.1"
|
||||||
|
yaml:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: yaml
|
||||||
|
sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.2"
|
||||||
|
sdks:
|
||||||
|
dart: ">=3.5.0 <4.0.0"
|
12
common/code_buffer/pubspec.yaml
Normal file
12
common/code_buffer/pubspec.yaml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
name: platform_code_buffer
|
||||||
|
version: 5.2.0
|
||||||
|
description: An advanced StringBuffer geared toward generating code, and source maps.
|
||||||
|
homepage: https://github.com/dart-backend/belatuk-common-utilities/tree/main/packages/code_buffer
|
||||||
|
environment:
|
||||||
|
sdk: '>=3.3.0 <4.0.0'
|
||||||
|
dependencies:
|
||||||
|
charcode: ^1.2.0
|
||||||
|
source_span: ^1.8.1
|
||||||
|
dev_dependencies:
|
||||||
|
test: ^1.24.0
|
||||||
|
lints: ^4.0.0
|
47
common/code_buffer/test/copy_test.dart
Normal file
47
common/code_buffer/test/copy_test.dart
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
import 'package:platform_code_buffer/code_buffer.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
var a = CodeBuffer(), b = CodeBuffer();
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
a.writeln('outer block 1');
|
||||||
|
b
|
||||||
|
..writeln('inner block 1')
|
||||||
|
..writeln('inner block 2');
|
||||||
|
b.copyInto(a..indent());
|
||||||
|
a
|
||||||
|
..outdent()
|
||||||
|
..writeln('outer block 2');
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDown(() {
|
||||||
|
a.clear();
|
||||||
|
b.clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('sets correct text', () {
|
||||||
|
expect(
|
||||||
|
a.toString(),
|
||||||
|
[
|
||||||
|
'outer block 1',
|
||||||
|
' inner block 1',
|
||||||
|
' inner block 2',
|
||||||
|
'outer block 2',
|
||||||
|
].join('\n'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('sets lastLine+lastSpan', () {
|
||||||
|
var c = CodeBuffer()
|
||||||
|
..indent()
|
||||||
|
..write('>')
|
||||||
|
..writeln('innermost');
|
||||||
|
c.copyInto(a);
|
||||||
|
expect(a.lastLine!.text, '>innermost');
|
||||||
|
expect(a.lastLine!.span.start.column, 2);
|
||||||
|
expect(a.lastLine!.lastSpan!.start.line, 4);
|
||||||
|
expect(a.lastLine!.lastSpan!.start.column, 3);
|
||||||
|
expect(a.lastLine!.lastSpan!.end.line, 4);
|
||||||
|
expect(a.lastLine!.lastSpan!.end.column, 12);
|
||||||
|
});
|
||||||
|
}
|
46
common/code_buffer/test/span_test.dart
Normal file
46
common/code_buffer/test/span_test.dart
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
import 'package:charcode/charcode.dart';
|
||||||
|
import 'package:platform_code_buffer/code_buffer.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
var buf = CodeBuffer();
|
||||||
|
tearDown(buf.clear);
|
||||||
|
|
||||||
|
test('writeCharCode', () {
|
||||||
|
buf.writeCharCode($x);
|
||||||
|
expect(buf.lastLine!.lastSpan!.start.column, 0);
|
||||||
|
expect(buf.lastLine!.lastSpan!.start.line, 0);
|
||||||
|
expect(buf.lastLine!.lastSpan!.end.column, 1);
|
||||||
|
expect(buf.lastLine!.lastSpan!.end.line, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('write', () {
|
||||||
|
buf.write('foo');
|
||||||
|
expect(buf.lastLine!.lastSpan!.start.column, 0);
|
||||||
|
expect(buf.lastLine!.lastSpan!.start.line, 0);
|
||||||
|
expect(buf.lastLine!.lastSpan!.end.column, 3);
|
||||||
|
expect(buf.lastLine!.lastSpan!.end.line, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('multiple writes in one line', () {
|
||||||
|
buf
|
||||||
|
..write('foo')
|
||||||
|
..write('baz');
|
||||||
|
expect(buf.lastLine!.lastSpan!.start.column, 3);
|
||||||
|
expect(buf.lastLine!.lastSpan!.start.line, 0);
|
||||||
|
expect(buf.lastLine!.lastSpan!.end.column, 6);
|
||||||
|
expect(buf.lastLine!.lastSpan!.end.line, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('multiple lines', () {
|
||||||
|
buf
|
||||||
|
..writeln('foo')
|
||||||
|
..write('bar')
|
||||||
|
..write('+')
|
||||||
|
..writeln('baz');
|
||||||
|
expect(buf.lastLine!.lastSpan!.start.column, 4);
|
||||||
|
expect(buf.lastLine!.lastSpan!.start.line, 1);
|
||||||
|
expect(buf.lastLine!.lastSpan!.end.column, 7);
|
||||||
|
expect(buf.lastLine!.lastSpan!.end.line, 1);
|
||||||
|
});
|
||||||
|
}
|
90
common/code_buffer/test/write_test.dart
Normal file
90
common/code_buffer/test/write_test.dart
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
import 'package:charcode/charcode.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
import 'package:platform_code_buffer/code_buffer.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
var buf = CodeBuffer();
|
||||||
|
tearDown(buf.clear);
|
||||||
|
|
||||||
|
test('writeCharCode', () {
|
||||||
|
buf.writeCharCode($x);
|
||||||
|
expect(buf.toString(), 'x');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('write', () {
|
||||||
|
buf.write('hello world');
|
||||||
|
expect(buf.toString(), 'hello world');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('custom space', () {
|
||||||
|
var b = CodeBuffer(space: '+')
|
||||||
|
..writeln('foo')
|
||||||
|
..indent()
|
||||||
|
..writeln('baz');
|
||||||
|
expect(b.toString(), 'foo\n+baz');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('custom newline', () {
|
||||||
|
var b = CodeBuffer(newline: 'N')
|
||||||
|
..writeln('foo')
|
||||||
|
..indent()
|
||||||
|
..writeln('baz');
|
||||||
|
expect(b.toString(), 'fooN baz');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('trailing newline', () {
|
||||||
|
var b = CodeBuffer(trailingNewline: true)..writeln('foo');
|
||||||
|
expect(b.toString(), 'foo\n');
|
||||||
|
});
|
||||||
|
|
||||||
|
group('multiple lines', () {
|
||||||
|
setUp(() {
|
||||||
|
buf
|
||||||
|
..writeln('foo')
|
||||||
|
..writeln('bar')
|
||||||
|
..writeln('baz');
|
||||||
|
expect(buf.lines, hasLength(3));
|
||||||
|
expect(buf.lines[0].text, 'foo');
|
||||||
|
expect(buf.lines[1].text, 'bar');
|
||||||
|
expect(buf.lines[2].text, 'baz');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('indent', () {
|
||||||
|
buf
|
||||||
|
..writeln('foo')
|
||||||
|
..indent()
|
||||||
|
..writeln('bar')
|
||||||
|
..indent()
|
||||||
|
..writeln('baz')
|
||||||
|
..outdent()
|
||||||
|
..writeln('quux')
|
||||||
|
..outdent()
|
||||||
|
..writeln('end');
|
||||||
|
expect(buf.toString(), 'foo\n bar\n baz\n quux\nend');
|
||||||
|
});
|
||||||
|
|
||||||
|
group('sets lastLine text', () {
|
||||||
|
test('writeCharCode', () {
|
||||||
|
buf.writeCharCode($x);
|
||||||
|
expect(buf.lastLine!.text, 'x');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('write', () {
|
||||||
|
buf.write('hello world');
|
||||||
|
expect(buf.lastLine!.text, 'hello world');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('sets lastLine lastSpan', () {
|
||||||
|
test('writeCharCode', () {
|
||||||
|
buf.writeCharCode($x);
|
||||||
|
expect(buf.lastLine!.lastSpan!.text, 'x');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('write', () {
|
||||||
|
buf.write('hello world');
|
||||||
|
expect(buf.lastLine!.lastSpan!.text, 'hello world');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
12
common/combinator/AUTHORS.md
Normal file
12
common/combinator/AUTHORS.md
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
Primary Authors
|
||||||
|
===============
|
||||||
|
|
||||||
|
* __[Thomas Hii](dukefirehawk.apps@gmail.com)__
|
||||||
|
|
||||||
|
Thomas is the current maintainer of the code base. He has refactored and migrated the
|
||||||
|
code base to support NNBD.
|
||||||
|
|
||||||
|
* __[Tobe O](thosakwe@gmail.com)__
|
||||||
|
|
||||||
|
Tobe has written much of the original code prior to NNBD migration. He has moved on and
|
||||||
|
is no longer involved with the project.
|
61
common/combinator/CHANGELOG.md
Normal file
61
common/combinator/CHANGELOG.md
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
# Change Log
|
||||||
|
|
||||||
|
## 5.2.0
|
||||||
|
|
||||||
|
* Require Dart >= 3.3
|
||||||
|
* Updated `lints` to 4.0.0
|
||||||
|
|
||||||
|
## 5.1.0
|
||||||
|
|
||||||
|
* Updated `lints` to 3.0.0
|
||||||
|
* Fixed lints warnings
|
||||||
|
|
||||||
|
## 5.0.0
|
||||||
|
|
||||||
|
* Require Dart >= 3.0
|
||||||
|
|
||||||
|
## 5.0.0-beta.1
|
||||||
|
|
||||||
|
* Require Dart >= 3.0
|
||||||
|
* Updated `platform_code_buffer` to 5.0.0
|
||||||
|
|
||||||
|
## 4.0.0
|
||||||
|
|
||||||
|
* Require Dart >= 2.17
|
||||||
|
|
||||||
|
## 3.0.1
|
||||||
|
|
||||||
|
* Fixed license link
|
||||||
|
|
||||||
|
## 3.0.0
|
||||||
|
|
||||||
|
* Upgraded from `pendantic` to `lints` linter
|
||||||
|
* Published as `platform_combinator` package
|
||||||
|
* Resolved static analysis warnings
|
||||||
|
|
||||||
|
## 2.0.2
|
||||||
|
|
||||||
|
* Resolved static analysis warnings
|
||||||
|
|
||||||
|
## 2.0.1
|
||||||
|
|
||||||
|
* Updated README
|
||||||
|
|
||||||
|
## 2.0.0
|
||||||
|
|
||||||
|
* Migrated to support Dart SDK 2.12.x NNBD
|
||||||
|
|
||||||
|
## 1.1.0
|
||||||
|
|
||||||
|
* Add `tupleX` parsers. Hooray for strong typing!
|
||||||
|
|
||||||
|
## 1.0.0+3
|
||||||
|
|
||||||
|
* `then` now *always* returns `dynamic`.
|
||||||
|
|
||||||
|
## 1.0.0+2
|
||||||
|
|
||||||
|
* `star` now includes with a call to `opt`.
|
||||||
|
* Added comments.
|
||||||
|
* Enforce generics on `separatedBy`.
|
||||||
|
* Enforce Dart 2 semantics.
|
29
common/combinator/LICENSE
Normal file
29
common/combinator/LICENSE
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
BSD 3-Clause License
|
||||||
|
|
||||||
|
Copyright (c) 2021, dukefirehawk.com
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
3. Neither the name of the copyright holder nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
129
common/combinator/README.md
Normal file
129
common/combinator/README.md
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
# Belatuk Combinator
|
||||||
|
|
||||||
|
![Pub Version (including pre-releases)](https://img.shields.io/pub/v/platform_combinator?include_prereleases)
|
||||||
|
[![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety)
|
||||||
|
[![License](https://img.shields.io/github/license/dart-backend/belatuk-common-utilities)](https://github.com/dart-backend/belatuk-common-utilities/blob/main/packages/combinator/LICENSE)
|
||||||
|
|
||||||
|
**Replacement of `package:combinator` with breaking changes to support NNBD.**
|
||||||
|
|
||||||
|
Packrat parser combinators that support static typing, generics, file spans, memoization, and more.
|
||||||
|
|
||||||
|
**RECOMMENDED:**
|
||||||
|
Check `example/` for examples. The examples contain examples of using:
|
||||||
|
|
||||||
|
* Generic typing
|
||||||
|
* Reading `FileSpan` from `ParseResult`
|
||||||
|
* More...
|
||||||
|
|
||||||
|
## Basic Usage
|
||||||
|
|
||||||
|
```dart
|
||||||
|
void main() {
|
||||||
|
// Parse a Pattern (usually String or RegExp).
|
||||||
|
var foo = match('foo');
|
||||||
|
var number = match(RegExp(r'[0-9]+'), errorMessage: 'Expected a number.');
|
||||||
|
|
||||||
|
// Set a value.
|
||||||
|
var numWithValue = number.map((r) => int.parse(r.span.text));
|
||||||
|
|
||||||
|
// Expect a pattern, or nothing.
|
||||||
|
var optional = numWithValue.opt();
|
||||||
|
|
||||||
|
// Expect a pattern zero or more times.
|
||||||
|
var star = optional.star();
|
||||||
|
|
||||||
|
// Expect one or more times.
|
||||||
|
var plus = optional.plus();
|
||||||
|
|
||||||
|
// Expect an arbitrary number of times.
|
||||||
|
var threeTimes = optional.times(3);
|
||||||
|
|
||||||
|
// Expect a sequence of patterns.
|
||||||
|
var doraTheExplorer = chain([
|
||||||
|
match('Dora').space(),
|
||||||
|
match('the').space(),
|
||||||
|
match('Explorer').space(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Choose exactly one of a set of patterns, whichever
|
||||||
|
// appears first.
|
||||||
|
var alt = any([
|
||||||
|
match('1'),
|
||||||
|
match('11'),
|
||||||
|
match('111'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Choose the *longest* match for any of the given alternatives.
|
||||||
|
var alt2 = longest([
|
||||||
|
match('1'),
|
||||||
|
match('11'),
|
||||||
|
match('111'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Friendly operators
|
||||||
|
var fooOrNumber = foo | number;
|
||||||
|
var fooAndNumber = foo & number;
|
||||||
|
var notFoo = ~foo;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Messages
|
||||||
|
|
||||||
|
Parsers without descriptive error messages can lead to frustrating dead-ends
|
||||||
|
for end-users. Fortunately, `platform_combinator` is built with error handling in mind.
|
||||||
|
|
||||||
|
```dart
|
||||||
|
void main(Parser parser) {
|
||||||
|
// Append an arbitrary error message to a parser if it is not matched.
|
||||||
|
var withError = parser.error(errorMessage: 'Hey!!! Wrong!!!');
|
||||||
|
|
||||||
|
// You can also set the severity of an error.
|
||||||
|
var asHint = parser.error(severity: SyntaxErrorSeverity.hint);
|
||||||
|
|
||||||
|
// Constructs like `any`, `chain`, and `longest` support this as well.
|
||||||
|
var foo = longest([
|
||||||
|
parser.error(errorMessage: 'foo'),
|
||||||
|
parser.error(errorMessage: 'bar')
|
||||||
|
], errorMessage: 'Expected a "foo" or a "bar"');
|
||||||
|
|
||||||
|
// If multiple errors are present at one location,
|
||||||
|
// it can create a lot of noise.
|
||||||
|
//
|
||||||
|
// Use `foldErrors` to only take one error at a given location.
|
||||||
|
var lessNoise = parser.foldErrors();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Whitespaces
|
||||||
|
|
||||||
|
Handling optional whitespace is dead-easy:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
void main(Parser parser) {
|
||||||
|
var optionalSpace = parser.space();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## For Programming Languages
|
||||||
|
|
||||||
|
`platform_combinator` was conceived to make writing parsers for complex grammars easier,
|
||||||
|
namely programming languages. Thus, there are functions built-in to make common constructs
|
||||||
|
easier:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
void main(Parser parser) {
|
||||||
|
var array = parser
|
||||||
|
.separatedByComma()
|
||||||
|
.surroundedBySquareBrackets(defaultValue: []);
|
||||||
|
|
||||||
|
var braces = parser.surroundedByCurlyBraces();
|
||||||
|
|
||||||
|
var sep = parser.separatedBy(match('!').space());
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Differences between this and Petitparser
|
||||||
|
|
||||||
|
* `platform_combinator` makes extensive use of Dart's dynamic typing
|
||||||
|
* `platform_combinator` supports detailed error messages (with configurable severity)
|
||||||
|
* `platform_combinator` keeps track of locations (ex. `line 1: 3`)
|
1
common/combinator/analysis_options.yaml
Normal file
1
common/combinator/analysis_options.yaml
Normal file
|
@ -0,0 +1 @@
|
||||||
|
include: package:lints/recommended.yaml
|
14
common/combinator/combinator.iml
Normal file
14
common/combinator/combinator.iml
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="WEB_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||||
|
<exclude-output />
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.dart_tool" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.pub" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
<orderEntry type="library" name="Dart SDK" level="project" />
|
||||||
|
<orderEntry type="library" name="Dart Packages" level="project" />
|
||||||
|
</component>
|
||||||
|
</module>
|
56
common/combinator/example/basic_auth.dart
Normal file
56
common/combinator/example/basic_auth.dart
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
// Run this with "Basic QWxhZGRpbjpPcGVuU2VzYW1l"
|
||||||
|
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:platform_combinator/combinator.dart';
|
||||||
|
import 'package:string_scanner/string_scanner.dart';
|
||||||
|
|
||||||
|
/// Parse a part of a decoded Basic auth string.
|
||||||
|
///
|
||||||
|
/// Namely, the `username` or `password` in `{username}:{password}`.
|
||||||
|
final Parser<String> string =
|
||||||
|
match<String>(RegExp(r'[^:$]+'), errorMessage: 'Expected a string.')
|
||||||
|
.value((r) => r.span!.text);
|
||||||
|
|
||||||
|
/// Transforms `{username}:{password}` to `{"username": username, "password": password}`.
|
||||||
|
final Parser<Map<String, String>> credentials = chain<String>([
|
||||||
|
string.opt(),
|
||||||
|
match<String>(':'),
|
||||||
|
string.opt(),
|
||||||
|
]).map<Map<String, String>>(
|
||||||
|
(r) => {'username': r.value![0], 'password': r.value![2]});
|
||||||
|
|
||||||
|
/// We can actually embed a parser within another parser.
|
||||||
|
///
|
||||||
|
/// This is used here to BASE64URL-decode a string, and then
|
||||||
|
/// parse the decoded string.
|
||||||
|
final Parser credentialString = match<Map<String, String>?>(
|
||||||
|
RegExp(r'([^\n$]+)'),
|
||||||
|
errorMessage: 'Expected a credential string.')
|
||||||
|
.value((r) {
|
||||||
|
var decoded = utf8.decode(base64Url.decode(r.span!.text));
|
||||||
|
var scanner = SpanScanner(decoded);
|
||||||
|
return credentials.parse(scanner).value;
|
||||||
|
});
|
||||||
|
|
||||||
|
final Parser basic = match<void>('Basic').space();
|
||||||
|
|
||||||
|
final Parser basicAuth = basic.then(credentialString).index(1);
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
while (true) {
|
||||||
|
stdout.write('Enter a basic auth value: ');
|
||||||
|
var line = stdin.readLineSync()!;
|
||||||
|
var scanner = SpanScanner(line, sourceUrl: 'stdin');
|
||||||
|
var result = basicAuth.parse(scanner);
|
||||||
|
|
||||||
|
if (!result.successful) {
|
||||||
|
for (var error in result.errors) {
|
||||||
|
print(error.toolString);
|
||||||
|
print(error.span!.highlight(color: true));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print(result.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
71
common/combinator/example/calculator.dart
Normal file
71
common/combinator/example/calculator.dart
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
import 'dart:math';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:platform_combinator/combinator.dart';
|
||||||
|
import 'package:string_scanner/string_scanner.dart';
|
||||||
|
|
||||||
|
/// Note: This grammar does not handle precedence, for the sake of simplicity.
|
||||||
|
Parser<num> calculatorGrammar() {
|
||||||
|
var expr = reference<num>();
|
||||||
|
|
||||||
|
var number = match<num>(RegExp(r'-?[0-9]+(\.[0-9]+)?'))
|
||||||
|
.value((r) => num.parse(r.span!.text));
|
||||||
|
|
||||||
|
var hex = match<int>(RegExp(r'0x([A-Fa-f0-9]+)'))
|
||||||
|
.map((r) => int.parse(r.scanner.lastMatch![1]!, radix: 16));
|
||||||
|
|
||||||
|
var binary = match<int>(RegExp(r'([0-1]+)b'))
|
||||||
|
.map((r) => int.parse(r.scanner.lastMatch![1]!, radix: 2));
|
||||||
|
|
||||||
|
var alternatives = <Parser<num>>[];
|
||||||
|
|
||||||
|
void registerBinary(String op, num Function(num, num) f) {
|
||||||
|
alternatives.add(
|
||||||
|
chain<num>([
|
||||||
|
expr.space(),
|
||||||
|
match<void>(op).space() as Parser<num>,
|
||||||
|
expr.space(),
|
||||||
|
]).map((r) => f(r.value![0], r.value![2])),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
registerBinary('**', (a, b) => pow(a, b));
|
||||||
|
registerBinary('*', (a, b) => a * b);
|
||||||
|
registerBinary('/', (a, b) => a / b);
|
||||||
|
registerBinary('%', (a, b) => a % b);
|
||||||
|
registerBinary('+', (a, b) => a + b);
|
||||||
|
registerBinary('-', (a, b) => a - b);
|
||||||
|
registerBinary('^', (a, b) => a.toInt() ^ b.toInt());
|
||||||
|
registerBinary('&', (a, b) => a.toInt() & b.toInt());
|
||||||
|
registerBinary('|', (a, b) => a.toInt() | b.toInt());
|
||||||
|
|
||||||
|
alternatives.addAll([
|
||||||
|
number,
|
||||||
|
hex,
|
||||||
|
binary,
|
||||||
|
expr.parenthesized(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
expr.parser = longest(alternatives);
|
||||||
|
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
var calculator = calculatorGrammar();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
stdout.write('Enter an expression: ');
|
||||||
|
var line = stdin.readLineSync()!;
|
||||||
|
var scanner = SpanScanner(line, sourceUrl: 'stdin');
|
||||||
|
var result = calculator.parse(scanner);
|
||||||
|
|
||||||
|
if (!result.successful) {
|
||||||
|
for (var error in result.errors) {
|
||||||
|
stderr.writeln(error.toolString);
|
||||||
|
stderr.writeln(error.span!.highlight(color: true));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print(result.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
29
common/combinator/example/delimiter.dart
Normal file
29
common/combinator/example/delimiter.dart
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:platform_combinator/combinator.dart';
|
||||||
|
import 'package:string_scanner/string_scanner.dart';
|
||||||
|
|
||||||
|
final Parser<String> id =
|
||||||
|
match<String>(RegExp(r'[A-Za-z]+')).value((r) => r.span!.text);
|
||||||
|
|
||||||
|
// We can use `separatedBy` to easily construct parser
|
||||||
|
// that can be matched multiple times, separated by another
|
||||||
|
// pattern.
|
||||||
|
//
|
||||||
|
// This is useful for parsing arrays or map literals.
|
||||||
|
void main() {
|
||||||
|
while (true) {
|
||||||
|
stdout.write('Enter a string (ex "a,b,c"): ');
|
||||||
|
var line = stdin.readLineSync()!;
|
||||||
|
var scanner = SpanScanner(line, sourceUrl: 'stdin');
|
||||||
|
var result = id.separatedBy(match(',').space()).parse(scanner);
|
||||||
|
|
||||||
|
if (!result.successful) {
|
||||||
|
for (var error in result.errors) {
|
||||||
|
print(error.toolString);
|
||||||
|
print(error.span!.highlight(color: true));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print(result.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
71
common/combinator/example/json.dart
Normal file
71
common/combinator/example/json.dart
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:platform_combinator/combinator.dart';
|
||||||
|
import 'package:string_scanner/string_scanner.dart';
|
||||||
|
|
||||||
|
Parser jsonGrammar() {
|
||||||
|
var expr = reference();
|
||||||
|
|
||||||
|
// Parse a number
|
||||||
|
var number = match<num>(RegExp(r'-?[0-9]+(\.[0-9]+)?'),
|
||||||
|
errorMessage: 'Expected a number.')
|
||||||
|
.value(
|
||||||
|
(r) => num.parse(r.span!.text),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Parse a string (no escapes supported, because lazy).
|
||||||
|
var string =
|
||||||
|
match(RegExp(r'"[^"]*"'), errorMessage: 'Expected a string.').value(
|
||||||
|
(r) => r.span!.text.substring(1, r.span!.text.length - 1),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Parse an array
|
||||||
|
var array = expr
|
||||||
|
.space()
|
||||||
|
.separatedByComma()
|
||||||
|
.surroundedBySquareBrackets(defaultValue: []);
|
||||||
|
|
||||||
|
// KV pair
|
||||||
|
var keyValuePair = chain([
|
||||||
|
string.space(),
|
||||||
|
match(':').space(),
|
||||||
|
expr.error(errorMessage: 'Missing expression.'),
|
||||||
|
]).castDynamic().cast<Map>().value((r) => {r.value![0]: r.value![2]});
|
||||||
|
|
||||||
|
// Parse an object.
|
||||||
|
var object = keyValuePair
|
||||||
|
.separatedByComma()
|
||||||
|
.castDynamic()
|
||||||
|
.surroundedByCurlyBraces(defaultValue: {});
|
||||||
|
|
||||||
|
expr.parser = longest(
|
||||||
|
[
|
||||||
|
array,
|
||||||
|
number,
|
||||||
|
string,
|
||||||
|
object.error(),
|
||||||
|
],
|
||||||
|
errorMessage: 'Expected an expression.',
|
||||||
|
).space();
|
||||||
|
|
||||||
|
return expr.foldErrors();
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
var json = jsonGrammar();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
stdout.write('Enter some JSON: ');
|
||||||
|
var line = stdin.readLineSync()!;
|
||||||
|
var scanner = SpanScanner(line, sourceUrl: 'stdin');
|
||||||
|
var result = json.parse(scanner);
|
||||||
|
|
||||||
|
if (!result.successful) {
|
||||||
|
for (var error in result.errors) {
|
||||||
|
print(error.toolString);
|
||||||
|
print(error.span!.highlight(color: true));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print(result.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
38
common/combinator/example/main.dart
Normal file
38
common/combinator/example/main.dart
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:platform_combinator/combinator.dart';
|
||||||
|
import 'package:string_scanner/string_scanner.dart';
|
||||||
|
|
||||||
|
final Parser minus = match('-');
|
||||||
|
|
||||||
|
final Parser<int> digit =
|
||||||
|
match(RegExp(r'[0-9]'), errorMessage: 'Expected a number');
|
||||||
|
|
||||||
|
final Parser digits = digit.plus();
|
||||||
|
|
||||||
|
final Parser dot = match('.');
|
||||||
|
|
||||||
|
final Parser decimal = ( // digits, (dot, digits)?
|
||||||
|
digits & (dot & digits).opt() //
|
||||||
|
);
|
||||||
|
|
||||||
|
final Parser number = //
|
||||||
|
(minus.opt() & decimal) // minus?, decimal
|
||||||
|
.map<num>((r) => num.parse(r.span!.text));
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
while (true) {
|
||||||
|
stdout.write('Enter a number: ');
|
||||||
|
var line = stdin.readLineSync()!;
|
||||||
|
var scanner = SpanScanner(line, sourceUrl: 'stdin');
|
||||||
|
var result = number.parse(scanner);
|
||||||
|
|
||||||
|
if (!result.successful) {
|
||||||
|
for (var error in result.errors) {
|
||||||
|
stderr.writeln(error.toolString);
|
||||||
|
stderr.writeln(error.span!.highlight(color: true));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print(result.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
45
common/combinator/example/query_string.dart
Normal file
45
common/combinator/example/query_string.dart
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
// For some reason, this cannot be run in checked mode???
|
||||||
|
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:platform_combinator/combinator.dart';
|
||||||
|
import 'package:string_scanner/string_scanner.dart';
|
||||||
|
|
||||||
|
final Parser<String> key =
|
||||||
|
match<String>(RegExp(r'[^=&\n]+'), errorMessage: 'Missing k/v')
|
||||||
|
.value((r) => r.span!.text);
|
||||||
|
|
||||||
|
final Parser value = key.map((r) => Uri.decodeQueryComponent(r.value!));
|
||||||
|
|
||||||
|
final Parser pair = chain([
|
||||||
|
key,
|
||||||
|
match('='),
|
||||||
|
value,
|
||||||
|
]).map((r) {
|
||||||
|
return {
|
||||||
|
r.value![0]: r.value![2],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
final Parser pairs = pair
|
||||||
|
.separatedBy(match(r'&'))
|
||||||
|
.map((r) => r.value!.reduce((a, b) => a..addAll(b)));
|
||||||
|
|
||||||
|
final Parser queryString = pairs.opt();
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
while (true) {
|
||||||
|
stdout.write('Enter a query string: ');
|
||||||
|
var line = stdin.readLineSync()!;
|
||||||
|
var scanner = SpanScanner(line, sourceUrl: 'stdin');
|
||||||
|
var result = pairs.parse(scanner);
|
||||||
|
|
||||||
|
if (!result.successful) {
|
||||||
|
for (var error in result.errors) {
|
||||||
|
print(error.toolString);
|
||||||
|
print(error.span!.highlight(color: true));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print(result.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
85
common/combinator/example/sexp.dart
Normal file
85
common/combinator/example/sexp.dart
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
import 'dart:collection';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'dart:math';
|
||||||
|
import 'package:platform_combinator/combinator.dart';
|
||||||
|
import 'package:string_scanner/string_scanner.dart';
|
||||||
|
import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
var expr = reference();
|
||||||
|
var symbols = <String, dynamic>{};
|
||||||
|
|
||||||
|
void registerFunction(String name, int nArgs, Function(List<num>) f) {
|
||||||
|
symbols[name] = Tuple2(nArgs, f);
|
||||||
|
}
|
||||||
|
|
||||||
|
registerFunction('**', 2, (args) => pow(args[0], args[1]));
|
||||||
|
registerFunction('*', 2, (args) => args[0] * args[1]);
|
||||||
|
registerFunction('/', 2, (args) => args[0] / args[1]);
|
||||||
|
registerFunction('%', 2, (args) => args[0] % args[1]);
|
||||||
|
registerFunction('+', 2, (args) => args[0] + args[1]);
|
||||||
|
registerFunction('-', 2, (args) => args[0] - args[1]);
|
||||||
|
registerFunction('.', 1, (args) => args[0].toDouble());
|
||||||
|
registerFunction('print', 1, (args) {
|
||||||
|
print(args[0]);
|
||||||
|
return args[0];
|
||||||
|
});
|
||||||
|
|
||||||
|
var number =
|
||||||
|
match(RegExp(r'[0-9]+(\.[0-9]+)?'), errorMessage: 'Expected a number.')
|
||||||
|
.map((r) => num.parse(r.span!.text));
|
||||||
|
|
||||||
|
var id = match(
|
||||||
|
RegExp(
|
||||||
|
r'[A-Za-z_!\\$",\\+-\\./:;\\?<>%&\\*@\[\]\\{\}\\|`\\^~][A-Za-z0-9_!\\$",\\+-\\./:;\\?<>%&\*@\[\]\\{\}\\|`\\^~]*'),
|
||||||
|
errorMessage: 'Expected an ID')
|
||||||
|
.map((r) => symbols[r.span!.text] ??=
|
||||||
|
throw "Undefined symbol: '${r.span!.text}'");
|
||||||
|
|
||||||
|
var atom = number.castDynamic().or(id);
|
||||||
|
|
||||||
|
var list = expr.space().times(2, exact: false).map((r) {
|
||||||
|
try {
|
||||||
|
var out = [];
|
||||||
|
var q = Queue.from(r.value!.reversed);
|
||||||
|
|
||||||
|
while (q.isNotEmpty) {
|
||||||
|
var current = q.removeFirst();
|
||||||
|
if (current is! Tuple2) {
|
||||||
|
out.insert(0, current);
|
||||||
|
} else {
|
||||||
|
var args = [];
|
||||||
|
for (var i = 0; i < (current.item1 as num); i++) {
|
||||||
|
args.add(out.removeLast());
|
||||||
|
}
|
||||||
|
out.add(current.item2(args));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out.length == 1 ? out.first : out;
|
||||||
|
} catch (_) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expr.parser = longest([
|
||||||
|
list,
|
||||||
|
atom,
|
||||||
|
expr.parenthesized(),
|
||||||
|
]); //list | atom | expr.parenthesized();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
stdout.write('> ');
|
||||||
|
var line = stdin.readLineSync()!;
|
||||||
|
var result = expr.parse(SpanScanner(line));
|
||||||
|
|
||||||
|
if (result.errors.isNotEmpty) {
|
||||||
|
for (var error in result.errors) {
|
||||||
|
print(error.toolString);
|
||||||
|
print(error.message);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print(result.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
14
common/combinator/example/tuple.dart
Normal file
14
common/combinator/example/tuple.dart
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import 'package:platform_combinator/combinator.dart';
|
||||||
|
import 'package:string_scanner/string_scanner.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
var pub = match('pub').map((r) => r.span!.text).space();
|
||||||
|
var dart = match('dart').map((r) => 24).space();
|
||||||
|
var lang = match('lang').map((r) => true).space();
|
||||||
|
|
||||||
|
// Parses a Tuple3<String, int, bool>
|
||||||
|
var grammar = tuple3(pub, dart, lang);
|
||||||
|
|
||||||
|
var scanner = SpanScanner('pub dart lang');
|
||||||
|
print(grammar.parse(scanner).value);
|
||||||
|
}
|
2
common/combinator/lib/combinator.dart
Normal file
2
common/combinator/lib/combinator.dart
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export 'src/combinator/combinator.dart';
|
||||||
|
export 'src/error.dart';
|
26
common/combinator/lib/src/combinator/advance.dart
Normal file
26
common/combinator/lib/src/combinator/advance.dart
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
part of 'combinator.dart';
|
||||||
|
|
||||||
|
class _Advance<T> extends Parser<T> {
|
||||||
|
final Parser<T> parser;
|
||||||
|
final int amount;
|
||||||
|
|
||||||
|
_Advance(this.parser, this.amount);
|
||||||
|
|
||||||
|
@override
|
||||||
|
ParseResult<T> __parse(ParseArgs args) {
|
||||||
|
var result = parser._parse(args.increaseDepth()).change(parser: this);
|
||||||
|
if (result.successful) args.scanner.position += amount;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void stringify(CodeBuffer buffer) {
|
||||||
|
buffer
|
||||||
|
..writeln('advance($amount) (')
|
||||||
|
..indent();
|
||||||
|
parser.stringify(buffer);
|
||||||
|
buffer
|
||||||
|
..outdent()
|
||||||
|
..writeln(')');
|
||||||
|
}
|
||||||
|
}
|
85
common/combinator/lib/src/combinator/any.dart
Normal file
85
common/combinator/lib/src/combinator/any.dart
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
part of 'combinator.dart';
|
||||||
|
|
||||||
|
/// Matches any one of the given [parsers].
|
||||||
|
///
|
||||||
|
/// If [backtrack] is `true` (default), a failed parse will not modify the scanner state.
|
||||||
|
///
|
||||||
|
/// You can provide a custom [errorMessage]. You can set it to `false` to not
|
||||||
|
/// generate any error at all.
|
||||||
|
Parser<T> any<T>(Iterable<Parser<T>> parsers,
|
||||||
|
{bool backtrack = true, errorMessage, SyntaxErrorSeverity? severity}) {
|
||||||
|
return _Any(parsers, backtrack != false, errorMessage,
|
||||||
|
severity ?? SyntaxErrorSeverity.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _Any<T> extends Parser<T> {
|
||||||
|
final Iterable<Parser<T>> parsers;
|
||||||
|
final bool backtrack;
|
||||||
|
final dynamic errorMessage;
|
||||||
|
final SyntaxErrorSeverity severity;
|
||||||
|
|
||||||
|
_Any(this.parsers, this.backtrack, this.errorMessage, this.severity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
ParseResult<T> _parse(ParseArgs args) {
|
||||||
|
var inactive = parsers
|
||||||
|
.where((p) => !args.trampoline.isActive(p, args.scanner.position));
|
||||||
|
|
||||||
|
if (inactive.isEmpty) {
|
||||||
|
return ParseResult(args.trampoline, args.scanner, this, false, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
var errors = <SyntaxError>[];
|
||||||
|
var replay = args.scanner.position;
|
||||||
|
|
||||||
|
for (var parser in inactive) {
|
||||||
|
var result = parser._parse(args.increaseDepth());
|
||||||
|
|
||||||
|
if (result.successful) {
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
if (backtrack) args.scanner.position = replay;
|
||||||
|
if (parser is _Alt) errors.addAll(result.errors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errorMessage != false) {
|
||||||
|
errors.add(
|
||||||
|
SyntaxError(
|
||||||
|
severity,
|
||||||
|
errorMessage?.toString() ??
|
||||||
|
'No match found for ${parsers.length} alternative(s)',
|
||||||
|
args.scanner.emptySpan,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ParseResult(args.trampoline, args.scanner, this, false, errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
ParseResult<T> __parse(ParseArgs args) {
|
||||||
|
// Never called
|
||||||
|
throw ArgumentError('[Combinator] Invalid method call');
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void stringify(CodeBuffer buffer) {
|
||||||
|
buffer
|
||||||
|
..writeln('any(${parsers.length}) (')
|
||||||
|
..indent();
|
||||||
|
var i = 1;
|
||||||
|
|
||||||
|
for (var parser in parsers) {
|
||||||
|
buffer
|
||||||
|
..writeln('#${i++}:')
|
||||||
|
..indent();
|
||||||
|
parser.stringify(buffer);
|
||||||
|
buffer.outdent();
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer
|
||||||
|
..outdent()
|
||||||
|
..writeln(')');
|
||||||
|
}
|
||||||
|
}
|
26
common/combinator/lib/src/combinator/cache.dart
Normal file
26
common/combinator/lib/src/combinator/cache.dart
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
part of 'combinator.dart';
|
||||||
|
|
||||||
|
class _Cache<T> extends Parser<T> {
|
||||||
|
final Map<int, ParseResult<T>> _cache = {};
|
||||||
|
final Parser<T> parser;
|
||||||
|
|
||||||
|
_Cache(this.parser);
|
||||||
|
|
||||||
|
@override
|
||||||
|
ParseResult<T> __parse(ParseArgs args) {
|
||||||
|
return _cache.putIfAbsent(args.scanner.position, () {
|
||||||
|
return parser._parse(args.increaseDepth());
|
||||||
|
}).change(parser: this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void stringify(CodeBuffer buffer) {
|
||||||
|
buffer
|
||||||
|
..writeln('cache(${_cache.length}) (')
|
||||||
|
..indent();
|
||||||
|
parser.stringify(buffer);
|
||||||
|
buffer
|
||||||
|
..outdent()
|
||||||
|
..writeln(')');
|
||||||
|
}
|
||||||
|
}
|
63
common/combinator/lib/src/combinator/cast.dart
Normal file
63
common/combinator/lib/src/combinator/cast.dart
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
part of 'combinator.dart';
|
||||||
|
|
||||||
|
class _Cast<T, U extends T> extends Parser<U> {
|
||||||
|
final Parser<T> parser;
|
||||||
|
|
||||||
|
_Cast(this.parser);
|
||||||
|
|
||||||
|
@override
|
||||||
|
ParseResult<U> __parse(ParseArgs args) {
|
||||||
|
var result = parser._parse(args.increaseDepth());
|
||||||
|
return ParseResult<U>(
|
||||||
|
args.trampoline,
|
||||||
|
args.scanner,
|
||||||
|
this,
|
||||||
|
result.successful,
|
||||||
|
result.errors,
|
||||||
|
span: result.span,
|
||||||
|
value: result.value as U?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void stringify(CodeBuffer buffer) {
|
||||||
|
buffer
|
||||||
|
..writeln('cast<$U> (')
|
||||||
|
..indent();
|
||||||
|
parser.stringify(buffer);
|
||||||
|
buffer
|
||||||
|
..outdent()
|
||||||
|
..writeln(')');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CastDynamic<T> extends Parser<dynamic> {
|
||||||
|
final Parser<T> parser;
|
||||||
|
|
||||||
|
_CastDynamic(this.parser);
|
||||||
|
|
||||||
|
@override
|
||||||
|
ParseResult<dynamic> __parse(ParseArgs args) {
|
||||||
|
var result = parser._parse(args.increaseDepth());
|
||||||
|
return ParseResult<dynamic>(
|
||||||
|
args.trampoline,
|
||||||
|
args.scanner,
|
||||||
|
this,
|
||||||
|
result.successful,
|
||||||
|
result.errors,
|
||||||
|
span: result.span,
|
||||||
|
value: result.value,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void stringify(CodeBuffer buffer) {
|
||||||
|
buffer
|
||||||
|
..writeln('cast<dynamic> (')
|
||||||
|
..indent();
|
||||||
|
parser.stringify(buffer);
|
||||||
|
buffer
|
||||||
|
..outdent()
|
||||||
|
..writeln(')');
|
||||||
|
}
|
||||||
|
}
|
111
common/combinator/lib/src/combinator/chain.dart
Normal file
111
common/combinator/lib/src/combinator/chain.dart
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
part of 'combinator.dart';
|
||||||
|
|
||||||
|
/// Expects to parse a sequence of [parsers].
|
||||||
|
///
|
||||||
|
/// If [failFast] is `true` (default), then the first failure to parse will abort the parse.
|
||||||
|
ListParser<T> chain<T>(Iterable<Parser<T>> parsers,
|
||||||
|
{bool failFast = true, SyntaxErrorSeverity? severity}) {
|
||||||
|
return _Chain<T>(
|
||||||
|
parsers, failFast != false, severity ?? SyntaxErrorSeverity.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _Alt<T> extends Parser<T> {
|
||||||
|
final Parser<T> parser;
|
||||||
|
final String? errorMessage;
|
||||||
|
final SyntaxErrorSeverity severity;
|
||||||
|
|
||||||
|
_Alt(this.parser, this.errorMessage, this.severity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
ParseResult<T> __parse(ParseArgs args) {
|
||||||
|
var result = parser._parse(args.increaseDepth());
|
||||||
|
return result.successful
|
||||||
|
? result
|
||||||
|
: result.addErrors([
|
||||||
|
SyntaxError(
|
||||||
|
severity, errorMessage, result.span ?? args.scanner.emptySpan),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void stringify(CodeBuffer buffer) {
|
||||||
|
parser.stringify(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _Chain<T> extends ListParser<T> {
|
||||||
|
final Iterable<Parser<T>> parsers;
|
||||||
|
final bool failFast;
|
||||||
|
final SyntaxErrorSeverity severity;
|
||||||
|
|
||||||
|
_Chain(this.parsers, this.failFast, this.severity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
ParseResult<List<T>> __parse(ParseArgs args) {
|
||||||
|
var errors = <SyntaxError>[];
|
||||||
|
var results = <T>[];
|
||||||
|
var spans = <FileSpan>[];
|
||||||
|
var successful = true;
|
||||||
|
|
||||||
|
for (var parser in parsers) {
|
||||||
|
var result = parser._parse(args.increaseDepth());
|
||||||
|
|
||||||
|
if (!result.successful) {
|
||||||
|
if (parser is _Alt) errors.addAll(result.errors);
|
||||||
|
|
||||||
|
if (failFast) {
|
||||||
|
return ParseResult(
|
||||||
|
args.trampoline, args.scanner, this, false, result.errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
successful = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.value != null) {
|
||||||
|
results.add(result.value as T);
|
||||||
|
} else {
|
||||||
|
results.add('NULL' as T);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.span != null) {
|
||||||
|
spans.add(result.span!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FileSpan? span;
|
||||||
|
|
||||||
|
if (spans.isNotEmpty) {
|
||||||
|
span = spans.reduce((a, b) => a.expand(b));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ParseResult<List<T>>(
|
||||||
|
args.trampoline,
|
||||||
|
args.scanner,
|
||||||
|
this,
|
||||||
|
successful,
|
||||||
|
errors,
|
||||||
|
span: span,
|
||||||
|
value: List<T>.unmodifiable(results),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void stringify(CodeBuffer buffer) {
|
||||||
|
buffer
|
||||||
|
..writeln('chain(${parsers.length}) (')
|
||||||
|
..indent();
|
||||||
|
var i = 1;
|
||||||
|
|
||||||
|
for (var parser in parsers) {
|
||||||
|
buffer
|
||||||
|
..writeln('#${i++}:')
|
||||||
|
..indent();
|
||||||
|
parser.stringify(buffer);
|
||||||
|
buffer.outdent();
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer
|
||||||
|
..outdent()
|
||||||
|
..writeln(')');
|
||||||
|
}
|
||||||
|
}
|
42
common/combinator/lib/src/combinator/check.dart
Normal file
42
common/combinator/lib/src/combinator/check.dart
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
part of 'combinator.dart';
|
||||||
|
|
||||||
|
class _Check<T> extends Parser<T> {
|
||||||
|
final Parser<T> parser;
|
||||||
|
final Matcher matcher;
|
||||||
|
final String? errorMessage;
|
||||||
|
final SyntaxErrorSeverity severity;
|
||||||
|
|
||||||
|
_Check(this.parser, this.matcher, this.errorMessage, this.severity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
ParseResult<T> __parse(ParseArgs args) {
|
||||||
|
var matchState = {};
|
||||||
|
var result = parser._parse(args.increaseDepth()).change(parser: this);
|
||||||
|
if (!result.successful) {
|
||||||
|
return result;
|
||||||
|
} else if (!matcher.matches(result.value, matchState)) {
|
||||||
|
return result.change(successful: false).addErrors([
|
||||||
|
SyntaxError(
|
||||||
|
severity,
|
||||||
|
errorMessage ??
|
||||||
|
'${matcher.describe(StringDescription('Expected '))}.',
|
||||||
|
result.span,
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void stringify(CodeBuffer buffer) {
|
||||||
|
var d = matcher.describe(StringDescription());
|
||||||
|
buffer
|
||||||
|
..writeln('check($d) (')
|
||||||
|
..indent();
|
||||||
|
parser.stringify(buffer);
|
||||||
|
buffer
|
||||||
|
..outdent()
|
||||||
|
..writeln(')');
|
||||||
|
}
|
||||||
|
}
|
394
common/combinator/lib/src/combinator/combinator.dart
Normal file
394
common/combinator/lib/src/combinator/combinator.dart
Normal file
|
@ -0,0 +1,394 @@
|
||||||
|
library lex.src.combinator;
|
||||||
|
|
||||||
|
import 'dart:collection';
|
||||||
|
|
||||||
|
import 'package:platform_code_buffer/code_buffer.dart';
|
||||||
|
import 'package:matcher/matcher.dart';
|
||||||
|
import 'package:source_span/source_span.dart';
|
||||||
|
import 'package:string_scanner/string_scanner.dart';
|
||||||
|
import 'package:tuple/tuple.dart';
|
||||||
|
import '../error.dart';
|
||||||
|
|
||||||
|
part 'any.dart';
|
||||||
|
|
||||||
|
part 'advance.dart';
|
||||||
|
|
||||||
|
part 'cache.dart';
|
||||||
|
|
||||||
|
part 'cast.dart';
|
||||||
|
|
||||||
|
part 'chain.dart';
|
||||||
|
|
||||||
|
part 'check.dart';
|
||||||
|
|
||||||
|
part 'compare.dart';
|
||||||
|
|
||||||
|
part 'fold_errors.dart';
|
||||||
|
|
||||||
|
part 'index.dart';
|
||||||
|
|
||||||
|
part 'longest.dart';
|
||||||
|
|
||||||
|
part 'map.dart';
|
||||||
|
|
||||||
|
part 'match.dart';
|
||||||
|
|
||||||
|
part 'max_depth.dart';
|
||||||
|
|
||||||
|
part 'negate.dart';
|
||||||
|
|
||||||
|
part 'opt.dart';
|
||||||
|
|
||||||
|
part 'recursion.dart';
|
||||||
|
|
||||||
|
part 'reduce.dart';
|
||||||
|
|
||||||
|
part 'reference.dart';
|
||||||
|
|
||||||
|
part 'repeat.dart';
|
||||||
|
|
||||||
|
part 'safe.dart';
|
||||||
|
|
||||||
|
part 'to_list.dart';
|
||||||
|
|
||||||
|
part 'util.dart';
|
||||||
|
|
||||||
|
part 'value.dart';
|
||||||
|
|
||||||
|
class ParseArgs {
|
||||||
|
final Trampoline trampoline;
|
||||||
|
final SpanScanner scanner;
|
||||||
|
final int depth;
|
||||||
|
|
||||||
|
ParseArgs(this.trampoline, this.scanner, this.depth);
|
||||||
|
|
||||||
|
ParseArgs increaseDepth() => ParseArgs(trampoline, scanner, depth + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A parser combinator, which can parse very complicated grammars in a manageable manner.
|
||||||
|
abstract class Parser<T> {
|
||||||
|
ParseResult<T> __parse(ParseArgs args);
|
||||||
|
|
||||||
|
ParseResult<T> _parse(ParseArgs args) {
|
||||||
|
var pos = args.scanner.position;
|
||||||
|
|
||||||
|
if (args.trampoline.hasMemoized(this, pos)) {
|
||||||
|
return args.trampoline.getMemoized<T>(this, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.trampoline.isActive(this, pos)) {
|
||||||
|
return ParseResult(args.trampoline, args.scanner, this, false, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
args.trampoline.enter(this, pos);
|
||||||
|
var result = __parse(args);
|
||||||
|
args.trampoline.memoize(this, pos, result);
|
||||||
|
args.trampoline.exit(this);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses text from a [SpanScanner].
|
||||||
|
ParseResult<T> parse(SpanScanner scanner, [int depth = 1]) {
|
||||||
|
var args = ParseArgs(Trampoline(), scanner, depth);
|
||||||
|
return _parse(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Skips forward a certain amount of steps after parsing, if it was successful.
|
||||||
|
Parser<T> forward(int amount) => _Advance<T>(this, amount);
|
||||||
|
|
||||||
|
/// Moves backward a certain amount of steps after parsing, if it was successful.
|
||||||
|
Parser<T> back(int amount) => _Advance<T>(this, amount * -1);
|
||||||
|
|
||||||
|
/// Casts this parser to produce [U] objects.
|
||||||
|
Parser<U> cast<U extends T>() => _Cast<T, U>(this);
|
||||||
|
|
||||||
|
/// Casts this parser to produce [dynamic] objects.
|
||||||
|
Parser<dynamic> castDynamic() => _CastDynamic<T>(this);
|
||||||
|
|
||||||
|
/// Runs the given function, which changes the returned [ParseResult] into one relating to a [U] object.
|
||||||
|
Parser<U> change<U>(ParseResult<U> Function(ParseResult<T>) f) {
|
||||||
|
return _Change<T, U>(this, f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Validates the parse result against a [Matcher].
|
||||||
|
///
|
||||||
|
/// You can provide a custom [errorMessage].
|
||||||
|
Parser<T> check(Matcher matcher,
|
||||||
|
{String? errorMessage, SyntaxErrorSeverity? severity}) =>
|
||||||
|
_Check<T>(
|
||||||
|
this, matcher, errorMessage, severity ?? SyntaxErrorSeverity.error);
|
||||||
|
|
||||||
|
/// Binds an [errorMessage] to a copy of this parser.
|
||||||
|
Parser<T> error({String? errorMessage, SyntaxErrorSeverity? severity}) =>
|
||||||
|
_Alt<T>(this, errorMessage, severity ?? SyntaxErrorSeverity.error);
|
||||||
|
|
||||||
|
/// Removes multiple errors that occur in the same spot; this can reduce noise in parser output.
|
||||||
|
Parser<T> foldErrors({bool Function(SyntaxError a, SyntaxError b)? equal}) {
|
||||||
|
equal ??= (b, e) => b.span?.start.offset == e.span?.start.offset;
|
||||||
|
return _FoldErrors<T>(this, equal);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transforms the parse result using a unary function.
|
||||||
|
Parser<U> map<U>(U Function(ParseResult<T>) f) {
|
||||||
|
return _Map<T, U>(this, f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prevents recursion past a certain [depth], preventing stack overflow errors.
|
||||||
|
Parser<T> maxDepth(int depth) => _MaxDepth<T>(this, depth);
|
||||||
|
|
||||||
|
Parser<T> operator ~() => negate();
|
||||||
|
|
||||||
|
/// Ensures this pattern is not matched.
|
||||||
|
///
|
||||||
|
/// You can provide an [errorMessage].
|
||||||
|
Parser<T> negate(
|
||||||
|
{String errorMessage = 'Negate error',
|
||||||
|
SyntaxErrorSeverity severity = SyntaxErrorSeverity.error}) =>
|
||||||
|
_Negate<T>(this, errorMessage, severity);
|
||||||
|
|
||||||
|
/// Caches the results of parse attempts at various locations within the source text.
|
||||||
|
///
|
||||||
|
/// Use this to prevent excessive recursion.
|
||||||
|
Parser<T> cache() => _Cache<T>(this);
|
||||||
|
|
||||||
|
Parser<T> operator &(Parser<T> other) => and(other);
|
||||||
|
|
||||||
|
/// Consumes `this` and another parser, but only considers the result of `this` parser.
|
||||||
|
Parser<T> and(Parser other) => then(other).change<T>((r) {
|
||||||
|
return ParseResult<T>(
|
||||||
|
r.trampoline,
|
||||||
|
r.scanner,
|
||||||
|
this,
|
||||||
|
r.successful,
|
||||||
|
r.errors,
|
||||||
|
span: r.span,
|
||||||
|
value: (r.value != null ? r.value![0] : r.value) as T?,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
Parser<T> operator |(Parser<T> other) => or(other);
|
||||||
|
|
||||||
|
/// Shortcut for [or]-ing two parsers.
|
||||||
|
Parser<T> or<U>(Parser<T> other) => any<T>([this, other]);
|
||||||
|
|
||||||
|
/// Parses this sequence one or more times.
|
||||||
|
ListParser<T> plus() => times(1, exact: false);
|
||||||
|
|
||||||
|
/// Safely escapes this parser when an error occurs.
|
||||||
|
///
|
||||||
|
/// The generated parser only runs once; repeated uses always exit eagerly.
|
||||||
|
Parser<T> safe(
|
||||||
|
{bool backtrack = true,
|
||||||
|
String errorMessage = 'error',
|
||||||
|
SyntaxErrorSeverity? severity}) =>
|
||||||
|
_Safe<T>(
|
||||||
|
this, backtrack, errorMessage, severity ?? SyntaxErrorSeverity.error);
|
||||||
|
|
||||||
|
Parser<List<T>> separatedByComma() =>
|
||||||
|
separatedBy(match<List<T>>(',').space());
|
||||||
|
|
||||||
|
/// Expects to see an infinite amounts of the pattern, separated by the [other] pattern.
|
||||||
|
///
|
||||||
|
/// Use this as a shortcut to parse arrays, parameter lists, etc.
|
||||||
|
Parser<List<T>> separatedBy(Parser other) {
|
||||||
|
var suffix = other.then(this).index(1).cast<T>();
|
||||||
|
return then(suffix.star()).map((r) {
|
||||||
|
var v = r.value;
|
||||||
|
if (v == null || v.length < 2) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
var preceding = v.isEmpty ? [] : (v[0] == null ? [] : [v[0]]);
|
||||||
|
var out = List<T>.from(preceding);
|
||||||
|
if (v[1] != null && v[1] != 'NULL') {
|
||||||
|
v[1].forEach((element) {
|
||||||
|
out.add(element as T);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Parser<T> surroundedByCurlyBraces({required T defaultValue}) => opt()
|
||||||
|
.surroundedBy(match('{').space(), match('}').space())
|
||||||
|
.map((r) => r.value ?? defaultValue);
|
||||||
|
|
||||||
|
Parser<T> surroundedBySquareBrackets({required T defaultValue}) => opt()
|
||||||
|
.surroundedBy(match('[').space(), match(']').space())
|
||||||
|
.map((r) => r.value ?? defaultValue);
|
||||||
|
|
||||||
|
/// Expects to see the pattern, surrounded by the others.
|
||||||
|
///
|
||||||
|
/// If no [right] is provided, it expects to see the same pattern on both sides.
|
||||||
|
/// Use this parse things like parenthesized expressions, arrays, etc.
|
||||||
|
Parser<T> surroundedBy(Parser left, [Parser? right]) {
|
||||||
|
return chain([
|
||||||
|
left,
|
||||||
|
this,
|
||||||
|
right ?? left,
|
||||||
|
]).index(1).castDynamic().cast<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses `this`, either as-is or wrapped in parentheses.
|
||||||
|
Parser<T> maybeParenthesized() {
|
||||||
|
return any([parenthesized(), this]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses `this`, wrapped in parentheses.
|
||||||
|
Parser<T> parenthesized() =>
|
||||||
|
surroundedBy(match('(').space(), match(')').space());
|
||||||
|
|
||||||
|
/// Consumes any trailing whitespace.
|
||||||
|
Parser<T> space() => trail(RegExp(r'[ \n\r\t]+'));
|
||||||
|
|
||||||
|
/// Consumes 0 or more instance(s) of this parser.
|
||||||
|
ListParser<T> star({bool backtrack = true}) =>
|
||||||
|
times(1, exact: false, backtrack: backtrack).opt();
|
||||||
|
|
||||||
|
/// Shortcut for [chain]-ing two parsers together.
|
||||||
|
ListParser<dynamic> then(Parser other) => chain<dynamic>([this, other]);
|
||||||
|
|
||||||
|
/// Casts this instance into a [ListParser].
|
||||||
|
ListParser<T> toList() => _ToList<T>(this);
|
||||||
|
|
||||||
|
/// Consumes and ignores any trailing occurrences of [pattern].
|
||||||
|
Parser<T> trail(Pattern pattern) =>
|
||||||
|
then(match(pattern).opt()).first().cast<T>();
|
||||||
|
|
||||||
|
/// Expect this pattern a certain number of times.
|
||||||
|
///
|
||||||
|
/// If [exact] is `false` (default: `true`), then the generated parser will accept
|
||||||
|
/// an infinite amount of occurrences after the specified [count].
|
||||||
|
///
|
||||||
|
/// You can provide custom error messages for when there are [tooFew] or [tooMany] occurrences.
|
||||||
|
ListParser<T> times(int count,
|
||||||
|
{bool exact = true,
|
||||||
|
String tooFew = 'Too few',
|
||||||
|
String tooMany = 'Too many',
|
||||||
|
bool backtrack = true,
|
||||||
|
SyntaxErrorSeverity? severity}) {
|
||||||
|
return _Repeat<T>(this, count, exact, tooFew, tooMany, backtrack,
|
||||||
|
severity ?? SyntaxErrorSeverity.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Produces an optional copy of this parser.
|
||||||
|
///
|
||||||
|
/// If [backtrack] is `true` (default), then a failed parse will not
|
||||||
|
/// modify the scanner state.
|
||||||
|
Parser<T> opt({bool backtrack = true}) => _Opt(this, backtrack);
|
||||||
|
|
||||||
|
/// Sets the value of the [ParseResult].
|
||||||
|
Parser<T> value(T Function(ParseResult<T?>) f) {
|
||||||
|
return _Value<T>(this, f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prints a representation of this parser, ideally without causing a stack overflow.
|
||||||
|
void stringify(CodeBuffer buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A [Parser] that produces [List]s of a type [T].
|
||||||
|
abstract class ListParser<T> extends Parser<List<T>> {
|
||||||
|
/// Shortcut for calling [index] with `0`.
|
||||||
|
Parser<T> first() => index(0);
|
||||||
|
|
||||||
|
/// Modifies this parser to only return the value at the given index [i].
|
||||||
|
Parser<T> index(int i) => _Index<T>(this, i);
|
||||||
|
|
||||||
|
/// Shortcut for calling [index] with the greatest-possible index.
|
||||||
|
Parser<T> last() => index(-1);
|
||||||
|
|
||||||
|
/// Modifies this parser to call `List.reduce` on the parsed values.
|
||||||
|
Parser<T> reduce(T Function(T, T) combine) => _Reduce<T>(this, combine);
|
||||||
|
|
||||||
|
/// Sorts the parsed values, using the given [Comparator].
|
||||||
|
ListParser<T> sort(Comparator<T> compare) => _Compare(this, compare);
|
||||||
|
|
||||||
|
@override
|
||||||
|
ListParser<T> opt({bool backtrack = true}) => _ListOpt(this, backtrack);
|
||||||
|
|
||||||
|
/// Modifies this parser, returning only the values that match a predicate.
|
||||||
|
Parser<List<T>> where(bool Function(T) f) =>
|
||||||
|
map<List<T>>((r) => r.value?.where(f).toList() ?? []);
|
||||||
|
|
||||||
|
/// Condenses a [ListParser] into having a value of the combined span's text.
|
||||||
|
Parser<String> flatten() => map<String>((r) => r.span?.text ?? '');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prevents stack overflow in recursive parsers.
|
||||||
|
class Trampoline {
|
||||||
|
final Map<Parser, Queue<int>> _active = {};
|
||||||
|
final Map<Parser, List<Tuple2<int, ParseResult>>> _memo = {};
|
||||||
|
|
||||||
|
bool hasMemoized(Parser parser, int position) {
|
||||||
|
var list = _memo[parser];
|
||||||
|
return list?.any((t) => t.item1 == position) == true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ParseResult<T> getMemoized<T>(Parser parser, int position) {
|
||||||
|
return _memo[parser]?.firstWhere((t) => t.item1 == position).item2
|
||||||
|
as ParseResult<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
void memoize(Parser parser, int position, ParseResult? result) {
|
||||||
|
if (result != null) {
|
||||||
|
var list = _memo.putIfAbsent(parser, () => []);
|
||||||
|
var tuple = Tuple2(position, result);
|
||||||
|
if (!list.contains(tuple)) list.add(tuple);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isActive(Parser parser, int position) {
|
||||||
|
if (!_active.containsKey(parser)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var q = _active[parser]!;
|
||||||
|
if (q.isEmpty) return false;
|
||||||
|
//return q.contains(position);
|
||||||
|
return q.first == position;
|
||||||
|
}
|
||||||
|
|
||||||
|
void enter(Parser parser, int position) {
|
||||||
|
_active.putIfAbsent(parser, () => Queue()).addFirst(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
void exit(Parser parser) {
|
||||||
|
if (_active.containsKey(parser)) _active[parser]?.removeFirst();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The result generated by a [Parser].
|
||||||
|
class ParseResult<T> {
|
||||||
|
final Parser<T> parser;
|
||||||
|
final bool successful;
|
||||||
|
final Iterable<SyntaxError> errors;
|
||||||
|
final FileSpan? span;
|
||||||
|
final T? value;
|
||||||
|
final SpanScanner scanner;
|
||||||
|
final Trampoline trampoline;
|
||||||
|
|
||||||
|
ParseResult(
|
||||||
|
this.trampoline, this.scanner, this.parser, this.successful, this.errors,
|
||||||
|
{this.span, this.value});
|
||||||
|
|
||||||
|
ParseResult<T> change(
|
||||||
|
{Parser<T>? parser,
|
||||||
|
bool? successful,
|
||||||
|
Iterable<SyntaxError> errors = const [],
|
||||||
|
FileSpan? span,
|
||||||
|
T? value}) {
|
||||||
|
return ParseResult<T>(
|
||||||
|
trampoline,
|
||||||
|
scanner,
|
||||||
|
parser ?? this.parser,
|
||||||
|
successful ?? this.successful,
|
||||||
|
errors.isNotEmpty ? errors : this.errors,
|
||||||
|
span: span ?? this.span,
|
||||||
|
value: value ?? this.value,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ParseResult<T> addErrors(Iterable<SyntaxError> errors) {
|
||||||
|
return change(
|
||||||
|
errors: List<SyntaxError>.from(this.errors)..addAll(errors),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
38
common/combinator/lib/src/combinator/compare.dart
Normal file
38
common/combinator/lib/src/combinator/compare.dart
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
part of 'combinator.dart';
|
||||||
|
|
||||||
|
class _Compare<T> extends ListParser<T> {
|
||||||
|
final ListParser<T> parser;
|
||||||
|
final Comparator<T> compare;
|
||||||
|
|
||||||
|
_Compare(this.parser, this.compare);
|
||||||
|
|
||||||
|
@override
|
||||||
|
ParseResult<List<T>> __parse(ParseArgs args) {
|
||||||
|
var result = parser._parse(args.increaseDepth());
|
||||||
|
if (!result.successful) return result;
|
||||||
|
|
||||||
|
result = result.change(
|
||||||
|
value: result.value?.isNotEmpty == true ? result.value : []);
|
||||||
|
result = result.change(value: List<T>.from(result.value!));
|
||||||
|
return ParseResult<List<T>>(
|
||||||
|
args.trampoline,
|
||||||
|
args.scanner,
|
||||||
|
this,
|
||||||
|
true,
|
||||||
|
[],
|
||||||
|
span: result.span,
|
||||||
|
value: result.value?..sort(compare),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void stringify(CodeBuffer buffer) {
|
||||||
|
buffer
|
||||||
|
..writeln('sort($compare) (')
|
||||||
|
..indent();
|
||||||
|
parser.stringify(buffer);
|
||||||
|
buffer
|
||||||
|
..outdent()
|
||||||
|
..writeln(')');
|
||||||
|
}
|
||||||
|
}
|
29
common/combinator/lib/src/combinator/fold_errors.dart
Normal file
29
common/combinator/lib/src/combinator/fold_errors.dart
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
part of 'combinator.dart';
|
||||||
|
|
||||||
|
class _FoldErrors<T> extends Parser<T> {
|
||||||
|
final Parser<T> parser;
|
||||||
|
final bool Function(SyntaxError, SyntaxError) equal;
|
||||||
|
|
||||||
|
_FoldErrors(this.parser, this.equal);
|
||||||
|
|
||||||
|
@override
|
||||||
|
ParseResult<T> __parse(ParseArgs args) {
|
||||||
|
var result = parser._parse(args.increaseDepth()).change(parser: this);
|
||||||
|
var errors = result.errors.fold<List<SyntaxError>>([], (out, e) {
|
||||||
|
if (!out.any((b) => equal(e, b))) out.add(e);
|
||||||
|
return out;
|
||||||
|
});
|
||||||
|
return result.change(errors: errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void stringify(CodeBuffer buffer) {
|
||||||
|
buffer
|
||||||
|
..writeln('fold errors (')
|
||||||
|
..indent();
|
||||||
|
parser.stringify(buffer);
|
||||||
|
buffer
|
||||||
|
..outdent()
|
||||||
|
..writeln(')');
|
||||||
|
}
|
||||||
|
}
|
52
common/combinator/lib/src/combinator/index.dart
Normal file
52
common/combinator/lib/src/combinator/index.dart
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
part of 'combinator.dart';
|
||||||
|
|
||||||
|
class _Index<T> extends Parser<T> {
|
||||||
|
final ListParser<T> parser;
|
||||||
|
final int index;
|
||||||
|
|
||||||
|
_Index(this.parser, this.index);
|
||||||
|
|
||||||
|
@override
|
||||||
|
ParseResult<T> __parse(ParseArgs args) {
|
||||||
|
var result = parser._parse(args.increaseDepth());
|
||||||
|
Object? value;
|
||||||
|
|
||||||
|
if (result.successful) {
|
||||||
|
var vList = result.value;
|
||||||
|
if (vList == null) {
|
||||||
|
throw ArgumentError('ParseResult is null');
|
||||||
|
}
|
||||||
|
if (index == -1) {
|
||||||
|
value = vList.last;
|
||||||
|
} else {
|
||||||
|
if (index < vList.length) {
|
||||||
|
// print(">>>>Index: $index, Size: ${vList.length}");
|
||||||
|
// value =
|
||||||
|
// index == -1 ? result.value!.last : result.value!.elementAt(index);
|
||||||
|
value = result.value!.elementAt(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ParseResult<T>(
|
||||||
|
args.trampoline,
|
||||||
|
args.scanner,
|
||||||
|
this,
|
||||||
|
result.successful,
|
||||||
|
result.errors,
|
||||||
|
span: result.span,
|
||||||
|
value: value as T?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void stringify(CodeBuffer buffer) {
|
||||||
|
buffer
|
||||||
|
..writeln('index($index) (')
|
||||||
|
..indent();
|
||||||
|
parser.stringify(buffer);
|
||||||
|
buffer
|
||||||
|
..outdent()
|
||||||
|
..writeln(')');
|
||||||
|
}
|
||||||
|
}
|
119
common/combinator/lib/src/combinator/longest.dart
Normal file
119
common/combinator/lib/src/combinator/longest.dart
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
part of 'combinator.dart';
|
||||||
|
|
||||||
|
/// Matches any one of the given [parsers].
|
||||||
|
///
|
||||||
|
/// You can provide a custom [errorMessage].
|
||||||
|
Parser<T> longest<T>(Iterable<Parser<T>> parsers,
|
||||||
|
{Object? errorMessage, SyntaxErrorSeverity? severity}) {
|
||||||
|
return _Longest(parsers, errorMessage, severity ?? SyntaxErrorSeverity.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _Longest<T> extends Parser<T> {
|
||||||
|
final Iterable<Parser<T>> parsers;
|
||||||
|
final Object? errorMessage;
|
||||||
|
final SyntaxErrorSeverity severity;
|
||||||
|
|
||||||
|
_Longest(this.parsers, this.errorMessage, this.severity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
ParseResult<T> _parse(ParseArgs args) {
|
||||||
|
var inactive = parsers
|
||||||
|
.toList()
|
||||||
|
.where((p) => !args.trampoline.isActive(p, args.scanner.position));
|
||||||
|
|
||||||
|
if (inactive.isEmpty) {
|
||||||
|
return ParseResult(args.trampoline, args.scanner, this, false, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
var replay = args.scanner.position;
|
||||||
|
var errors = <SyntaxError>[];
|
||||||
|
var results = <ParseResult<T>>[];
|
||||||
|
|
||||||
|
for (var parser in inactive) {
|
||||||
|
var result = parser._parse(args.increaseDepth());
|
||||||
|
|
||||||
|
if (result.successful && result.span != null) {
|
||||||
|
results.add(result);
|
||||||
|
} else if (parser is _Alt) {
|
||||||
|
errors.addAll(result.errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
args.scanner.position = replay;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (results.isNotEmpty) {
|
||||||
|
results.sort((a, b) => b.span!.length.compareTo(a.span!.length));
|
||||||
|
args.scanner.scan(results.first.span!.text);
|
||||||
|
return results.first;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errorMessage != false) {
|
||||||
|
errors.add(
|
||||||
|
SyntaxError(
|
||||||
|
severity,
|
||||||
|
errorMessage?.toString() ??
|
||||||
|
'No match found for ${parsers.length} alternative(s)',
|
||||||
|
args.scanner.emptySpan,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ParseResult(args.trampoline, args.scanner, this, false, errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
ParseResult<T> __parse(ParseArgs args) {
|
||||||
|
var replay = args.scanner.position;
|
||||||
|
var errors = <SyntaxError>[];
|
||||||
|
var results = <ParseResult<T>>[];
|
||||||
|
|
||||||
|
for (var parser in parsers) {
|
||||||
|
var result = parser._parse(args.increaseDepth());
|
||||||
|
|
||||||
|
if (result.successful) {
|
||||||
|
results.add(result);
|
||||||
|
} else if (parser is _Alt) {
|
||||||
|
errors.addAll(result.errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
args.scanner.position = replay;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (results.isNotEmpty) {
|
||||||
|
results.sort((a, b) => b.span!.length.compareTo(a.span!.length));
|
||||||
|
args.scanner.scan(results.first.span!.text);
|
||||||
|
return results.first;
|
||||||
|
}
|
||||||
|
|
||||||
|
errors.add(
|
||||||
|
SyntaxError(
|
||||||
|
severity,
|
||||||
|
errorMessage?.toString() ??
|
||||||
|
'No match found for ${parsers.length} alternative(s)',
|
||||||
|
args.scanner.emptySpan,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return ParseResult(args.trampoline, args.scanner, this, false, errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void stringify(CodeBuffer buffer) {
|
||||||
|
buffer
|
||||||
|
..writeln('longest(${parsers.length}) (')
|
||||||
|
..indent();
|
||||||
|
var i = 1;
|
||||||
|
|
||||||
|
for (var parser in parsers) {
|
||||||
|
buffer
|
||||||
|
..writeln('#${i++}:')
|
||||||
|
..indent();
|
||||||
|
parser.stringify(buffer);
|
||||||
|
buffer.outdent();
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer
|
||||||
|
..outdent()
|
||||||
|
..writeln(')');
|
||||||
|
}
|
||||||
|
}
|
56
common/combinator/lib/src/combinator/map.dart
Normal file
56
common/combinator/lib/src/combinator/map.dart
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
part of 'combinator.dart';
|
||||||
|
|
||||||
|
class _Map<T, U> extends Parser<U> {
|
||||||
|
final Parser<T> parser;
|
||||||
|
final U Function(ParseResult<T>) f;
|
||||||
|
|
||||||
|
_Map(this.parser, this.f);
|
||||||
|
|
||||||
|
@override
|
||||||
|
ParseResult<U> __parse(ParseArgs args) {
|
||||||
|
var result = parser._parse(args.increaseDepth());
|
||||||
|
return ParseResult<U>(
|
||||||
|
args.trampoline,
|
||||||
|
args.scanner,
|
||||||
|
this,
|
||||||
|
result.successful,
|
||||||
|
result.errors,
|
||||||
|
span: result.span,
|
||||||
|
value: result.successful ? f(result) : null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void stringify(CodeBuffer buffer) {
|
||||||
|
buffer
|
||||||
|
..writeln('map<$U> (')
|
||||||
|
..indent();
|
||||||
|
parser.stringify(buffer);
|
||||||
|
buffer
|
||||||
|
..outdent()
|
||||||
|
..writeln(')');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _Change<T, U> extends Parser<U> {
|
||||||
|
final Parser<T> parser;
|
||||||
|
final ParseResult<U> Function(ParseResult<T>) f;
|
||||||
|
|
||||||
|
_Change(this.parser, this.f);
|
||||||
|
|
||||||
|
@override
|
||||||
|
ParseResult<U> __parse(ParseArgs args) {
|
||||||
|
return f(parser._parse(args.increaseDepth())).change(parser: this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void stringify(CodeBuffer buffer) {
|
||||||
|
buffer
|
||||||
|
..writeln('change($f) (')
|
||||||
|
..indent();
|
||||||
|
parser.stringify(buffer);
|
||||||
|
buffer
|
||||||
|
..outdent()
|
||||||
|
..writeln(')');
|
||||||
|
}
|
||||||
|
}
|
41
common/combinator/lib/src/combinator/match.dart
Normal file
41
common/combinator/lib/src/combinator/match.dart
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
part of 'combinator.dart';
|
||||||
|
|
||||||
|
/// Expects to match a given [pattern]. If it is not matched, you can provide a custom [errorMessage].
|
||||||
|
Parser<T> match<T>(Pattern pattern,
|
||||||
|
{String? errorMessage, SyntaxErrorSeverity? severity}) =>
|
||||||
|
_Match<T>(pattern, errorMessage, severity ?? SyntaxErrorSeverity.error);
|
||||||
|
|
||||||
|
class _Match<T> extends Parser<T> {
|
||||||
|
final Pattern pattern;
|
||||||
|
final String? errorMessage;
|
||||||
|
final SyntaxErrorSeverity severity;
|
||||||
|
|
||||||
|
_Match(this.pattern, this.errorMessage, this.severity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
ParseResult<T> __parse(ParseArgs args) {
|
||||||
|
var scanner = args.scanner;
|
||||||
|
if (!scanner.scan(pattern)) {
|
||||||
|
return ParseResult(args.trampoline, scanner, this, false, [
|
||||||
|
SyntaxError(
|
||||||
|
severity,
|
||||||
|
errorMessage ?? 'Expected "$pattern".',
|
||||||
|
scanner.emptySpan,
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
return ParseResult<T>(
|
||||||
|
args.trampoline,
|
||||||
|
scanner,
|
||||||
|
this,
|
||||||
|
true,
|
||||||
|
[],
|
||||||
|
span: scanner.lastSpan,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void stringify(CodeBuffer buffer) {
|
||||||
|
buffer.writeln('match($pattern)');
|
||||||
|
}
|
||||||
|
}
|
28
common/combinator/lib/src/combinator/max_depth.dart
Normal file
28
common/combinator/lib/src/combinator/max_depth.dart
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
part of 'combinator.dart';
|
||||||
|
|
||||||
|
class _MaxDepth<T> extends Parser<T> {
|
||||||
|
final Parser<T> parser;
|
||||||
|
final int cap;
|
||||||
|
|
||||||
|
_MaxDepth(this.parser, this.cap);
|
||||||
|
|
||||||
|
@override
|
||||||
|
ParseResult<T> __parse(ParseArgs args) {
|
||||||
|
if (args.depth > cap) {
|
||||||
|
return ParseResult<T>(args.trampoline, args.scanner, this, false, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
return parser._parse(args.increaseDepth());
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void stringify(CodeBuffer buffer) {
|
||||||
|
buffer
|
||||||
|
..writeln('max depth($cap) (')
|
||||||
|
..indent();
|
||||||
|
parser.stringify(buffer);
|
||||||
|
buffer
|
||||||
|
..outdent()
|
||||||
|
..writeln(')');
|
||||||
|
}
|
||||||
|
}
|
51
common/combinator/lib/src/combinator/negate.dart
Normal file
51
common/combinator/lib/src/combinator/negate.dart
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
part of 'combinator.dart';
|
||||||
|
|
||||||
|
class _Negate<T> extends Parser<T> {
|
||||||
|
final Parser<T> parser;
|
||||||
|
final String? errorMessage;
|
||||||
|
final SyntaxErrorSeverity severity;
|
||||||
|
|
||||||
|
_Negate(this.parser, this.errorMessage, this.severity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
ParseResult<T> __parse(ParseArgs args) {
|
||||||
|
var result = parser._parse(args.increaseDepth()).change(parser: this);
|
||||||
|
|
||||||
|
if (!result.successful) {
|
||||||
|
return ParseResult<T>(
|
||||||
|
args.trampoline,
|
||||||
|
args.scanner,
|
||||||
|
this,
|
||||||
|
true,
|
||||||
|
[],
|
||||||
|
span: result.span ?? args.scanner.lastSpan ?? args.scanner.emptySpan,
|
||||||
|
value: result.value,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
result = result.change(successful: false);
|
||||||
|
|
||||||
|
if (errorMessage != null) {
|
||||||
|
result = result.addErrors([
|
||||||
|
SyntaxError(
|
||||||
|
severity,
|
||||||
|
errorMessage,
|
||||||
|
result.span,
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void stringify(CodeBuffer buffer) {
|
||||||
|
buffer
|
||||||
|
..writeln('negate (')
|
||||||
|
..indent();
|
||||||
|
parser.stringify(buffer);
|
||||||
|
buffer
|
||||||
|
..outdent()
|
||||||
|
..writeln(')');
|
||||||
|
}
|
||||||
|
}
|
57
common/combinator/lib/src/combinator/opt.dart
Normal file
57
common/combinator/lib/src/combinator/opt.dart
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
part of 'combinator.dart';
|
||||||
|
|
||||||
|
class _Opt<T> extends Parser<T> {
|
||||||
|
final Parser<T> parser;
|
||||||
|
final bool backtrack;
|
||||||
|
|
||||||
|
_Opt(this.parser, this.backtrack);
|
||||||
|
|
||||||
|
@override
|
||||||
|
ParseResult<T> __parse(ParseArgs args) {
|
||||||
|
var replay = args.scanner.position;
|
||||||
|
var result = parser._parse(args.increaseDepth());
|
||||||
|
|
||||||
|
if (!result.successful) args.scanner.position = replay;
|
||||||
|
|
||||||
|
return result.change(parser: this, successful: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void stringify(CodeBuffer buffer) {
|
||||||
|
buffer
|
||||||
|
..writeln('optional (')
|
||||||
|
..indent();
|
||||||
|
parser.stringify(buffer);
|
||||||
|
buffer
|
||||||
|
..outdent()
|
||||||
|
..writeln(')');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ListOpt<T> extends ListParser<T> {
|
||||||
|
final ListParser<T> parser;
|
||||||
|
final bool backtrack;
|
||||||
|
|
||||||
|
_ListOpt(this.parser, this.backtrack);
|
||||||
|
|
||||||
|
@override
|
||||||
|
ParseResult<List<T>> __parse(ParseArgs args) {
|
||||||
|
var replay = args.scanner.position;
|
||||||
|
var result = parser._parse(args.increaseDepth());
|
||||||
|
|
||||||
|
if (!result.successful) args.scanner.position = replay;
|
||||||
|
|
||||||
|
return result.change(parser: this, successful: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void stringify(CodeBuffer buffer) {
|
||||||
|
buffer
|
||||||
|
..writeln('optional (')
|
||||||
|
..indent();
|
||||||
|
parser.stringify(buffer);
|
||||||
|
buffer
|
||||||
|
..outdent()
|
||||||
|
..writeln(')');
|
||||||
|
}
|
||||||
|
}
|
142
common/combinator/lib/src/combinator/recursion.dart
Normal file
142
common/combinator/lib/src/combinator/recursion.dart
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
part of 'combinator.dart';
|
||||||
|
|
||||||
|
/*
|
||||||
|
/// Handles left recursion in a grammar using the Pratt algorithm.
|
||||||
|
class Recursion<T> {
|
||||||
|
Iterable<Parser<T>> prefix;
|
||||||
|
Map<Parser, T Function(T, T, ParseResult<T>)> infix;
|
||||||
|
Map<Parser, T Function(T, T, ParseResult<T>)> postfix;
|
||||||
|
|
||||||
|
Recursion({this.prefix, this.infix, this.postfix}) {
|
||||||
|
prefix ??= [];
|
||||||
|
infix ??= {};
|
||||||
|
postfix ??= {};
|
||||||
|
}
|
||||||
|
|
||||||
|
Parser<T> precedence(int p) => _Precedence(this, p);
|
||||||
|
|
||||||
|
void stringify(CodeBuffer buffer) {
|
||||||
|
buffer
|
||||||
|
..writeln('recursion (')
|
||||||
|
..indent()
|
||||||
|
..writeln('prefix(${prefix.length}')
|
||||||
|
..writeln('infix(${infix.length}')
|
||||||
|
..writeln('postfix(${postfix.length}')
|
||||||
|
..outdent()
|
||||||
|
..writeln(')');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _Precedence<T> extends Parser<T> {
|
||||||
|
final Recursion r;
|
||||||
|
final int precedence;
|
||||||
|
|
||||||
|
_Precedence(this.r, this.precedence);
|
||||||
|
|
||||||
|
@override
|
||||||
|
ParseResult<T> __parse(ParseArgs args) {
|
||||||
|
int replay = args.scanner.position;
|
||||||
|
var errors = <SyntaxError>[];
|
||||||
|
var start = args.scanner.state;
|
||||||
|
var reversedKeys = r.infix.keys.toList().reversed;
|
||||||
|
|
||||||
|
for (var pre in r.prefix) {
|
||||||
|
var result = pre._parse(args.increaseDepth()), originalResult = result;
|
||||||
|
|
||||||
|
if (!result.successful) {
|
||||||
|
if (pre is _Alt) errors.addAll(result.errors);
|
||||||
|
args.scanner.position = replay;
|
||||||
|
} else {
|
||||||
|
var left = result.value;
|
||||||
|
replay = args.scanner.position;
|
||||||
|
//print('${result.span.text}:\n' + scanner.emptySpan.highlight());
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
bool matched = false;
|
||||||
|
|
||||||
|
//for (int i = 0; i < r.infix.length; i++) {
|
||||||
|
for (int i = r.infix.length - 1; i >= 0; i--) {
|
||||||
|
//var fix = r.infix.keys.elementAt(r.infix.length - i - 1);
|
||||||
|
var fix = reversedKeys.elementAt(i);
|
||||||
|
|
||||||
|
if (i < precedence) continue;
|
||||||
|
|
||||||
|
var result = fix._parse(args.increaseDepth());
|
||||||
|
|
||||||
|
if (!result.successful) {
|
||||||
|
if (fix is _Alt) errors.addAll(result.errors);
|
||||||
|
// If this is the last alternative and it failed, don't continue looping.
|
||||||
|
//if (true || i + 1 < r.infix.length)
|
||||||
|
args.scanner.position = replay;
|
||||||
|
} else {
|
||||||
|
//print('FOUND $fix when left was $left');
|
||||||
|
//print('$i vs $precedence\n${originalResult.span.highlight()}');
|
||||||
|
result = r.precedence(i)._parse(args.increaseDepth());
|
||||||
|
|
||||||
|
if (!result.successful) {
|
||||||
|
} else {
|
||||||
|
matched = false;
|
||||||
|
var old = left;
|
||||||
|
left = r.infix[fix](left, result.value, result);
|
||||||
|
print(
|
||||||
|
'$old $fix ${result.value} = $left\n${result.span.highlight()}');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!matched) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
replay = args.scanner.position;
|
||||||
|
//print('f ${result.span.text}');
|
||||||
|
|
||||||
|
for (var post in r.postfix.keys) {
|
||||||
|
var result = pre._parse(args.increaseDepth());
|
||||||
|
|
||||||
|
if (!result.successful) {
|
||||||
|
if (post is _Alt) errors.addAll(result.errors);
|
||||||
|
args.scanner.position = replay;
|
||||||
|
} else {
|
||||||
|
left = r.infix[post](left, originalResult.value, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!args.scanner.isDone) {
|
||||||
|
// If we're not done scanning, then we need some sort of guard to ensure the
|
||||||
|
// that this exact parser does not run again in the exact position.
|
||||||
|
}
|
||||||
|
return ParseResult(
|
||||||
|
args.trampoline,
|
||||||
|
args.scanner,
|
||||||
|
this,
|
||||||
|
true,
|
||||||
|
errors,
|
||||||
|
value: left,
|
||||||
|
span: args.scanner.spanFrom(start),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ParseResult(
|
||||||
|
args.trampoline,
|
||||||
|
args.scanner,
|
||||||
|
this,
|
||||||
|
false,
|
||||||
|
errors,
|
||||||
|
span: args.scanner.spanFrom(start),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void stringify(CodeBuffer buffer) {
|
||||||
|
buffer
|
||||||
|
..writeln('precedence($precedence) (')
|
||||||
|
..indent();
|
||||||
|
r.stringify(buffer);
|
||||||
|
buffer
|
||||||
|
..outdent()
|
||||||
|
..writeln(')');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
46
common/combinator/lib/src/combinator/reduce.dart
Normal file
46
common/combinator/lib/src/combinator/reduce.dart
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
part of 'combinator.dart';
|
||||||
|
|
||||||
|
class _Reduce<T> extends Parser<T> {
|
||||||
|
final ListParser<T> parser;
|
||||||
|
final T Function(T, T) combine;
|
||||||
|
|
||||||
|
_Reduce(this.parser, this.combine);
|
||||||
|
|
||||||
|
@override
|
||||||
|
ParseResult<T> __parse(ParseArgs args) {
|
||||||
|
var result = parser._parse(args.increaseDepth());
|
||||||
|
|
||||||
|
if (!result.successful) {
|
||||||
|
return ParseResult<T>(
|
||||||
|
args.trampoline,
|
||||||
|
args.scanner,
|
||||||
|
this,
|
||||||
|
false,
|
||||||
|
result.errors,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
result = result.change(
|
||||||
|
value: result.value?.isNotEmpty == true ? result.value : []);
|
||||||
|
return ParseResult<T>(
|
||||||
|
args.trampoline,
|
||||||
|
args.scanner,
|
||||||
|
this,
|
||||||
|
result.successful,
|
||||||
|
[],
|
||||||
|
span: result.span,
|
||||||
|
value: result.value!.isEmpty ? null : result.value!.reduce(combine),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void stringify(CodeBuffer buffer) {
|
||||||
|
buffer
|
||||||
|
..writeln('reduce($combine) (')
|
||||||
|
..indent();
|
||||||
|
parser.stringify(buffer);
|
||||||
|
buffer
|
||||||
|
..outdent()
|
||||||
|
..writeln(')');
|
||||||
|
}
|
||||||
|
}
|
44
common/combinator/lib/src/combinator/reference.dart
Normal file
44
common/combinator/lib/src/combinator/reference.dart
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
part of 'combinator.dart';
|
||||||
|
|
||||||
|
Reference<T> reference<T>() => Reference<T>._();
|
||||||
|
|
||||||
|
class Reference<T> extends Parser<T> {
|
||||||
|
Parser<T>? _parser;
|
||||||
|
bool printed = false;
|
||||||
|
|
||||||
|
Reference._();
|
||||||
|
|
||||||
|
set parser(Parser<T> value) {
|
||||||
|
if (_parser != null) {
|
||||||
|
throw StateError('There is already a parser assigned to this reference.');
|
||||||
|
}
|
||||||
|
_parser = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
ParseResult<T> __parse(ParseArgs args) {
|
||||||
|
if (_parser == null) {
|
||||||
|
throw StateError('There is no parser assigned to this reference.');
|
||||||
|
}
|
||||||
|
return _parser!._parse(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
ParseResult<T> _parse(ParseArgs args) {
|
||||||
|
if (_parser == null) {
|
||||||
|
throw StateError('There is no parser assigned to this reference.');
|
||||||
|
}
|
||||||
|
return _parser!._parse(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void stringify(CodeBuffer buffer) {
|
||||||
|
if (_parser == null) {
|
||||||
|
buffer.writeln('(undefined reference <$T>)');
|
||||||
|
} else if (!printed) {
|
||||||
|
_parser!.stringify(buffer);
|
||||||
|
}
|
||||||
|
printed = true;
|
||||||
|
buffer.writeln('(previously printed reference)');
|
||||||
|
}
|
||||||
|
}
|
91
common/combinator/lib/src/combinator/repeat.dart
Normal file
91
common/combinator/lib/src/combinator/repeat.dart
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
part of 'combinator.dart';
|
||||||
|
|
||||||
|
class _Repeat<T> extends ListParser<T> {
|
||||||
|
final Parser<T> parser;
|
||||||
|
final int count;
|
||||||
|
final bool exact, backtrack;
|
||||||
|
final String tooFew;
|
||||||
|
final String tooMany;
|
||||||
|
final SyntaxErrorSeverity severity;
|
||||||
|
|
||||||
|
_Repeat(this.parser, this.count, this.exact, this.tooFew, this.tooMany,
|
||||||
|
this.backtrack, this.severity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
ParseResult<List<T>> __parse(ParseArgs args) {
|
||||||
|
var errors = <SyntaxError>[];
|
||||||
|
var results = <T>[];
|
||||||
|
var spans = <FileSpan>[];
|
||||||
|
var success = 0;
|
||||||
|
var replay = args.scanner.position;
|
||||||
|
ParseResult<T> result;
|
||||||
|
|
||||||
|
do {
|
||||||
|
result = parser._parse(args.increaseDepth());
|
||||||
|
if (result.successful) {
|
||||||
|
success++;
|
||||||
|
if (result.value != null) {
|
||||||
|
results.add(result.value as T);
|
||||||
|
}
|
||||||
|
replay = args.scanner.position;
|
||||||
|
} else if (backtrack) {
|
||||||
|
args.scanner.position = replay;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.span != null) {
|
||||||
|
spans.add(result.span!);
|
||||||
|
}
|
||||||
|
} while (result.successful);
|
||||||
|
|
||||||
|
if (success < count) {
|
||||||
|
errors.addAll(result.errors);
|
||||||
|
errors.add(
|
||||||
|
SyntaxError(
|
||||||
|
severity,
|
||||||
|
tooFew,
|
||||||
|
result.span ?? args.scanner.emptySpan,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (backtrack) args.scanner.position = replay;
|
||||||
|
|
||||||
|
return ParseResult<List<T>>(
|
||||||
|
args.trampoline, args.scanner, this, false, errors);
|
||||||
|
} else if (success > count && exact) {
|
||||||
|
if (backtrack) args.scanner.position = replay;
|
||||||
|
|
||||||
|
return ParseResult<List<T>>(args.trampoline, args.scanner, this, false, [
|
||||||
|
SyntaxError(
|
||||||
|
severity,
|
||||||
|
tooMany,
|
||||||
|
result.span ?? args.scanner.emptySpan,
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var span = spans.reduce((a, b) => a.expand(b));
|
||||||
|
return ParseResult<List<T>>(
|
||||||
|
args.trampoline,
|
||||||
|
args.scanner,
|
||||||
|
this,
|
||||||
|
true,
|
||||||
|
[],
|
||||||
|
span: span,
|
||||||
|
value: results,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void stringify(CodeBuffer buffer) {
|
||||||
|
var r = StringBuffer('{$count');
|
||||||
|
if (!exact) r.write(',');
|
||||||
|
r.write('}');
|
||||||
|
buffer
|
||||||
|
..writeln('repeat($r) (')
|
||||||
|
..indent();
|
||||||
|
parser.stringify(buffer);
|
||||||
|
buffer
|
||||||
|
..outdent()
|
||||||
|
..writeln(')');
|
||||||
|
}
|
||||||
|
}
|
47
common/combinator/lib/src/combinator/safe.dart
Normal file
47
common/combinator/lib/src/combinator/safe.dart
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
part of 'combinator.dart';
|
||||||
|
|
||||||
|
class _Safe<T> extends Parser<T> {
|
||||||
|
final Parser<T> parser;
|
||||||
|
final bool backtrack;
|
||||||
|
final String errorMessage;
|
||||||
|
final SyntaxErrorSeverity severity;
|
||||||
|
bool _triggered = false;
|
||||||
|
|
||||||
|
_Safe(this.parser, this.backtrack, this.errorMessage, this.severity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
ParseResult<T> __parse(ParseArgs args) {
|
||||||
|
var replay = args.scanner.position;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (_triggered) throw Exception();
|
||||||
|
return parser._parse(args.increaseDepth());
|
||||||
|
} catch (_) {
|
||||||
|
_triggered = true;
|
||||||
|
if (backtrack) args.scanner.position = replay;
|
||||||
|
var errors = <SyntaxError>[];
|
||||||
|
|
||||||
|
errors.add(
|
||||||
|
SyntaxError(
|
||||||
|
severity,
|
||||||
|
errorMessage,
|
||||||
|
args.scanner.lastSpan ?? args.scanner.emptySpan,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return ParseResult<T>(args.trampoline, args.scanner, this, false, errors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void stringify(CodeBuffer buffer) {
|
||||||
|
var t = _triggered ? 'triggered' : 'not triggered';
|
||||||
|
buffer
|
||||||
|
..writeln('safe($t) (')
|
||||||
|
..indent();
|
||||||
|
parser.stringify(buffer);
|
||||||
|
buffer
|
||||||
|
..outdent()
|
||||||
|
..writeln(')');
|
||||||
|
}
|
||||||
|
}
|
41
common/combinator/lib/src/combinator/to_list.dart
Normal file
41
common/combinator/lib/src/combinator/to_list.dart
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
part of 'combinator.dart';
|
||||||
|
|
||||||
|
class _ToList<T> extends ListParser<T> {
|
||||||
|
final Parser<T> parser;
|
||||||
|
|
||||||
|
_ToList(this.parser);
|
||||||
|
|
||||||
|
@override
|
||||||
|
ParseResult<List<T>> __parse(ParseArgs args) {
|
||||||
|
var result = parser._parse(args.increaseDepth());
|
||||||
|
|
||||||
|
if (result.value is List) {
|
||||||
|
return (result as ParseResult<List<T>>).change(parser: this);
|
||||||
|
}
|
||||||
|
|
||||||
|
var values = <T>[];
|
||||||
|
if (result.value != null) {
|
||||||
|
values.add(result.value as T);
|
||||||
|
}
|
||||||
|
return ParseResult(
|
||||||
|
args.trampoline,
|
||||||
|
args.scanner,
|
||||||
|
this,
|
||||||
|
result.successful,
|
||||||
|
result.errors,
|
||||||
|
span: result.span,
|
||||||
|
value: values,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void stringify(CodeBuffer buffer) {
|
||||||
|
buffer
|
||||||
|
..writeln('to list (')
|
||||||
|
..indent();
|
||||||
|
parser.stringify(buffer);
|
||||||
|
buffer
|
||||||
|
..outdent()
|
||||||
|
..writeln(')');
|
||||||
|
}
|
||||||
|
}
|
57
common/combinator/lib/src/combinator/util.dart
Normal file
57
common/combinator/lib/src/combinator/util.dart
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
part of 'combinator.dart';
|
||||||
|
|
||||||
|
/// A typed parser that parses a sequence of 2 values of different types.
|
||||||
|
Parser<Tuple2<A, B>> tuple2<A, B>(Parser<A> a, Parser<B> b) {
|
||||||
|
return chain([a, b]).map((r) {
|
||||||
|
return Tuple2(r.value?[0] as A, r.value?[1] as B);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A typed parser that parses a sequence of 3 values of different types.
|
||||||
|
Parser<Tuple3<A, B, C>> tuple3<A, B, C>(Parser<A> a, Parser<B> b, Parser<C> c) {
|
||||||
|
return chain([a, b, c]).map((r) {
|
||||||
|
return Tuple3(r.value?[0] as A, r.value?[1] as B, r.value?[2] as C);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A typed parser that parses a sequence of 4 values of different types.
|
||||||
|
Parser<Tuple4<A, B, C, D>> tuple4<A, B, C, D>(
|
||||||
|
Parser<A> a, Parser<B> b, Parser<C> c, Parser<D> d) {
|
||||||
|
return chain([a, b, c, d]).map((r) {
|
||||||
|
return Tuple4(
|
||||||
|
r.value?[0] as A, r.value?[1] as B, r.value?[2] as C, r.value?[3] as D);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A typed parser that parses a sequence of 5 values of different types.
|
||||||
|
Parser<Tuple5<A, B, C, D, E>> tuple5<A, B, C, D, E>(
|
||||||
|
Parser<A> a, Parser<B> b, Parser<C> c, Parser<D> d, Parser<E> e) {
|
||||||
|
return chain([a, b, c, d, e]).map((r) {
|
||||||
|
return Tuple5(r.value?[0] as A, r.value?[1] as B, r.value?[2] as C,
|
||||||
|
r.value?[3] as D, r.value?[4] as E);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A typed parser that parses a sequence of 6 values of different types.
|
||||||
|
Parser<Tuple6<A, B, C, D, E, F>> tuple6<A, B, C, D, E, F>(Parser<A> a,
|
||||||
|
Parser<B> b, Parser<C> c, Parser<D> d, Parser<E> e, Parser<F> f) {
|
||||||
|
return chain([a, b, c, d, e, f]).map((r) {
|
||||||
|
return Tuple6(r.value?[0] as A, r.value?[1] as B, r.value?[2] as C,
|
||||||
|
r.value?[3] as D, r.value?[4] as E, r.value?[5] as F);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A typed parser that parses a sequence of 7 values of different types.
|
||||||
|
Parser<Tuple7<A, B, C, D, E, F, G>> tuple7<A, B, C, D, E, F, G>(
|
||||||
|
Parser<A> a,
|
||||||
|
Parser<B> b,
|
||||||
|
Parser<C> c,
|
||||||
|
Parser<D> d,
|
||||||
|
Parser<E> e,
|
||||||
|
Parser<F> f,
|
||||||
|
Parser<G> g) {
|
||||||
|
return chain([a, b, c, d, e, f, g]).map((r) {
|
||||||
|
return Tuple7(r.value?[0] as A, r.value?[1] as B, r.value?[2] as C,
|
||||||
|
r.value?[3] as D, r.value?[4] as E, r.value?[5] as F, r.value?[6] as G);
|
||||||
|
});
|
||||||
|
}
|
25
common/combinator/lib/src/combinator/value.dart
Normal file
25
common/combinator/lib/src/combinator/value.dart
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
part of 'combinator.dart';
|
||||||
|
|
||||||
|
class _Value<T> extends Parser<T> {
|
||||||
|
final Parser<T> parser;
|
||||||
|
final T Function(ParseResult<T>) f;
|
||||||
|
|
||||||
|
_Value(this.parser, this.f);
|
||||||
|
|
||||||
|
@override
|
||||||
|
ParseResult<T> __parse(ParseArgs args) {
|
||||||
|
var result = parser._parse(args.increaseDepth()).change(parser: this);
|
||||||
|
return result.successful ? result.change(value: f(result)) : result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void stringify(CodeBuffer buffer) {
|
||||||
|
buffer
|
||||||
|
..writeln('set value($f) (')
|
||||||
|
..indent();
|
||||||
|
parser.stringify(buffer);
|
||||||
|
buffer
|
||||||
|
..outdent()
|
||||||
|
..writeln(')');
|
||||||
|
}
|
||||||
|
}
|
23
common/combinator/lib/src/error.dart
Normal file
23
common/combinator/lib/src/error.dart
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import 'package:source_span/source_span.dart';
|
||||||
|
|
||||||
|
class SyntaxError implements Exception {
|
||||||
|
final SyntaxErrorSeverity severity;
|
||||||
|
final String? message;
|
||||||
|
final FileSpan? span;
|
||||||
|
String? _toolString;
|
||||||
|
|
||||||
|
SyntaxError(this.severity, this.message, this.span);
|
||||||
|
|
||||||
|
String? get toolString {
|
||||||
|
if (_toolString != null) return _toolString;
|
||||||
|
var type = severity == SyntaxErrorSeverity.warning ? 'warning' : 'error';
|
||||||
|
return _toolString = '$type: ${span!.start.toolString}: $message';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum SyntaxErrorSeverity {
|
||||||
|
warning,
|
||||||
|
error,
|
||||||
|
info,
|
||||||
|
hint,
|
||||||
|
}
|
425
common/combinator/pubspec.lock
Normal file
425
common/combinator/pubspec.lock
Normal file
|
@ -0,0 +1,425 @@
|
||||||
|
# Generated by pub
|
||||||
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
|
packages:
|
||||||
|
_fe_analyzer_shared:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: _fe_analyzer_shared
|
||||||
|
sha256: "45cfa8471b89fb6643fe9bf51bd7931a76b8f5ec2d65de4fb176dba8d4f22c77"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "73.0.0"
|
||||||
|
_macros:
|
||||||
|
dependency: transitive
|
||||||
|
description: dart
|
||||||
|
source: sdk
|
||||||
|
version: "0.3.2"
|
||||||
|
analyzer:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: analyzer
|
||||||
|
sha256: "4959fec185fe70cce007c57e9ab6983101dbe593d2bf8bbfb4453aaec0cf470a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.8.0"
|
||||||
|
args:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: args
|
||||||
|
sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.6.0"
|
||||||
|
async:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: async
|
||||||
|
sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.12.0"
|
||||||
|
boolean_selector:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: boolean_selector
|
||||||
|
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.2"
|
||||||
|
charcode:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: charcode
|
||||||
|
sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.0"
|
||||||
|
collection:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: collection
|
||||||
|
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.19.1"
|
||||||
|
convert:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: convert
|
||||||
|
sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.2"
|
||||||
|
coverage:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: coverage
|
||||||
|
sha256: e3493833ea012784c740e341952298f1cc77f1f01b1bbc3eb4eecf6984fb7f43
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.11.1"
|
||||||
|
crypto:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: crypto
|
||||||
|
sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.6"
|
||||||
|
file:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file
|
||||||
|
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "7.0.1"
|
||||||
|
frontend_server_client:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: frontend_server_client
|
||||||
|
sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.0"
|
||||||
|
glob:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: glob
|
||||||
|
sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.2"
|
||||||
|
http_multi_server:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http_multi_server
|
||||||
|
sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.2.1"
|
||||||
|
http_parser:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http_parser
|
||||||
|
sha256: "76d306a1c3afb33fe82e2bbacad62a61f409b5634c915fceb0d799de1a913360"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.1.1"
|
||||||
|
io:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: io
|
||||||
|
sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.5"
|
||||||
|
js:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: js
|
||||||
|
sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.7.1"
|
||||||
|
lints:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: lints
|
||||||
|
sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.0"
|
||||||
|
logging:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: logging
|
||||||
|
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.0"
|
||||||
|
macros:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: macros
|
||||||
|
sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.2-main.4"
|
||||||
|
matcher:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: matcher
|
||||||
|
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.12.16+1"
|
||||||
|
meta:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: meta
|
||||||
|
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.16.0"
|
||||||
|
mime:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: mime
|
||||||
|
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.0"
|
||||||
|
node_preamble:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: node_preamble
|
||||||
|
sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.2"
|
||||||
|
package_config:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: package_config
|
||||||
|
sha256: "92d4488434b520a62570293fbd33bb556c7d49230791c1b4bbd973baf6d2dc67"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.1"
|
||||||
|
path:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path
|
||||||
|
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.9.1"
|
||||||
|
platform_code_buffer:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
path: "../code_buffer"
|
||||||
|
relative: true
|
||||||
|
source: path
|
||||||
|
version: "5.2.0"
|
||||||
|
pool:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pool
|
||||||
|
sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.5.1"
|
||||||
|
pub_semver:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pub_semver
|
||||||
|
sha256: "7b3cfbf654f3edd0c6298ecd5be782ce997ddf0e00531b9464b55245185bbbbd"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.5"
|
||||||
|
shelf:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf
|
||||||
|
sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.2"
|
||||||
|
shelf_packages_handler:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf_packages_handler
|
||||||
|
sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.2"
|
||||||
|
shelf_static:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf_static
|
||||||
|
sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.3"
|
||||||
|
shelf_web_socket:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf_web_socket
|
||||||
|
sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.1"
|
||||||
|
source_map_stack_trace:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_map_stack_trace
|
||||||
|
sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.2"
|
||||||
|
source_maps:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_maps
|
||||||
|
sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.10.13"
|
||||||
|
source_span:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: source_span
|
||||||
|
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.10.1"
|
||||||
|
stack_trace:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stack_trace
|
||||||
|
sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.12.0"
|
||||||
|
stream_channel:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stream_channel
|
||||||
|
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.2"
|
||||||
|
string_scanner:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: string_scanner
|
||||||
|
sha256: "0bd04f5bb74fcd6ff0606a888a30e917af9bd52820b178eaa464beb11dca84b6"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.0"
|
||||||
|
term_glyph:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: term_glyph
|
||||||
|
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.1"
|
||||||
|
test:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: test
|
||||||
|
sha256: "22eb7769bee38c7e032d532e8daa2e1cc901b799f603550a4db8f3a5f5173ea2"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.25.12"
|
||||||
|
test_api:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: test_api
|
||||||
|
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.7.4"
|
||||||
|
test_core:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: test_core
|
||||||
|
sha256: "84d17c3486c8dfdbe5e12a50c8ae176d15e2a771b96909a9442b40173649ccaa"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.6.8"
|
||||||
|
tuple:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: tuple
|
||||||
|
sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.2"
|
||||||
|
typed_data:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: typed_data
|
||||||
|
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.0"
|
||||||
|
vm_service:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: vm_service
|
||||||
|
sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "15.0.0"
|
||||||
|
watcher:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: watcher
|
||||||
|
sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0"
|
||||||
|
web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web
|
||||||
|
sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0"
|
||||||
|
web_socket:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web_socket
|
||||||
|
sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.6"
|
||||||
|
web_socket_channel:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web_socket_channel
|
||||||
|
sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.1"
|
||||||
|
webkit_inspection_protocol:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: webkit_inspection_protocol
|
||||||
|
sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.1"
|
||||||
|
yaml:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: yaml
|
||||||
|
sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.2"
|
||||||
|
sdks:
|
||||||
|
dart: ">=3.5.0 <4.0.0"
|
15
common/combinator/pubspec.yaml
Normal file
15
common/combinator/pubspec.yaml
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
name: platform_combinator
|
||||||
|
version: 5.2.0
|
||||||
|
description: Packrat parser combinators that support static typing, generics, file spans, memoization, and more.
|
||||||
|
homepage: https://github.com/dart-backend/belatuk-common-utilities/tree/main/packages/combinator
|
||||||
|
environment:
|
||||||
|
sdk: '>=3.3.0 <4.0.0'
|
||||||
|
dependencies:
|
||||||
|
platform_code_buffer: ^5.0.0
|
||||||
|
matcher: ^0.12.10
|
||||||
|
source_span: ^1.8.1
|
||||||
|
string_scanner: ^1.1.0
|
||||||
|
tuple: ^2.0.0
|
||||||
|
dev_dependencies:
|
||||||
|
test: ^1.24.0
|
||||||
|
lints: ^4.0.0
|
12
common/combinator/test/all.dart
Normal file
12
common/combinator/test/all.dart
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
import 'list_test.dart' as list;
|
||||||
|
import 'match_test.dart' as match;
|
||||||
|
import 'misc_test.dart' as misc;
|
||||||
|
import 'value_test.dart' as value;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('list', list.main);
|
||||||
|
group('match', match.main);
|
||||||
|
group('value', value.main);
|
||||||
|
misc.main();
|
||||||
|
}
|
3
common/combinator/test/common.dart
Normal file
3
common/combinator/test/common.dart
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
import 'package:string_scanner/string_scanner.dart';
|
||||||
|
|
||||||
|
SpanScanner scan(String text) => SpanScanner(text);
|
22
common/combinator/test/list_test.dart
Normal file
22
common/combinator/test/list_test.dart
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import 'package:platform_combinator/combinator.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
import 'common.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
var number = chain([
|
||||||
|
match(RegExp(r'[0-9]+')).value((r) => int.parse(r.span!.text)),
|
||||||
|
match(',').opt(),
|
||||||
|
]).first().cast<int>();
|
||||||
|
|
||||||
|
var numbers = number.plus();
|
||||||
|
|
||||||
|
test('sort', () {
|
||||||
|
var parser = numbers.sort((a, b) => a.compareTo(b));
|
||||||
|
expect(parser.parse(scan('21,2,3,34,20')).value, [2, 3, 20, 21, 34]);
|
||||||
|
});
|
||||||
|
test('reduce', () {
|
||||||
|
var parser = numbers.reduce((a, b) => a + b);
|
||||||
|
expect(parser.parse(scan('21,2,3,34,20')).value, 80);
|
||||||
|
expect(parser.parse(scan('not numbers')).value, isNull);
|
||||||
|
});
|
||||||
|
}
|
16
common/combinator/test/match_test.dart
Normal file
16
common/combinator/test/match_test.dart
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import 'package:platform_combinator/combinator.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
import 'common.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
test('match string', () {
|
||||||
|
expect(match('hello').parse(scan('hello world')).successful, isTrue);
|
||||||
|
});
|
||||||
|
test('match start only', () {
|
||||||
|
expect(match('hello').parse(scan('goodbye hello')).successful, isFalse);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('fail if no match', () {
|
||||||
|
expect(match('hello').parse(scan('world')).successful, isFalse);
|
||||||
|
});
|
||||||
|
}
|
65
common/combinator/test/misc_test.dart
Normal file
65
common/combinator/test/misc_test.dart
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
import 'package:platform_combinator/combinator.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
import 'common.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
test('advance', () {
|
||||||
|
var scanner = scan('hello world');
|
||||||
|
|
||||||
|
// Casted -> dynamic just for the sake of coverage.
|
||||||
|
var parser = match('he').forward(2).castDynamic();
|
||||||
|
parser.parse(scanner);
|
||||||
|
expect(scanner.position, 4);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('change', () {
|
||||||
|
var parser = match('hello').change((r) => r.change(value: 23));
|
||||||
|
expect(parser.parse(scan('helloworld')).value, 23);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('check', () {
|
||||||
|
var parser = match<int>(RegExp(r'[A-Za-z]+'))
|
||||||
|
.value((r) => r.span!.length)
|
||||||
|
.check(greaterThan(3));
|
||||||
|
expect(parser.parse(scan('helloworld')).successful, isTrue);
|
||||||
|
expect(parser.parse(scan('yo')).successful, isFalse);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('map', () {
|
||||||
|
var parser = match(RegExp(r'[A-Za-z]+')).map<int>((r) => r.span!.length);
|
||||||
|
expect(parser.parse(scan('hello')).value, 5);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('negate', () {
|
||||||
|
var parser = match('hello').negate(errorMessage: 'world');
|
||||||
|
expect(parser.parse(scan('goodbye world')).successful, isTrue);
|
||||||
|
expect(parser.parse(scan('hello world')).successful, isFalse);
|
||||||
|
expect(parser.parse(scan('hello world')).errors.first.message, 'world');
|
||||||
|
});
|
||||||
|
|
||||||
|
group('opt', () {
|
||||||
|
var single = match('hello').opt(backtrack: true);
|
||||||
|
var list = match('hel').then(match('lo')).opt();
|
||||||
|
|
||||||
|
test('succeeds if present', () {
|
||||||
|
expect(single.parse(scan('hello')).successful, isTrue);
|
||||||
|
expect(list.parse(scan('hello')).successful, isTrue);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('succeeds if not present', () {
|
||||||
|
expect(single.parse(scan('goodbye')).successful, isTrue);
|
||||||
|
expect(list.parse(scan('goodbye')).successful, isTrue);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('backtracks if not present', () {
|
||||||
|
for (var parser in [single, list]) {
|
||||||
|
var scanner = scan('goodbye');
|
||||||
|
var pos = scanner.position;
|
||||||
|
parser.parse(scanner);
|
||||||
|
expect(scanner.position, pos);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('safe', () {});
|
||||||
|
}
|
53
common/combinator/test/recursion_test.dart
Normal file
53
common/combinator/test/recursion_test.dart
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
void main() {}
|
||||||
|
|
||||||
|
/*
|
||||||
|
void main() {
|
||||||
|
var number = match( RegExp(r'-?[0-9]+(\.[0-9]+)?'))
|
||||||
|
.map<num>((r) => num.parse(r.span.text));
|
||||||
|
|
||||||
|
var term = reference<num>();
|
||||||
|
|
||||||
|
var r = Recursion<num>();
|
||||||
|
|
||||||
|
r.prefix = [number];
|
||||||
|
|
||||||
|
r.infix.addAll({
|
||||||
|
match('*'): (l, r, _) => l * r,
|
||||||
|
match('/'): (l, r, _) => l / r,
|
||||||
|
match('+'): (l, r, _) => l + r,
|
||||||
|
match('-'): (l, r, _) => l - r,
|
||||||
|
|
||||||
|
|
||||||
|
match('-'): (l, r, _) => l - r,
|
||||||
|
match('+'): (l, r, _) => l + r,
|
||||||
|
match('/'): (l, r, _) => l / r,
|
||||||
|
match('*'): (l, r, _) => l * r,
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
term.parser = r.precedence(0);
|
||||||
|
|
||||||
|
num parse(String text) {
|
||||||
|
var scanner = SpanScanner(text);
|
||||||
|
var result = term.parse(scanner);
|
||||||
|
print(result.span.highlight());
|
||||||
|
return result.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
test('prefix', () {
|
||||||
|
expect(parse('24'), 24);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('infix', () {
|
||||||
|
expect(parse('12/6'), 2);
|
||||||
|
expect(parse('24+23'), 47);
|
||||||
|
expect(parse('24-23'), 1);
|
||||||
|
expect(parse('4*3'), 12);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('precedence', () {
|
||||||
|
expect(parse('2+3*5*2'), 15);
|
||||||
|
//expect(parse('2+3+5-2*2'), 15);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
*/
|
15
common/combinator/test/value_test.dart
Normal file
15
common/combinator/test/value_test.dart
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import 'package:platform_combinator/combinator.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
import 'common.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
var parser = match('hello').value((r) => 'world');
|
||||||
|
|
||||||
|
test('sets value', () {
|
||||||
|
expect(parser.parse(scan('hello world')).value, 'world');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('no value if no match', () {
|
||||||
|
expect(parser.parse(scan('goodbye world')).value, isNull);
|
||||||
|
});
|
||||||
|
}
|
12
common/html_builder/AUTHORS.md
Normal file
12
common/html_builder/AUTHORS.md
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
Primary Authors
|
||||||
|
===============
|
||||||
|
|
||||||
|
* __[Thomas Hii](dukefirehawk.apps@gmail.com)__
|
||||||
|
|
||||||
|
Thomas is the current maintainer of the code base. He has refactored and migrated the
|
||||||
|
code base to support NNBD.
|
||||||
|
|
||||||
|
* __[Tobe O](thosakwe@gmail.com)__
|
||||||
|
|
||||||
|
Tobe has written much of the original code prior to NNBD migration. He has moved on and
|
||||||
|
is no longer involved with the project.
|
66
common/html_builder/CHANGELOG.md
Normal file
66
common/html_builder/CHANGELOG.md
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
# Change Log
|
||||||
|
|
||||||
|
## 5.2.0
|
||||||
|
|
||||||
|
* Require Dart >= 3.3
|
||||||
|
* Updated `lints` to 4.0.0
|
||||||
|
|
||||||
|
## 5.1.0
|
||||||
|
|
||||||
|
* Updated `lints` to 3.0.0
|
||||||
|
|
||||||
|
## 5.0.0
|
||||||
|
|
||||||
|
* Require Dart >= 3.0
|
||||||
|
|
||||||
|
## 5.0.0-beta.1
|
||||||
|
|
||||||
|
* Require Dart >= 3.0
|
||||||
|
|
||||||
|
## 4.0.0
|
||||||
|
|
||||||
|
* Require Dart >= 2.17
|
||||||
|
* Added hashCode
|
||||||
|
|
||||||
|
## 3.0.2
|
||||||
|
|
||||||
|
* Fixed license link
|
||||||
|
|
||||||
|
## 3.0.1
|
||||||
|
|
||||||
|
* Update broken link in pubspec
|
||||||
|
|
||||||
|
## 3.0.0
|
||||||
|
|
||||||
|
* Upgraded from `pendantic` to `lints` linter
|
||||||
|
* Removed deprecated parameters
|
||||||
|
* Published as `platform_html_builder` package
|
||||||
|
|
||||||
|
## 2.0.3
|
||||||
|
|
||||||
|
* Added an example
|
||||||
|
* Updated README
|
||||||
|
|
||||||
|
## 2.0.2
|
||||||
|
|
||||||
|
* Run `dartfmt -w .`
|
||||||
|
|
||||||
|
## 2.0.1
|
||||||
|
|
||||||
|
* Added pedantic dart rules
|
||||||
|
|
||||||
|
## 2.0.0
|
||||||
|
|
||||||
|
* Migrated to work with Dart SDK 2.12.x NNBD
|
||||||
|
|
||||||
|
## 1.0.4
|
||||||
|
|
||||||
|
* Added `rebuild`, `rebuildRecursive`, and `NodeBuilder`.
|
||||||
|
|
||||||
|
## 1.0.3
|
||||||
|
|
||||||
|
* Dart 2 ready!
|
||||||
|
|
||||||
|
## 1.0.2
|
||||||
|
|
||||||
|
* Changed `h` and the `Node` constructor to take `Iterable`s of children, instead of just `List`s.
|
113
common/html_builder/README.md
Normal file
113
common/html_builder/README.md
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
# Betaluk Html Builder
|
||||||
|
|
||||||
|
![Pub Version (including pre-releases)](https://img.shields.io/pub/v/platform_html_builder?include_prereleases)
|
||||||
|
[![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety)
|
||||||
|
[![License](https://img.shields.io/github/license/dart-backend/belatuk-common-utilities)](https://github.com/dart-backend/belatuk-common-utilities/blob/main/packages/html_builder/LICENSE)
|
||||||
|
|
||||||
|
**Replacement of `package:html_builder` with breaking changes to support NNBD.**
|
||||||
|
|
||||||
|
This package builds HTML AST's and renders them to HTML. It can be used as an internal DSL, i.e. for a templating engine.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
In your `pubspec.yaml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
dependencies:
|
||||||
|
platform_html_builder: ^5.1.0
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```dart
|
||||||
|
import 'package:platform_html_builder/platform_html_builder.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
// Akin to React.createElement(...);
|
||||||
|
var $el = h('my-element', p: {}, c: []);
|
||||||
|
|
||||||
|
// Attributes can be plain Strings.
|
||||||
|
h('foo', p: {
|
||||||
|
'bar': 'baz'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Null attributes do not appear.
|
||||||
|
h('foo', p: {
|
||||||
|
'does-not-appear': null
|
||||||
|
});
|
||||||
|
|
||||||
|
// If an attribute is a bool, then it will only appear if its value is true.
|
||||||
|
h('foo', p: {
|
||||||
|
'appears': true,
|
||||||
|
'does-not-appear': false
|
||||||
|
});
|
||||||
|
|
||||||
|
// Or, a String or Map.
|
||||||
|
h('foo', p: {
|
||||||
|
'style': 'background-color: white; color: red;'
|
||||||
|
});
|
||||||
|
|
||||||
|
h('foo', p: {
|
||||||
|
'style': {
|
||||||
|
'background-color': 'white',
|
||||||
|
'color': 'red'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Or, a String or Iterable.
|
||||||
|
h('foo', p: {
|
||||||
|
'class': 'a b'
|
||||||
|
});
|
||||||
|
|
||||||
|
h('foo', p: {
|
||||||
|
'class': ['a', 'b']
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Standard HTML5 elements:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
import 'package:platform_html_builder/elements.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
var $dom = html(lang: 'en', c: [
|
||||||
|
head(c: [
|
||||||
|
title(c: [text('Hello, world!')])
|
||||||
|
]),
|
||||||
|
body(c: [
|
||||||
|
h1(c: [text('Hello, world!')]),
|
||||||
|
p(c: [text('Ok')])
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Rendering to HTML:
|
||||||
|
|
||||||
|
```dart
|
||||||
|
String html = StringRenderer().render($dom);
|
||||||
|
```
|
||||||
|
|
||||||
|
Example implementation with the [Angel3](https://pub.dev/packages/angel3_framework) backend framework,
|
||||||
|
which uses [dedicated html_builder package](https://github.com/dukefirehawk/angel/tree/html):
|
||||||
|
|
||||||
|
```dart
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:belatuk_framework/belatuk_framework.dart';
|
||||||
|
import 'package:platform_html_builder/elements.dart';
|
||||||
|
|
||||||
|
configureViews(Angel app) async {
|
||||||
|
app.get('/foo/:id', (req, res) async {
|
||||||
|
var foo = await app.service('foo').read(req.params['id']);
|
||||||
|
return html(c: [
|
||||||
|
head(c: [
|
||||||
|
title(c: [text(foo.name)])
|
||||||
|
]),
|
||||||
|
body(c: [
|
||||||
|
h1(c: [text(foo.name)])
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
1
common/html_builder/analysis_options.yaml
Normal file
1
common/html_builder/analysis_options.yaml
Normal file
|
@ -0,0 +1 @@
|
||||||
|
include: package:lints/recommended.yaml
|
15
common/html_builder/example/main.dart
Normal file
15
common/html_builder/example/main.dart
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import 'package:platform_html_builder/elements.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
var dom = html(lang: 'en', c: [
|
||||||
|
head(c: [
|
||||||
|
title(c: [text('Hello, world!')])
|
||||||
|
]),
|
||||||
|
body(c: [
|
||||||
|
h1(c: [text('Hello, world!')]),
|
||||||
|
p(c: [text('Ok')])
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
|
||||||
|
print(dom);
|
||||||
|
}
|
1841
common/html_builder/lib/elements.dart
Normal file
1841
common/html_builder/lib/elements.dart
Normal file
File diff suppressed because it is too large
Load diff
4
common/html_builder/lib/html_builder.dart
Normal file
4
common/html_builder/lib/html_builder.dart
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
export 'src/mutations.dart';
|
||||||
|
export 'src/node.dart';
|
||||||
|
export 'src/node_builder.dart';
|
||||||
|
export 'src/renderer.dart';
|
20
common/html_builder/lib/src/mutations.dart
Normal file
20
common/html_builder/lib/src/mutations.dart
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import 'node.dart';
|
||||||
|
import 'node_builder.dart';
|
||||||
|
|
||||||
|
/// Returns a function that rebuilds an arbitrary [Node] by applying the [transform] to it.
|
||||||
|
Node Function(Node) rebuild(NodeBuilder Function(NodeBuilder) transform,
|
||||||
|
{bool selfClosing = false}) {
|
||||||
|
return (node) =>
|
||||||
|
transform(NodeBuilder.from(node)).build(selfClosing: selfClosing);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Applies [f] to all children of this node, recursively.
|
||||||
|
///
|
||||||
|
/// Use this alongside [rebuild].
|
||||||
|
Node Function(Node) rebuildRecursive(Node Function(Node) f) {
|
||||||
|
Node build(Node node) {
|
||||||
|
return NodeBuilder.from(f(node)).mapChildren(build).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
return build;
|
||||||
|
}
|
73
common/html_builder/lib/src/node.dart
Normal file
73
common/html_builder/lib/src/node.dart
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
|
|
||||||
|
/// Shorthand function to generate a new [Node].
|
||||||
|
Node h(String tagName,
|
||||||
|
[Map<String, dynamic> attributes = const {},
|
||||||
|
Iterable<Node> children = const []]) =>
|
||||||
|
Node(tagName, attributes, children);
|
||||||
|
|
||||||
|
/// Represents an HTML node.
|
||||||
|
class Node {
|
||||||
|
final String tagName;
|
||||||
|
final Map<String, dynamic> attributes = {};
|
||||||
|
final List<Node> children = [];
|
||||||
|
|
||||||
|
Node(this.tagName,
|
||||||
|
[Map<String, dynamic> attributes = const {},
|
||||||
|
Iterable<Node> children = const []]) {
|
||||||
|
this
|
||||||
|
..attributes.addAll(attributes)
|
||||||
|
..children.addAll(children);
|
||||||
|
}
|
||||||
|
|
||||||
|
Node._selfClosing(this.tagName,
|
||||||
|
[Map<String, dynamic> attributes = const {}]) {
|
||||||
|
this.attributes.addAll(attributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(other) {
|
||||||
|
return other is Node &&
|
||||||
|
other.tagName == tagName &&
|
||||||
|
const ListEquality<Node>().equals(other.children, children) &&
|
||||||
|
const MapEquality<String, dynamic>()
|
||||||
|
.equals(other.attributes, attributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
int hash = Object.hash(tagName, Object.hashAll(children));
|
||||||
|
return Object.hash(hash, Object.hashAll(attributes.values));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a self-closing tag, i.e. `<br>`.
|
||||||
|
class SelfClosingNode extends Node {
|
||||||
|
/*
|
||||||
|
@override
|
||||||
|
final String tagName;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final Map<String, dynamic> attributes = {};
|
||||||
|
*/
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Node> get children => List<Node>.unmodifiable([]);
|
||||||
|
|
||||||
|
// ignore: use_super_parameters
|
||||||
|
SelfClosingNode(String tagName, [Map<String, dynamic> attributes = const {}])
|
||||||
|
: super._selfClosing(tagName, attributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a text node.
|
||||||
|
class TextNode extends Node {
|
||||||
|
final String text;
|
||||||
|
|
||||||
|
TextNode(this.text) : super(':text');
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(other) => other is TextNode && other.text == text;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => text.hashCode;
|
||||||
|
}
|
108
common/html_builder/lib/src/node_builder.dart
Normal file
108
common/html_builder/lib/src/node_builder.dart
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
import 'node.dart';
|
||||||
|
|
||||||
|
/// Helper class to build nodes.
|
||||||
|
class NodeBuilder {
|
||||||
|
final String tagName;
|
||||||
|
final Map<String, dynamic> attributes;
|
||||||
|
final Iterable<Node> children;
|
||||||
|
Node? _existing;
|
||||||
|
|
||||||
|
NodeBuilder(this.tagName,
|
||||||
|
{this.attributes = const {}, this.children = const []});
|
||||||
|
|
||||||
|
/// Creates a [NodeBuilder] that just spits out an already-existing [Node].
|
||||||
|
factory NodeBuilder.existing(Node existingNode) =>
|
||||||
|
NodeBuilder(existingNode.tagName).._existing = existingNode;
|
||||||
|
|
||||||
|
factory NodeBuilder.from(Node node) => NodeBuilder(node.tagName,
|
||||||
|
attributes: Map<String, dynamic>.from(node.attributes),
|
||||||
|
children: List<Node>.from(node.children));
|
||||||
|
|
||||||
|
/// Builds the node.
|
||||||
|
Node build({bool selfClosing = false}) =>
|
||||||
|
_existing ??
|
||||||
|
(selfClosing
|
||||||
|
? SelfClosingNode(tagName, attributes)
|
||||||
|
: Node(tagName, attributes, children));
|
||||||
|
|
||||||
|
/// Produce a modified copy of this builder.
|
||||||
|
NodeBuilder change(
|
||||||
|
{String? tagName,
|
||||||
|
Map<String, dynamic>? attributes,
|
||||||
|
Iterable<Node>? children}) {
|
||||||
|
return NodeBuilder(tagName ?? this.tagName,
|
||||||
|
attributes: attributes ?? this.attributes,
|
||||||
|
children: children ?? this.children);
|
||||||
|
}
|
||||||
|
|
||||||
|
NodeBuilder changeTagName(String tagName) => change(tagName: tagName);
|
||||||
|
|
||||||
|
NodeBuilder changeAttributes(Map<String, dynamic> attributes) =>
|
||||||
|
change(attributes: attributes);
|
||||||
|
|
||||||
|
NodeBuilder changeChildren(Iterable<Node> children) =>
|
||||||
|
change(children: children);
|
||||||
|
|
||||||
|
NodeBuilder changeAttributesMapped(
|
||||||
|
Map<String, dynamic> Function(Map<String, dynamic>) f) {
|
||||||
|
var map = Map<String, dynamic>.from(attributes);
|
||||||
|
return changeAttributes(f(map));
|
||||||
|
}
|
||||||
|
|
||||||
|
NodeBuilder changeChildrenMapped(Iterable<Node> Function(List<Node>) f) {
|
||||||
|
var list = List<Node>.from(children);
|
||||||
|
return changeChildren(f(list));
|
||||||
|
}
|
||||||
|
|
||||||
|
NodeBuilder mapChildren(Node Function(Node) f) =>
|
||||||
|
changeChildrenMapped((list) => list.map(f));
|
||||||
|
|
||||||
|
NodeBuilder mapAttributes(
|
||||||
|
MapEntry<String, dynamic> Function(String, dynamic) f) =>
|
||||||
|
changeAttributesMapped((map) => map.map(f));
|
||||||
|
|
||||||
|
NodeBuilder setAttribute(String name, dynamic value) =>
|
||||||
|
changeAttributesMapped((map) => map..[name] = value);
|
||||||
|
|
||||||
|
NodeBuilder addChild(Node child) =>
|
||||||
|
changeChildrenMapped((list) => list..add(child));
|
||||||
|
|
||||||
|
NodeBuilder removeChild(Node child) =>
|
||||||
|
changeChildrenMapped((list) => list..remove(child));
|
||||||
|
|
||||||
|
NodeBuilder removeAttribute(String name) =>
|
||||||
|
changeAttributesMapped((map) => map..remove(name));
|
||||||
|
|
||||||
|
NodeBuilder setId(String id) => setAttribute('id', id);
|
||||||
|
|
||||||
|
NodeBuilder setClassName(String className) =>
|
||||||
|
setAttribute('class', className);
|
||||||
|
|
||||||
|
NodeBuilder setClasses(Iterable<String> classes) =>
|
||||||
|
setClassName(classes.join(' '));
|
||||||
|
|
||||||
|
NodeBuilder setClassesMapped(Iterable<String> Function(List<String>) f) {
|
||||||
|
var clazz = attributes['class'];
|
||||||
|
var classes = <String>[];
|
||||||
|
|
||||||
|
if (clazz is String) {
|
||||||
|
classes.addAll(clazz.split(' '));
|
||||||
|
} else if (clazz is Iterable) {
|
||||||
|
classes.addAll(clazz.map((s) => s.toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return setClasses(f(classes));
|
||||||
|
}
|
||||||
|
|
||||||
|
NodeBuilder addClass(String className) => setClassesMapped(
|
||||||
|
(classes) => classes.contains(className) ? classes : classes
|
||||||
|
..add(className));
|
||||||
|
|
||||||
|
NodeBuilder removeClass(String className) =>
|
||||||
|
setClassesMapped((classes) => classes..remove(className));
|
||||||
|
|
||||||
|
NodeBuilder toggleClass(String className) =>
|
||||||
|
setClassesMapped((classes) => classes.contains(className)
|
||||||
|
? (classes..remove(className))
|
||||||
|
: (classes..add(className)));
|
||||||
|
}
|
140
common/html_builder/lib/src/renderer.dart
Normal file
140
common/html_builder/lib/src/renderer.dart
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
import 'node.dart';
|
||||||
|
|
||||||
|
/// An object that can render a DOM tree into another representation, i.e. a `String`.
|
||||||
|
abstract class Renderer<T> {
|
||||||
|
/// Renders a DOM tree into another representation.
|
||||||
|
T render(Node rootNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Renders a DOM tree into a HTML string.
|
||||||
|
abstract class StringRenderer implements Renderer<String> {
|
||||||
|
/// Initializes a new [StringRenderer].
|
||||||
|
///
|
||||||
|
/// If [html5] is not `false` (default: `true`), then self-closing elements will be rendered with a slash before the last angle bracket, ex. `<br />`.
|
||||||
|
/// If [pretty] is `true` (default), then [whitespace] (default: `' '`) will be inserted between nodes.
|
||||||
|
/// You can also provide a [doctype] (default: `html`).
|
||||||
|
factory StringRenderer(
|
||||||
|
{bool html5 = true,
|
||||||
|
bool pretty = true,
|
||||||
|
String doctype = 'html',
|
||||||
|
String whitespace = ' '}) =>
|
||||||
|
pretty == true
|
||||||
|
? _PrettyStringRendererImpl(
|
||||||
|
html5: html5 != false, doctype: doctype, whitespace: whitespace)
|
||||||
|
: _StringRendererImpl(html5: html5 != false, doctype: doctype);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _StringRendererImpl implements StringRenderer {
|
||||||
|
final String? doctype;
|
||||||
|
final bool? html5;
|
||||||
|
|
||||||
|
_StringRendererImpl({this.html5, this.doctype});
|
||||||
|
|
||||||
|
void _renderInto(Node node, StringBuffer buf) {
|
||||||
|
if (node is TextNode) {
|
||||||
|
buf.write(node.text);
|
||||||
|
} else {
|
||||||
|
buf.write('<${node.tagName}');
|
||||||
|
|
||||||
|
node.attributes.forEach((k, v) {
|
||||||
|
if (v == true) {
|
||||||
|
buf.write(' $k');
|
||||||
|
} else if (v == false || v == null) {
|
||||||
|
// Ignore
|
||||||
|
} else if (v is Iterable) {
|
||||||
|
var val = v.join(' ').replaceAll('"', '\\"');
|
||||||
|
buf.write(' $k="$val"');
|
||||||
|
} else if (v is Map) {
|
||||||
|
var val = v.keys
|
||||||
|
.fold<String>('', (out, k) => out += '$k: ${v[k]};')
|
||||||
|
.replaceAll('"', '\\"');
|
||||||
|
buf.write(' $k="$val"');
|
||||||
|
} else {
|
||||||
|
var val = v.toString().replaceAll('"', '\\"');
|
||||||
|
buf.write(' $k="$val"');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (node is SelfClosingNode) {
|
||||||
|
buf.write((html5 != false) ? '>' : '/>');
|
||||||
|
} else {
|
||||||
|
buf.write('>');
|
||||||
|
for (var child in node.children) {
|
||||||
|
_renderInto(child, buf);
|
||||||
|
}
|
||||||
|
buf.write('</${node.tagName}>');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String render(Node rootNode) {
|
||||||
|
var buf = StringBuffer();
|
||||||
|
if (doctype?.isNotEmpty == true) buf.write('<!DOCTYPE $doctype>');
|
||||||
|
_renderInto(rootNode, buf);
|
||||||
|
return buf.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PrettyStringRendererImpl implements StringRenderer {
|
||||||
|
final bool? html5;
|
||||||
|
final String? doctype, whitespace;
|
||||||
|
|
||||||
|
_PrettyStringRendererImpl({this.html5, this.whitespace, this.doctype});
|
||||||
|
|
||||||
|
void _applyTabs(int tabs, StringBuffer buf) {
|
||||||
|
for (var i = 0; i < tabs; i++) {
|
||||||
|
buf.write(whitespace ?? ' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _renderInto(int tabs, Node node, StringBuffer buf) {
|
||||||
|
if (tabs > 0) buf.writeln();
|
||||||
|
_applyTabs(tabs, buf);
|
||||||
|
|
||||||
|
if (node is TextNode) {
|
||||||
|
buf.write(node.text);
|
||||||
|
} else {
|
||||||
|
buf.write('<${node.tagName}');
|
||||||
|
|
||||||
|
node.attributes.forEach((k, v) {
|
||||||
|
if (v == true) {
|
||||||
|
buf.write(' $k');
|
||||||
|
} else if (v == false || v == null) {
|
||||||
|
// Ignore
|
||||||
|
} else if (v is Iterable) {
|
||||||
|
var val = v.join(' ').replaceAll('"', '\\"');
|
||||||
|
buf.write(' $k="$val"');
|
||||||
|
} else if (v is Map) {
|
||||||
|
var val = v.keys
|
||||||
|
.fold<String>('', (out, k) => out += '$k: ${v[k]};')
|
||||||
|
.replaceAll('"', '\\"');
|
||||||
|
buf.write(' $k="$val"');
|
||||||
|
} else {
|
||||||
|
var val = v.toString().replaceAll('"', '\\"');
|
||||||
|
buf.write(' $k="$val"');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (node is SelfClosingNode) {
|
||||||
|
buf.write((html5 != false) ? '>' : '/>');
|
||||||
|
} else {
|
||||||
|
buf.write('>');
|
||||||
|
for (var child in node.children) {
|
||||||
|
_renderInto(tabs + 1, child, buf);
|
||||||
|
}
|
||||||
|
buf.writeln();
|
||||||
|
_applyTabs(tabs, buf);
|
||||||
|
buf.write('</${node.tagName}>');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String render(Node rootNode) {
|
||||||
|
var buf = StringBuffer();
|
||||||
|
if (doctype?.isNotEmpty == true) buf.writeln('<!DOCTYPE $doctype>');
|
||||||
|
_renderInto(0, rootNode, buf);
|
||||||
|
return buf.toString();
|
||||||
|
}
|
||||||
|
}
|
418
common/html_builder/pubspec.lock
Normal file
418
common/html_builder/pubspec.lock
Normal file
|
@ -0,0 +1,418 @@
|
||||||
|
# Generated by pub
|
||||||
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
|
packages:
|
||||||
|
_fe_analyzer_shared:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: _fe_analyzer_shared
|
||||||
|
sha256: "45cfa8471b89fb6643fe9bf51bd7931a76b8f5ec2d65de4fb176dba8d4f22c77"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "73.0.0"
|
||||||
|
_macros:
|
||||||
|
dependency: transitive
|
||||||
|
description: dart
|
||||||
|
source: sdk
|
||||||
|
version: "0.3.2"
|
||||||
|
analyzer:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: analyzer
|
||||||
|
sha256: "4959fec185fe70cce007c57e9ab6983101dbe593d2bf8bbfb4453aaec0cf470a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.8.0"
|
||||||
|
args:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: args
|
||||||
|
sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.6.0"
|
||||||
|
async:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: async
|
||||||
|
sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.12.0"
|
||||||
|
boolean_selector:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: boolean_selector
|
||||||
|
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.2"
|
||||||
|
collection:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: collection
|
||||||
|
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.19.1"
|
||||||
|
convert:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: convert
|
||||||
|
sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.2"
|
||||||
|
coverage:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: coverage
|
||||||
|
sha256: e3493833ea012784c740e341952298f1cc77f1f01b1bbc3eb4eecf6984fb7f43
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.11.1"
|
||||||
|
crypto:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: crypto
|
||||||
|
sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.6"
|
||||||
|
csslib:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: csslib
|
||||||
|
sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.2"
|
||||||
|
file:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file
|
||||||
|
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "7.0.1"
|
||||||
|
frontend_server_client:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: frontend_server_client
|
||||||
|
sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.0"
|
||||||
|
glob:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: glob
|
||||||
|
sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.2"
|
||||||
|
html:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: html
|
||||||
|
sha256: "1fc58edeaec4307368c60d59b7e15b9d658b57d7f3125098b6294153c75337ec"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.15.5"
|
||||||
|
http_multi_server:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http_multi_server
|
||||||
|
sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.2.1"
|
||||||
|
http_parser:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http_parser
|
||||||
|
sha256: "76d306a1c3afb33fe82e2bbacad62a61f409b5634c915fceb0d799de1a913360"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.1.1"
|
||||||
|
io:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: io
|
||||||
|
sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.5"
|
||||||
|
js:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: js
|
||||||
|
sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.7.1"
|
||||||
|
lints:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: lints
|
||||||
|
sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.0"
|
||||||
|
logging:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: logging
|
||||||
|
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.0"
|
||||||
|
macros:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: macros
|
||||||
|
sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.2-main.4"
|
||||||
|
matcher:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: matcher
|
||||||
|
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.12.16+1"
|
||||||
|
meta:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: meta
|
||||||
|
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.16.0"
|
||||||
|
mime:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: mime
|
||||||
|
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.0"
|
||||||
|
node_preamble:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: node_preamble
|
||||||
|
sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.2"
|
||||||
|
package_config:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: package_config
|
||||||
|
sha256: "92d4488434b520a62570293fbd33bb556c7d49230791c1b4bbd973baf6d2dc67"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.1"
|
||||||
|
path:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path
|
||||||
|
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.9.1"
|
||||||
|
pool:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pool
|
||||||
|
sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.5.1"
|
||||||
|
pub_semver:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pub_semver
|
||||||
|
sha256: "7b3cfbf654f3edd0c6298ecd5be782ce997ddf0e00531b9464b55245185bbbbd"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.5"
|
||||||
|
shelf:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf
|
||||||
|
sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.2"
|
||||||
|
shelf_packages_handler:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf_packages_handler
|
||||||
|
sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.2"
|
||||||
|
shelf_static:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf_static
|
||||||
|
sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.3"
|
||||||
|
shelf_web_socket:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf_web_socket
|
||||||
|
sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.1"
|
||||||
|
source_map_stack_trace:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_map_stack_trace
|
||||||
|
sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.2"
|
||||||
|
source_maps:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_maps
|
||||||
|
sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.10.13"
|
||||||
|
source_span:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_span
|
||||||
|
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.10.1"
|
||||||
|
stack_trace:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stack_trace
|
||||||
|
sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.12.0"
|
||||||
|
stream_channel:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stream_channel
|
||||||
|
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.2"
|
||||||
|
string_scanner:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: string_scanner
|
||||||
|
sha256: "0bd04f5bb74fcd6ff0606a888a30e917af9bd52820b178eaa464beb11dca84b6"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.0"
|
||||||
|
term_glyph:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: term_glyph
|
||||||
|
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.1"
|
||||||
|
test:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: test
|
||||||
|
sha256: "22eb7769bee38c7e032d532e8daa2e1cc901b799f603550a4db8f3a5f5173ea2"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.25.12"
|
||||||
|
test_api:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: test_api
|
||||||
|
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.7.4"
|
||||||
|
test_core:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: test_core
|
||||||
|
sha256: "84d17c3486c8dfdbe5e12a50c8ae176d15e2a771b96909a9442b40173649ccaa"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.6.8"
|
||||||
|
typed_data:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: typed_data
|
||||||
|
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.0"
|
||||||
|
vm_service:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: vm_service
|
||||||
|
sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "15.0.0"
|
||||||
|
watcher:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: watcher
|
||||||
|
sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0"
|
||||||
|
web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web
|
||||||
|
sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0"
|
||||||
|
web_socket:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web_socket
|
||||||
|
sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.6"
|
||||||
|
web_socket_channel:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web_socket_channel
|
||||||
|
sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.1"
|
||||||
|
webkit_inspection_protocol:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: webkit_inspection_protocol
|
||||||
|
sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.1"
|
||||||
|
yaml:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: yaml
|
||||||
|
sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.2"
|
||||||
|
sdks:
|
||||||
|
dart: ">=3.5.0 <4.0.0"
|
12
common/html_builder/pubspec.yaml
Normal file
12
common/html_builder/pubspec.yaml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
name: platform_html_builder
|
||||||
|
version: 5.2.0
|
||||||
|
description: Build HTML AST's and render them to HTML. This can be used as an internal DSL, i.e. for a templating engine.
|
||||||
|
homepage: https://github.com/dart-backend/belatuk-common-utilities/tree/main/packages/html_builder
|
||||||
|
environment:
|
||||||
|
sdk: '>=3.3.0 <4.0.0'
|
||||||
|
dependencies:
|
||||||
|
collection: ^1.17.0
|
||||||
|
dev_dependencies:
|
||||||
|
html: ^0.15.0
|
||||||
|
test: ^1.24.0
|
||||||
|
lints: ^4.0.0
|
33
common/html_builder/test/render_test.dart
Normal file
33
common/html_builder/test/render_test.dart
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import 'package:html/parser.dart' as html5;
|
||||||
|
import 'package:platform_html_builder/elements.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
test('pretty', () {
|
||||||
|
var $dom = html(
|
||||||
|
lang: 'en',
|
||||||
|
c: [
|
||||||
|
head(c: [
|
||||||
|
title(c: [text('Hello, world!')])
|
||||||
|
]),
|
||||||
|
body(
|
||||||
|
p: {'unresolved': true},
|
||||||
|
c: [
|
||||||
|
h1(c: [text('Hello, world!')]),
|
||||||
|
br(),
|
||||||
|
hr(),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
var rendered = StringRenderer().render($dom);
|
||||||
|
print(rendered);
|
||||||
|
|
||||||
|
var $parsed = html5.parse(rendered);
|
||||||
|
var $title = $parsed.querySelector('title')!;
|
||||||
|
expect($title.text.trim(), 'Hello, world!');
|
||||||
|
var $h1 = $parsed.querySelector('h1')!;
|
||||||
|
expect($h1.text.trim(), 'Hello, world!');
|
||||||
|
});
|
||||||
|
}
|
5
common/http_server/.gitignore
vendored
Normal file
5
common/http_server/.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
.dart_tool
|
||||||
|
.packages
|
||||||
|
.metals
|
||||||
|
.vscode
|
||||||
|
pubspec.lock
|
3
common/http_server/.test_config
Normal file
3
common/http_server/.test_config
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"test_package": true
|
||||||
|
}
|
9
common/http_server/AUTHORS.md
Normal file
9
common/http_server/AUTHORS.md
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
# Authors
|
||||||
|
|
||||||
|
## Current
|
||||||
|
|
||||||
|
* __[Thomas Hii](dukefirehawk.apps@gmail.com)__
|
||||||
|
|
||||||
|
## Previous
|
||||||
|
|
||||||
|
* Google Inc.
|
112
common/http_server/CHANGELOG.md
Normal file
112
common/http_server/CHANGELOG.md
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
# Change Log
|
||||||
|
|
||||||
|
## 4.5.1
|
||||||
|
|
||||||
|
* Fixed linter warnings
|
||||||
|
|
||||||
|
## 4.5.0
|
||||||
|
|
||||||
|
* Require Dart >= 3.5
|
||||||
|
* Updated `lints` to 5.0.0
|
||||||
|
* Updated `mime` to 2.0.0
|
||||||
|
|
||||||
|
## 4.4.0
|
||||||
|
|
||||||
|
* Updated `lints` to 4.0.0
|
||||||
|
|
||||||
|
## 4.3.0
|
||||||
|
|
||||||
|
* Require Dart >= 3.3
|
||||||
|
|
||||||
|
## 4.2.0
|
||||||
|
|
||||||
|
* Upgraded `lints` to 3.0.0
|
||||||
|
|
||||||
|
## 4.1.2
|
||||||
|
|
||||||
|
* Fixed `_VirtualDirectoryFileStream` class due to change in Dart 3.0.3
|
||||||
|
|
||||||
|
## 4.1.1
|
||||||
|
|
||||||
|
* Updated README
|
||||||
|
|
||||||
|
## 4.1.0
|
||||||
|
|
||||||
|
* Upgraded `test_api` to 0.6.0
|
||||||
|
|
||||||
|
## 4.0.0
|
||||||
|
|
||||||
|
* Require Dart >= 3.0
|
||||||
|
|
||||||
|
## 4.0.0-beta.1
|
||||||
|
|
||||||
|
* Require Dart >= 3.0
|
||||||
|
* Fixed linter warnings
|
||||||
|
|
||||||
|
## 3.0.0
|
||||||
|
|
||||||
|
* Require Dart >= 2.17
|
||||||
|
* Fixed analyzer warnings
|
||||||
|
|
||||||
|
## 2.1.0
|
||||||
|
|
||||||
|
* Updated linter to `package:lints`
|
||||||
|
|
||||||
|
## 2.0.2
|
||||||
|
|
||||||
|
* Transfered repository to `dart-backend`
|
||||||
|
|
||||||
|
## 2.0.1
|
||||||
|
|
||||||
|
* Added example
|
||||||
|
|
||||||
|
## 2.0.0
|
||||||
|
|
||||||
|
* Migrated to `platform_http_server`
|
||||||
|
|
||||||
|
## 1.0.0
|
||||||
|
|
||||||
|
* Migrate to null safety.
|
||||||
|
* Allow multipart form data with specified encodings that don't require
|
||||||
|
decoding.
|
||||||
|
|
||||||
|
## 0.9.8+3
|
||||||
|
|
||||||
|
* Prepare for `HttpClientResponse` SDK change (implements `Stream<Uint8List>`
|
||||||
|
rather than `Stream<List<int>>`).
|
||||||
|
|
||||||
|
## 0.9.8+2
|
||||||
|
|
||||||
|
* Prepare for `File.openRead()` SDK change in signature.
|
||||||
|
|
||||||
|
## 0.9.8+1
|
||||||
|
|
||||||
|
* Fix a Dart 2 type issue.
|
||||||
|
|
||||||
|
## 0.9.8
|
||||||
|
|
||||||
|
* Updates to support Dart 2 constants.
|
||||||
|
|
||||||
|
## 0.9.7
|
||||||
|
|
||||||
|
* Updates to support Dart 2.0 core library changes (wave
|
||||||
|
2.2). See [issue 31847][sdk#31847] for details.
|
||||||
|
|
||||||
|
[sdk#31847]: https://github.com/dart-lang/sdk/issues/31847
|
||||||
|
|
||||||
|
## 0.9.6
|
||||||
|
|
||||||
|
* Updated the secure networking code to the SDKs version 1.15 SecurityContext api
|
||||||
|
|
||||||
|
## 0.9.5+1
|
||||||
|
|
||||||
|
* Updated the layout of package contents.
|
||||||
|
|
||||||
|
## 0.9.5
|
||||||
|
|
||||||
|
* Removed the decoding of HTML entity values (in the form &#xxxxx;) for
|
||||||
|
values when parsing multipart/form-post requests.
|
||||||
|
|
||||||
|
## 0.9.4
|
||||||
|
|
||||||
|
* Fixed bugs in the handling of the Range header
|
3
common/http_server/CONTRIBUTING.md
Normal file
3
common/http_server/CONTRIBUTING.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# Contributing
|
||||||
|
|
||||||
|
Any contributions from the community are welcome.
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue