Removed deprecated packages
This commit is contained in:
parent
70e6f1b6c1
commit
9b000c768e
147 changed files with 3 additions and 7167 deletions
64
packages/body_parser/.gitignore
vendored
64
packages/body_parser/.gitignore
vendored
|
@ -1,64 +0,0 @@
|
||||||
# Created by .ignore support plugin (hsz.mobi)
|
|
||||||
### JetBrains template
|
|
||||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
|
|
||||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
|
||||||
|
|
||||||
# User-specific stuff:
|
|
||||||
.idea/**/workspace.xml
|
|
||||||
.idea/**/tasks.xml
|
|
||||||
.idea/dictionaries
|
|
||||||
|
|
||||||
# Sensitive or high-churn files:
|
|
||||||
.idea/**/dataSources/
|
|
||||||
.idea/**/dataSources.ids
|
|
||||||
.idea/**/dataSources.xml
|
|
||||||
.idea/**/dataSources.local.xml
|
|
||||||
.idea/**/sqlDataSources.xml
|
|
||||||
.idea/**/dynamic.xml
|
|
||||||
.idea/**/uiDesigner.xml
|
|
||||||
|
|
||||||
# Gradle:
|
|
||||||
.idea/**/gradle.xml
|
|
||||||
.idea/**/libraries
|
|
||||||
|
|
||||||
# CMake
|
|
||||||
cmake-build-debug/
|
|
||||||
|
|
||||||
# Mongo Explorer plugin:
|
|
||||||
.idea/**/mongoSettings.xml
|
|
||||||
|
|
||||||
## File-based project format:
|
|
||||||
*.iws
|
|
||||||
|
|
||||||
## Plugin-specific files:
|
|
||||||
|
|
||||||
# IntelliJ
|
|
||||||
out/
|
|
||||||
|
|
||||||
# mpeltonen/sbt-idea plugin
|
|
||||||
.idea_modules/
|
|
||||||
|
|
||||||
# JIRA plugin
|
|
||||||
atlassian-ide-plugin.xml
|
|
||||||
|
|
||||||
# Cursive Clojure plugin
|
|
||||||
.idea/replstate.xml
|
|
||||||
|
|
||||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
|
||||||
com_crashlytics_export_strings.xml
|
|
||||||
crashlytics.properties
|
|
||||||
crashlytics-build.properties
|
|
||||||
fabric.properties
|
|
||||||
### Dart template
|
|
||||||
# See https://www.dartlang.org/tools/private-files.html
|
|
||||||
|
|
||||||
# Files and directories created by pub
|
|
||||||
.packages
|
|
||||||
.pub/
|
|
||||||
build/
|
|
||||||
# If you're building an application, you may want to check-in your pubspec.lock
|
|
||||||
pubspec.lock
|
|
||||||
|
|
||||||
# Directory created by dartdoc
|
|
||||||
# If you don't generate documentation locally you can remove this line.
|
|
||||||
doc/api/
|
|
|
@ -1,4 +0,0 @@
|
||||||
language: dart
|
|
||||||
dart:
|
|
||||||
- dev
|
|
||||||
- stable
|
|
|
@ -1,12 +0,0 @@
|
||||||
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.
|
|
|
@ -1,30 +0,0 @@
|
||||||
# Change Log
|
|
||||||
|
|
||||||
## 2.1.2
|
|
||||||
|
|
||||||
* Final release. Replaced by `belatuk_body_parser` package.
|
|
||||||
|
|
||||||
## 2.1.1
|
|
||||||
|
|
||||||
* Fixed calling deprecated methods in unit test
|
|
||||||
|
|
||||||
## 2.1.0
|
|
||||||
|
|
||||||
* Replaced `http_server` with `belatuk_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`
|
|
|
@ -1,21 +0,0 @@
|
||||||
MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2021 dukefirehawk.com
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
|
@ -1,77 +0,0 @@
|
||||||
# Angel3 Body Parser
|
|
||||||
|
|
||||||
[![version](https://img.shields.io/badge/pub-v2.1.2-brightgreen)](https://pub.dartlang.org/packages/angel3_body_parser)
|
|
||||||
[![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety)
|
|
||||||
[![Gitter](https://img.shields.io/gitter/room/angel_dart/discussion)](https://gitter.im/angel_dart/discussion)
|
|
||||||
|
|
||||||
[![License](https://img.shields.io/github/license/dukefirehawk/angel)](https://github.com/dukefirehawk/angel/tree/angel3/packages/body_parser/LICENSE)
|
|
||||||
|
|
||||||
**DEPRECATED: Replaced by [`belatuk_body_parser`](https://pub.dartlang.org/packages/belatuk_body_parser) package**
|
|
||||||
|
|
||||||
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://github.com/dukefirehawk/angel). 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
|
|
||||||
|
|
||||||
- [Angel3 Body Parser](#angel3-body-parser)
|
|
||||||
- [Contents](#contents)
|
|
||||||
- [About](#about)
|
|
||||||
- [Installation](#installation)
|
|
||||||
- [Usage](#usage)
|
|
||||||
- [Custom Body Parsing](#custom-body-parsing)
|
|
||||||
|
|
||||||
### About
|
|
||||||
|
|
||||||
I needed something like Express.js's `body-parser` module, so I made it here. It fully supports JSON requests. x-www-form-urlencoded fully supported, as well as query strings. You can also include arrays in your query, in the same way you would for a PHP application. Full file upload support will also be present by the production 1.0.0 release.
|
|
||||||
|
|
||||||
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'`. A very semantic difference, yes, but it relieves stress in my head.
|
|
||||||
|
|
||||||
### Installation
|
|
||||||
|
|
||||||
To install Body Parser for your Dart project, simply add body_parser to your pub dependencies.
|
|
||||||
|
|
||||||
dependencies:
|
|
||||||
angel3_body_parser: ^2.1.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:angel3_body_parser/angel3_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,4 +0,0 @@
|
||||||
include: package:pedantic/analysis_options.yaml
|
|
||||||
analyzer:
|
|
||||||
strong-mode:
|
|
||||||
implicit-casts: false
|
|
|
@ -1,61 +0,0 @@
|
||||||
import 'dart:async';
|
|
||||||
import 'dart:convert';
|
|
||||||
import 'dart:io';
|
|
||||||
import 'dart:isolate';
|
|
||||||
import 'package:http_parser/http_parser.dart';
|
|
||||||
import 'package:angel3_body_parser/angel3_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);
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
-- 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"
|
|
|
@ -1,6 +0,0 @@
|
||||||
/// A library for parsing HTTP request bodies and queries.
|
|
||||||
library angel3_body_parser;
|
|
||||||
|
|
||||||
export 'src/body_parse_result.dart';
|
|
||||||
export 'src/file_upload_info.dart';
|
|
||||||
export 'src/parse_body.dart';
|
|
|
@ -1,28 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
import 'file_upload_info.dart';
|
|
||||||
|
|
||||||
List<FileUploadInfo> getFileDataFromChunk(
|
|
||||||
String chunk, String boundary, String fileUploadName, Map body) {
|
|
||||||
var result = <FileUploadInfo>[];
|
|
||||||
return result;
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
/// 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 []});
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,44 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,149 +0,0 @@
|
||||||
import 'dart:async';
|
|
||||||
import 'dart:convert';
|
|
||||||
import 'dart:io';
|
|
||||||
import 'dart:typed_data';
|
|
||||||
|
|
||||||
import 'package:http_parser/http_parser.dart';
|
|
||||||
import 'package:belatuk_http_server/belatuk_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
|
|
||||||
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
|
|
||||||
var error;
|
|
||||||
|
|
||||||
@override
|
|
||||||
StackTrace? stack;
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, dynamic>? _foldToStringDynamic(Map? map) {
|
|
||||||
return map == null
|
|
||||||
? null
|
|
||||||
: map.keys.fold<Map<String, dynamic>>(
|
|
||||||
<String, dynamic>{}, (out, k) => out..[k.toString()] = map[k]);
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
name: angel3_body_parser
|
|
||||||
version: 2.1.2
|
|
||||||
description: Parse request bodies and query strings in Dart. Supports JSON, URL-encoded, and multi-part bodies.
|
|
||||||
homepage: https://angel3-framework.web.app/
|
|
||||||
repository: https://github.com/dukefirehawk/angel/tree/angel3/packages/body_parser
|
|
||||||
environment:
|
|
||||||
sdk: '>=2.12.0 <3.0.0'
|
|
||||||
dependencies:
|
|
||||||
http_parser: ^4.0.0
|
|
||||||
belatuk_http_server: ^2.0.0
|
|
||||||
mime: ^1.0.0
|
|
||||||
dev_dependencies:
|
|
||||||
http: ^0.13.0
|
|
||||||
test: ^1.17.0
|
|
||||||
pedantic: ^1.11.0
|
|
|
@ -1,156 +0,0 @@
|
||||||
import 'dart:io';
|
|
||||||
import 'dart:convert';
|
|
||||||
import 'package:angel3_body_parser/angel3_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 == null
|
|
||||||
? null
|
|
||||||
: 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));
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,174 +0,0 @@
|
||||||
import 'dart:convert';
|
|
||||||
import 'dart:io' show HttpRequest, HttpServer;
|
|
||||||
|
|
||||||
import 'package:angel3_body_parser/angel3_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'}));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
# GraphQL
|
|
||||||
|
|
||||||
Moved to [`GraphQL Repo`](https://github.com/dukefirehawk/graphql_dart)
|
|
|
@ -1,3 +0,0 @@
|
||||||
# Angel3 Html Builder
|
|
||||||
|
|
||||||
`angel3_html_builder` package has been deprecated and replaced by `belatuk_html_builder`package at [`Belatuk Common Utilities`](<https://github.com/dart-backend/belatuk-common-utilities/tree/main/packages/html_builder>)
|
|
|
@ -1,3 +0,0 @@
|
||||||
George Moschovitis <george.moschovitis@gmail.com>
|
|
||||||
Tobe O <thosakwe@gmail.com>
|
|
||||||
Thomas Hii <thomashii@dukefirehawk.com>
|
|
|
@ -1,38 +0,0 @@
|
||||||
## 1.0.0
|
|
||||||
* Migrated to support Dart SDK 2.12.x NNBD
|
|
||||||
|
|
||||||
## 0.4.3
|
|
||||||
- Add past tense support (thanks @phodonou !)
|
|
||||||
|
|
||||||
## 0.4.2
|
|
||||||
- Dart 2 compatibility.
|
|
||||||
- Hard fork.
|
|
||||||
|
|
||||||
## 0.4.1
|
|
||||||
|
|
||||||
- Cleanup, follow latest conventions and developments in the Dartiverse.
|
|
||||||
- Added past inflections.
|
|
||||||
|
|
||||||
## 0.3.2
|
|
||||||
|
|
||||||
- Cleanup.
|
|
||||||
|
|
||||||
## 0.3.1
|
|
||||||
|
|
||||||
- Case conversion handles sentences delimited with whitespace.
|
|
||||||
|
|
||||||
## 0.3.0
|
|
||||||
|
|
||||||
- Handle irregular plural nouns.
|
|
||||||
- Added shortcut functions.
|
|
||||||
|
|
||||||
## 0.2.0
|
|
||||||
|
|
||||||
- Handle uncountable nouns.
|
|
||||||
- Added singular inflection.
|
|
||||||
- Added plural inflection.
|
|
||||||
- Refactored to use a dart:convert based API.
|
|
||||||
|
|
||||||
## 0.1.0
|
|
||||||
|
|
||||||
- Initial version, convertToSnakeCase / convertToSpinalCase.
|
|
|
@ -1,21 +0,0 @@
|
||||||
MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2021 dukefirehawk.com
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
|
@ -1,44 +0,0 @@
|
||||||
# Inflection3
|
|
||||||
[![version](https://img.shields.io/badge/pub-v1.0.0-brightgreen)](https://pub.dartlang.org/packages/inflection3)
|
|
||||||
[![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety)
|
|
||||||
|
|
||||||
In grammar, inflection or inflexion is the modification of a word to express
|
|
||||||
different grammatical categories such as tense, mood, voice, aspect, person,
|
|
||||||
number, gender and case.
|
|
||||||
|
|
||||||
A port of the Rails/ActiveSupport inflector library to Dart.
|
|
||||||
|
|
||||||
**IMPORTANT NOTE**: This is a *hard fork* of the original `inflection2` package,
|
|
||||||
as the former is now archived, and abandoned.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
A simple usage example:
|
|
||||||
|
|
||||||
```dart
|
|
||||||
import 'package:inflection3/inflection3.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
// Using 'shortcut' functions.
|
|
||||||
|
|
||||||
print(pluralize("house")); // => "houses"
|
|
||||||
print(convertToPlural("house")); // => "houses", alias for pluralize
|
|
||||||
print(pluralizeVerb("goes")); // => "go"
|
|
||||||
print(singularize("axes")); // => "axis"
|
|
||||||
print(convertToSingular("axes")); // => "axis", alias for pluralize
|
|
||||||
print(singularizeVerb("write")); // => "writes"
|
|
||||||
print(convertToSnakeCase("CamelCaseName")); // => "camel_case_name"
|
|
||||||
print(convertToSpinalCase("CamelCaseName")); // => "camel-case-name"
|
|
||||||
print(past("forgo")); // => "forwent"
|
|
||||||
|
|
||||||
// Using default encoders.
|
|
||||||
|
|
||||||
print(PLURAL.convert("virus")); // => "viri"
|
|
||||||
print(SINGULAR.convert("Matrices")); // => "Matrix"
|
|
||||||
print(SINGULAR.convert("species")); // => "species"
|
|
||||||
print(SNAKE_CASE.convert("CamelCaseName")); // => "camel_case_name"
|
|
||||||
print(SPINAL_CASE.convert("CamelCaseName")); // => "camel-case-name"
|
|
||||||
print(PAST.convert("miss")); // => "missed"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
include: package:pedantic/analysis_options.yaml
|
|
||||||
|
|
||||||
analyzer:
|
|
||||||
strong-mode:
|
|
||||||
implicit-casts: false
|
|
|
@ -1,24 +0,0 @@
|
||||||
import 'package:inflection3/inflection3.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
// Using 'shortcut' functions.
|
|
||||||
|
|
||||||
print(pluralize('house')); // => 'houses'
|
|
||||||
print(convertToPlural('house')); // => 'houses', alias for pluralize
|
|
||||||
print(pluralizeVerb('goes')); // => 'go'
|
|
||||||
print(singularize('axes')); // => 'axis'
|
|
||||||
print(convertToSingular('axes')); // => 'axis', alias for pluralize
|
|
||||||
print(singularizeVerb('write')); // => 'writes'
|
|
||||||
print(convertToSnakeCase('CamelCaseName')); // => 'camel_case_name'
|
|
||||||
print(convertToSpinalCase('CamelCaseName')); // => 'camel-case-name'
|
|
||||||
print(past('forgo')); // => 'forwent'
|
|
||||||
|
|
||||||
// Using default encoders.
|
|
||||||
|
|
||||||
print(PLURAL.convert('virus')); // => 'viri'
|
|
||||||
print(SINGULAR.convert('Matrices')); // => 'Matrix'
|
|
||||||
print(SINGULAR.convert('species')); // => 'species'
|
|
||||||
print(SNAKE_CASE.convert('CamelCaseName')); // => 'camel_case_name'
|
|
||||||
print(SPINAL_CASE.convert('CamelCaseName')); // => 'camel-case-name'
|
|
||||||
print(PAST.convert('miss')); // => 'missed'
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
/// In grammar, inflection or inflexion is the modification of a word to express
|
|
||||||
/// different grammatical categories such as tense, mood, voice, aspect, person,
|
|
||||||
/// number, gender and case.
|
|
||||||
///
|
|
||||||
/// [ActiveSupport Inflector](https://github.com/rails/rails/tree/master/activesupport/lib/active_support/inflector)
|
|
||||||
/// [Letter case](http://en.wikipedia.org/wiki/Letter_case#Special_case_styles)
|
|
||||||
library inflection3;
|
|
||||||
|
|
||||||
import 'src/past.dart';
|
|
||||||
import 'src/plural.dart';
|
|
||||||
import 'src/plural_verb.dart';
|
|
||||||
import 'src/singular.dart';
|
|
||||||
import 'src/singular_verb.dart';
|
|
||||||
import 'src/snake_case.dart';
|
|
||||||
import 'src/spinal_case.dart';
|
|
||||||
|
|
||||||
export 'src/past.dart';
|
|
||||||
export 'src/plural.dart';
|
|
||||||
export 'src/plural_verb.dart';
|
|
||||||
export 'src/singular.dart';
|
|
||||||
export 'src/singular_verb.dart';
|
|
||||||
export 'src/snake_case.dart';
|
|
||||||
export 'src/spinal_case.dart';
|
|
||||||
export 'src/is_tense.dart';
|
|
||||||
|
|
||||||
String convertToPlural(String word) => PLURAL.convert(word);
|
|
||||||
String pluralize(String word) => PLURAL.convert(word);
|
|
||||||
|
|
||||||
String convertToPluralVerb(String word) => PLURALVERB.convert(word);
|
|
||||||
String pluralizeVerb(String word) => PLURALVERB.convert(word);
|
|
||||||
|
|
||||||
String convertToSingular(String word) => SINGULAR.convert(word);
|
|
||||||
String singularize(String word) => SINGULAR.convert(word);
|
|
||||||
|
|
||||||
String convertToSingularVerb(String word) => SINGULARVERB.convert(word);
|
|
||||||
String singularizeVerb(String word) => SINGULARVERB.convert(word);
|
|
||||||
|
|
||||||
String convertToSnakeCase(String word) => SNAKE_CASE.convert(word);
|
|
||||||
|
|
||||||
String convertToSpinalCase(String word) => SPINAL_CASE.convert(word);
|
|
||||||
|
|
||||||
String convertToPast(String word) => PAST.convert(word);
|
|
||||||
String past(String word) => PAST.convert(word);
|
|
|
@ -1,323 +0,0 @@
|
||||||
//library inflection2.irregular_past_verbs;
|
|
||||||
|
|
||||||
/// A collection of verbs with irregular past.
|
|
||||||
final Map<String, String> irregularPastVerbs = const {
|
|
||||||
'backslidden': 'backslid',
|
|
||||||
'forbidden': 'forbade',
|
|
||||||
'stridden': 'strode',
|
|
||||||
'stricken': 'struck',
|
|
||||||
'brought': 'brought',
|
|
||||||
'striven': 'strove',
|
|
||||||
'swollen': 'swelled',
|
|
||||||
'thought': 'thought',
|
|
||||||
'trodden': 'trod',
|
|
||||||
'forbade': 'forbade',
|
|
||||||
'arisen': 'arose',
|
|
||||||
'awoken': 'awoke',
|
|
||||||
'beaten': 'beat',
|
|
||||||
'became': 'became',
|
|
||||||
'become': 'became',
|
|
||||||
'bidden': 'bid',
|
|
||||||
'bitten': 'bit',
|
|
||||||
'broken': 'broke',
|
|
||||||
'bought': 'bought',
|
|
||||||
'caught': 'caught',
|
|
||||||
'choose': 'chose',
|
|
||||||
'chosen': 'chose',
|
|
||||||
'forbid': 'forbade',
|
|
||||||
'dreamt': 'dreamt',
|
|
||||||
'driven': 'drove',
|
|
||||||
'fallen': 'fell',
|
|
||||||
'fought': 'fought',
|
|
||||||
'freeze': 'froze',
|
|
||||||
'frozen': 'froze',
|
|
||||||
'gotten': 'got',
|
|
||||||
'ground': 'ground',
|
|
||||||
'hidden': 'hid',
|
|
||||||
'learnt': 'learnt',
|
|
||||||
'proven': 'proved',
|
|
||||||
'sought': 'sought',
|
|
||||||
'shaken': 'shook',
|
|
||||||
'shaven': 'shaved',
|
|
||||||
'shrank': 'shrank',
|
|
||||||
'shrink': 'shrank',
|
|
||||||
'shrunk': 'shrank',
|
|
||||||
'spoken': 'spoke',
|
|
||||||
'spoilt': 'spoilt',
|
|
||||||
'spread': 'spread',
|
|
||||||
'sprang': 'sprang',
|
|
||||||
'spring': 'sprang',
|
|
||||||
'sprung': 'sprang',
|
|
||||||
'stolen': 'stole',
|
|
||||||
'strewn': 'strewed',
|
|
||||||
'strode': 'strode',
|
|
||||||
'stride': 'strode',
|
|
||||||
'strike': 'struck',
|
|
||||||
'strove': 'strove',
|
|
||||||
'struck': 'struck',
|
|
||||||
'string': 'strung',
|
|
||||||
'strung': 'strung',
|
|
||||||
'taught': 'taught',
|
|
||||||
'thrown': 'threw',
|
|
||||||
'thrust': 'thrust',
|
|
||||||
'arise': 'arose',
|
|
||||||
'arose': 'arose',
|
|
||||||
'awake': 'awoke',
|
|
||||||
'awoke': 'awoke',
|
|
||||||
'borne': 'bore',
|
|
||||||
'began': 'began',
|
|
||||||
'begin': 'began',
|
|
||||||
'begun': 'began',
|
|
||||||
'bound': 'bound',
|
|
||||||
'bleed': 'bled',
|
|
||||||
'blown': 'blew',
|
|
||||||
'break': 'broke',
|
|
||||||
'breed': 'bred',
|
|
||||||
'bring': 'brought',
|
|
||||||
'broke': 'broke',
|
|
||||||
'build': 'built',
|
|
||||||
'built': 'built',
|
|
||||||
'burnt': 'burnt',
|
|
||||||
'catch': 'caught',
|
|
||||||
'chose': 'chose',
|
|
||||||
'cling': 'clung',
|
|
||||||
'clung': 'clung',
|
|
||||||
'creep': 'crept',
|
|
||||||
'crept': 'crept',
|
|
||||||
'dealt': 'dealt',
|
|
||||||
'wound': 'wound',
|
|
||||||
'wring': 'wrung',
|
|
||||||
'wrung': 'wrung',
|
|
||||||
'write': 'wrote',
|
|
||||||
'wrote': 'wrote',
|
|
||||||
'drawn': 'drew',
|
|
||||||
'drank': 'drank',
|
|
||||||
'drink': 'drank',
|
|
||||||
'drunk': 'drank',
|
|
||||||
'drive': 'drove',
|
|
||||||
'drove': 'drove',
|
|
||||||
'dwelt': 'dwelt',
|
|
||||||
'eaten': 'ate',
|
|
||||||
'fight': 'fought',
|
|
||||||
'found': 'found',
|
|
||||||
'fling': 'flung',
|
|
||||||
'flung': 'flung',
|
|
||||||
'flown': 'flew',
|
|
||||||
'froze': 'froze',
|
|
||||||
'given': 'gave',
|
|
||||||
'grind': 'ground',
|
|
||||||
'grown': 'grew',
|
|
||||||
'heard': 'heard',
|
|
||||||
'knelt': 'knelt',
|
|
||||||
'known': 'knew',
|
|
||||||
'leant': 'leant',
|
|
||||||
'leapt': 'leapt',
|
|
||||||
'leave': 'left',
|
|
||||||
'meant': 'meant',
|
|
||||||
'risen': 'rose',
|
|
||||||
'shake': 'shook',
|
|
||||||
'shorn': 'sheared',
|
|
||||||
'shone': 'shone',
|
|
||||||
'shook': 'shook',
|
|
||||||
'shoot': 'shot',
|
|
||||||
'shown': 'showed',
|
|
||||||
'slain': 'slew',
|
|
||||||
'sleep': 'slept',
|
|
||||||
'slept': 'slept',
|
|
||||||
'slide': 'slid',
|
|
||||||
'sling': 'slung',
|
|
||||||
'slung': 'slung',
|
|
||||||
'slunk': 'slunk',
|
|
||||||
'smelt': 'smelt',
|
|
||||||
'snuck': 'snuck',
|
|
||||||
'speak': 'spoke',
|
|
||||||
'spelt': 'spelt',
|
|
||||||
'spend': 'spent',
|
|
||||||
'spent': 'spent',
|
|
||||||
'spilt': 'spilt',
|
|
||||||
'split': 'split',
|
|
||||||
'spoke': 'spoke',
|
|
||||||
'stand': 'stood',
|
|
||||||
'stood': 'stood',
|
|
||||||
'steal': 'stole',
|
|
||||||
'stick': 'stuck',
|
|
||||||
'stole': 'stole',
|
|
||||||
'stuck': 'stuck',
|
|
||||||
'sting': 'stung',
|
|
||||||
'stung': 'stung',
|
|
||||||
'stank': 'stank',
|
|
||||||
'stink': 'stank',
|
|
||||||
'stunk': 'stank',
|
|
||||||
'swear': 'swore',
|
|
||||||
'swore': 'swore',
|
|
||||||
'sworn': 'swore',
|
|
||||||
'sweep': 'swept',
|
|
||||||
'swept': 'swept',
|
|
||||||
'swing': 'swung',
|
|
||||||
'swung': 'swung',
|
|
||||||
'taken': 'took',
|
|
||||||
'teach': 'taught',
|
|
||||||
'think': 'thought',
|
|
||||||
'threw': 'threw',
|
|
||||||
'throw': 'threw',
|
|
||||||
'tread': 'trod',
|
|
||||||
'wake': 'woke',
|
|
||||||
'woken': 'woke',
|
|
||||||
'woven': 'wove',
|
|
||||||
'bear': 'bore',
|
|
||||||
'bore': 'bore',
|
|
||||||
'born': 'bore',
|
|
||||||
'beat': 'beat',
|
|
||||||
'bend': 'bent',
|
|
||||||
'bent': 'bent',
|
|
||||||
'bind': 'bound',
|
|
||||||
'bite': 'bit',
|
|
||||||
'bled': 'bled',
|
|
||||||
'blew': 'blew',
|
|
||||||
'blow': 'blew',
|
|
||||||
'bred': 'bred',
|
|
||||||
'cast': 'cast',
|
|
||||||
'clad': 'clad',
|
|
||||||
'come': 'came',
|
|
||||||
'cost': 'cost',
|
|
||||||
'deal': 'dealt',
|
|
||||||
'does': 'did',
|
|
||||||
'done': 'did',
|
|
||||||
'draw': 'drew',
|
|
||||||
'drew': 'drew',
|
|
||||||
'fall': 'fell',
|
|
||||||
'feed': 'fed',
|
|
||||||
'feel': 'felt',
|
|
||||||
'fell': 'fell',
|
|
||||||
'felt': 'felt',
|
|
||||||
'find': 'found',
|
|
||||||
'flee': 'fled',
|
|
||||||
'fled': 'fled',
|
|
||||||
'flew': 'flew',
|
|
||||||
'gave': 'gave',
|
|
||||||
'give': 'gave',
|
|
||||||
'gone': 'went',
|
|
||||||
'grew': 'grew',
|
|
||||||
'grow': 'grew',
|
|
||||||
'hang': 'hung',
|
|
||||||
'hung': 'hung',
|
|
||||||
'have': 'had',
|
|
||||||
'hear': 'heard',
|
|
||||||
'hewn': 'hewed',
|
|
||||||
'hide': 'hid',
|
|
||||||
'hold': 'held',
|
|
||||||
'held': 'held',
|
|
||||||
'hurt': 'hurt',
|
|
||||||
'keep': 'kept',
|
|
||||||
'kept': 'kept',
|
|
||||||
'knew': 'knew',
|
|
||||||
'know': 'knew',
|
|
||||||
'laid': 'laid',
|
|
||||||
'lead': 'led',
|
|
||||||
'left': 'left',
|
|
||||||
'lend': 'lent',
|
|
||||||
'lent': 'lent',
|
|
||||||
'lain': 'lay',
|
|
||||||
'lose': 'lost',
|
|
||||||
'lost': 'lost',
|
|
||||||
'make': 'made',
|
|
||||||
'made': 'made',
|
|
||||||
'mean': 'meant',
|
|
||||||
'meet': 'met',
|
|
||||||
'mown': 'mowed',
|
|
||||||
'paid': 'paid',
|
|
||||||
'pled': 'pled',
|
|
||||||
'read': 'read',
|
|
||||||
'ride': 'rode',
|
|
||||||
'rode': 'rode',
|
|
||||||
'ring': 'rang',
|
|
||||||
'rung': 'rang',
|
|
||||||
'rise': 'rose',
|
|
||||||
'rose': 'rose',
|
|
||||||
'sang': 'sang',
|
|
||||||
'sawn': 'sawed',
|
|
||||||
'said': 'said',
|
|
||||||
'seen': 'saw',
|
|
||||||
'seek': 'sought',
|
|
||||||
'sell': 'sold',
|
|
||||||
'slew': 'slew',
|
|
||||||
'sold': 'sold',
|
|
||||||
'send': 'sent',
|
|
||||||
'sent': 'sent',
|
|
||||||
'sewn': 'sewed',
|
|
||||||
'shed': 'shed',
|
|
||||||
'shot': 'shot',
|
|
||||||
'shut': 'shut',
|
|
||||||
'sing': 'sang',
|
|
||||||
'sung': 'sang',
|
|
||||||
'slid': 'slid',
|
|
||||||
'slit': 'slit',
|
|
||||||
'sown': 'sowed',
|
|
||||||
'sped': 'sped',
|
|
||||||
'spin': 'spun',
|
|
||||||
'spun': 'spun',
|
|
||||||
'spit': 'spit',
|
|
||||||
'spat': 'spat',
|
|
||||||
'swam': 'swam',
|
|
||||||
'swim': 'swam',
|
|
||||||
'swum': 'swam',
|
|
||||||
'take': 'took',
|
|
||||||
'tear': 'tore',
|
|
||||||
'tore': 'tore',
|
|
||||||
'torn': 'tore',
|
|
||||||
'tell': 'told',
|
|
||||||
'told': 'told',
|
|
||||||
'took': 'took',
|
|
||||||
'trod': 'trod',
|
|
||||||
'wear': 'wore',
|
|
||||||
'wore': 'wore',
|
|
||||||
'worn': 'wore',
|
|
||||||
'weep': 'wept',
|
|
||||||
'went': 'went',
|
|
||||||
'wept': 'wept',
|
|
||||||
'were': 'were',
|
|
||||||
'wind': 'wound',
|
|
||||||
'woke': 'woke',
|
|
||||||
'wove': 'wove',
|
|
||||||
'are': 'were',
|
|
||||||
'ate': 'ate',
|
|
||||||
'bet': 'bet',
|
|
||||||
'bid': 'bid',
|
|
||||||
'bit': 'bit',
|
|
||||||
'buy': 'bought',
|
|
||||||
'cut': 'cut',
|
|
||||||
'did': 'did',
|
|
||||||
'dig': 'dug',
|
|
||||||
'dug': 'dug',
|
|
||||||
'eat': 'ate',
|
|
||||||
'fed': 'fed',
|
|
||||||
'fly': 'flew',
|
|
||||||
'get': 'got',
|
|
||||||
'got': 'got',
|
|
||||||
'had': 'had',
|
|
||||||
'has': 'had',
|
|
||||||
'hid': 'hid',
|
|
||||||
'hit': 'hit',
|
|
||||||
'lay': 'laid',
|
|
||||||
'led': 'led',
|
|
||||||
'let': 'let',
|
|
||||||
'lit': 'lit',
|
|
||||||
'met': 'met',
|
|
||||||
'pay': 'paid',
|
|
||||||
'put': 'put',
|
|
||||||
'ran': 'ran',
|
|
||||||
'rid': 'rid',
|
|
||||||
'run': 'ran',
|
|
||||||
'saw': 'saw',
|
|
||||||
'say': 'said',
|
|
||||||
'see': 'saw',
|
|
||||||
'sit': 'sat',
|
|
||||||
'sat': 'sat',
|
|
||||||
'set': 'set',
|
|
||||||
'was': 'was',
|
|
||||||
'win': 'won',
|
|
||||||
'won': 'won',
|
|
||||||
'do': 'did',
|
|
||||||
'go': 'went',
|
|
||||||
'is': 'was',
|
|
||||||
};
|
|
|
@ -1,11 +0,0 @@
|
||||||
//library inflection2.irregular_plural_nouns;
|
|
||||||
|
|
||||||
/// A collection of nouns with irregular plurals.
|
|
||||||
///
|
|
||||||
/// [A List of 100 Irregular Plural Nouns in English](http://grammar.about.com/od/words/a/A-List-Of-Irregular-Plural-Nouns-In-English.htm)
|
|
||||||
final Map<String, String> irregularPluralNouns = const {
|
|
||||||
'person': 'people',
|
|
||||||
'man': 'men',
|
|
||||||
'child': 'children',
|
|
||||||
'sex': 'sexes'
|
|
||||||
};
|
|
|
@ -1,9 +0,0 @@
|
||||||
//library inflection2.irregular_plural_verbs;
|
|
||||||
|
|
||||||
/// A collection of verbs with irregular plurals.
|
|
||||||
final Map<String, String> irregularPluralVerbs = const {
|
|
||||||
'is': 'are',
|
|
||||||
'am': 'are',
|
|
||||||
'was': 'were',
|
|
||||||
'has': 'have'
|
|
||||||
};
|
|
|
@ -1,6 +0,0 @@
|
||||||
import '../inflection3.dart';
|
|
||||||
|
|
||||||
/// returns true if this word is in the past tense
|
|
||||||
bool isPastTense(String word) {
|
|
||||||
return word.toLowerCase().trim() == past(word).toLowerCase().trim();
|
|
||||||
}
|
|
|
@ -1,69 +0,0 @@
|
||||||
//library inflection2.past;
|
|
||||||
|
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'irregular_past_verbs.dart';
|
|
||||||
import 'verbs_ending_with_ed.dart';
|
|
||||||
import 'util.dart';
|
|
||||||
|
|
||||||
class PastEncoder extends Converter<String, String> {
|
|
||||||
final List<List> _inflectionRules = [];
|
|
||||||
|
|
||||||
PastEncoder() {
|
|
||||||
irregularPastVerbs.forEach((String presentOrParticiple, String past) {
|
|
||||||
addIrregularInflectionRule(presentOrParticiple, past);
|
|
||||||
});
|
|
||||||
[
|
|
||||||
[r'.+', (Match m) => '${m[0]}ed'],
|
|
||||||
[r'([^aeiou])y$', (Match m) => '${m[1]}ied'],
|
|
||||||
[r'([aeiou]e)$', (Match m) => '${m[1]}d'],
|
|
||||||
[r'[aeiou][^aeiou]e$', (Match m) => '${m[0]}d']
|
|
||||||
]
|
|
||||||
.reversed
|
|
||||||
.forEach((rule) => addInflectionRule(rule.first as String, rule.last));
|
|
||||||
}
|
|
||||||
|
|
||||||
void addInflectionRule(String presentOrParticiple, dynamic past) {
|
|
||||||
_inflectionRules
|
|
||||||
.add([RegExp(presentOrParticiple, caseSensitive: false), past]);
|
|
||||||
}
|
|
||||||
|
|
||||||
void addIrregularInflectionRule(String presentOrParticiple, String past) {
|
|
||||||
_inflectionRules.add([
|
|
||||||
RegExp(
|
|
||||||
r'^(back|dis|for|fore|in|inter|mis|off|over|out|par|pre|re|type|un|under|up)?' +
|
|
||||||
presentOrParticiple +
|
|
||||||
r'$',
|
|
||||||
caseSensitive: false),
|
|
||||||
(Match m) => (m[1] == null) ? past : m[1]! + past
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String convert(String word) {
|
|
||||||
if (word.isNotEmpty) {
|
|
||||||
if (word.contains('ed', word.length - 2)) {
|
|
||||||
var reg = RegExp(
|
|
||||||
r'^(back|dis|for|fore|in|inter|mis|off|over|out|par|pre|re|type|un|under|up)(.+)$');
|
|
||||||
if (reg.hasMatch(word)) {
|
|
||||||
if (!verbsEndingWithEd.contains(reg.firstMatch(word)!.group(2))) {
|
|
||||||
return word;
|
|
||||||
}
|
|
||||||
} else if (!verbsEndingWithEd.contains(word)) {
|
|
||||||
return word;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var r in _inflectionRules) {
|
|
||||||
var pattern = r.first as RegExp;
|
|
||||||
if (pattern.hasMatch(word)) {
|
|
||||||
return word.replaceAllMapped(pattern, r.last as MatchToString);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return word;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final Converter<String, String> PAST = PastEncoder();
|
|
|
@ -1,87 +0,0 @@
|
||||||
//library inflection2.plural;
|
|
||||||
|
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'uncountable_nouns.dart';
|
|
||||||
import 'irregular_plural_nouns.dart';
|
|
||||||
import 'util.dart';
|
|
||||||
|
|
||||||
class PluralEncoder extends Converter<String, String> {
|
|
||||||
final List<List> _inflectionRules = [];
|
|
||||||
|
|
||||||
PluralEncoder() {
|
|
||||||
irregularPluralNouns.forEach((singular, plural) {
|
|
||||||
addIrregularInflectionRule(singular, plural);
|
|
||||||
});
|
|
||||||
|
|
||||||
[
|
|
||||||
[r'$', (Match m) => 's'],
|
|
||||||
[r's$', (Match m) => 's'],
|
|
||||||
[r'^(ax|test)is$', (Match m) => '${m[1]}es'],
|
|
||||||
[r'(octop|vir)us$', (Match m) => '${m[1]}i'],
|
|
||||||
[r'(octop|vir)i$', (Match m) => m[0]],
|
|
||||||
[r'(alias|status)$', (Match m) => '${m[1]}es'],
|
|
||||||
[r'(bu)s$', (Match m) => '${m[1]}ses'],
|
|
||||||
[r'(buffal|tomat)o$', (Match m) => '${m[1]}oes'],
|
|
||||||
[r'([ti])um$', (Match m) => '${m[1]}a'],
|
|
||||||
[r'([ti])a$', (Match m) => m[0]],
|
|
||||||
[r'sis$', (Match m) => 'ses'],
|
|
||||||
[r'(?:([^f])fe|([lr])f)$', (Match m) => '${m[1]}${m[2]}ves'],
|
|
||||||
[r'([^aeiouy]|qu)y$', (Match m) => '${m[1]}ies'],
|
|
||||||
[r'(x|ch|ss|sh)$', (Match m) => '${m[1]}es'],
|
|
||||||
[r'(matr|vert|ind)(?:ix|ex)$', (Match m) => '${m[1]}ices'],
|
|
||||||
[r'^(m|l)ouse$', (Match m) => '${m[1]}ice'],
|
|
||||||
[r'^(m|l)ice$', (Match m) => m[0]],
|
|
||||||
[r'^(ox)$', (Match m) => '${m[1]}en'],
|
|
||||||
[r'^(oxen)$', (Match m) => m[1]],
|
|
||||||
[r'(quiz)$', (Match m) => '${m[1]}zes']
|
|
||||||
]
|
|
||||||
.reversed
|
|
||||||
.forEach((rule) => addInflectionRule(rule.first as String, rule.last));
|
|
||||||
}
|
|
||||||
|
|
||||||
void addInflectionRule(String singular, dynamic plural) {
|
|
||||||
_inflectionRules.add([RegExp(singular, caseSensitive: false), plural]);
|
|
||||||
}
|
|
||||||
|
|
||||||
void addIrregularInflectionRule(String singular, String plural) {
|
|
||||||
final s0 = singular.substring(0, 1);
|
|
||||||
final srest = singular.substring(1);
|
|
||||||
final p0 = plural.substring(0, 1);
|
|
||||||
final prest = plural.substring(1);
|
|
||||||
|
|
||||||
if (s0.toUpperCase() == p0.toUpperCase()) {
|
|
||||||
addInflectionRule('($s0)$srest\$', (Match m) => '${m[1]}$prest');
|
|
||||||
addInflectionRule('($p0)$prest\$', (Match m) => '${m[1]}$prest');
|
|
||||||
} else {
|
|
||||||
addInflectionRule('${s0.toUpperCase()}(?i)$srest\$',
|
|
||||||
(Match m) => '${p0.toUpperCase()}$prest');
|
|
||||||
addInflectionRule('${s0.toLowerCase()}(?i)$srest\$',
|
|
||||||
(Match m) => '${p0.toUpperCase()}$prest');
|
|
||||||
addInflectionRule('${p0.toUpperCase()}(?i)$prest\$',
|
|
||||||
(Match m) => '${p0.toUpperCase()}$prest');
|
|
||||||
addInflectionRule('${p0.toLowerCase()}(?i)$prest\$',
|
|
||||||
(Match m) => '${p0.toLowerCase()}$prest');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String convert(String word) {
|
|
||||||
if (word.isNotEmpty) {
|
|
||||||
if (uncountableNouns.contains(word.toLowerCase())) {
|
|
||||||
return word;
|
|
||||||
} else {
|
|
||||||
for (var r in _inflectionRules) {
|
|
||||||
var pattern = r.first as RegExp;
|
|
||||||
if (pattern.hasMatch(word)) {
|
|
||||||
return word.replaceAllMapped(pattern, r.last as MatchToString);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return word;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final Converter<String, String> PLURAL = PluralEncoder();
|
|
|
@ -1,49 +0,0 @@
|
||||||
//library inflection2.plural_verb;
|
|
||||||
|
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'irregular_plural_verbs.dart';
|
|
||||||
import 'util.dart';
|
|
||||||
|
|
||||||
class PluralVerbEncoder extends Converter<String, String> {
|
|
||||||
final List<List> _inflectionRules = [];
|
|
||||||
|
|
||||||
PluralVerbEncoder() {
|
|
||||||
irregularPluralVerbs.forEach((singular, plural) {
|
|
||||||
addInflectionRule(singular, (Match m) => plural);
|
|
||||||
});
|
|
||||||
|
|
||||||
[
|
|
||||||
[r'e?s$', (Match m) => ''],
|
|
||||||
[r'ies$', (Match m) => 'y'],
|
|
||||||
[r'([^h|z|o|i])es$', (Match m) => '${m[1]}e'],
|
|
||||||
[r'ses$', (Match m) => 's'],
|
|
||||||
[r'zzes$', (Match m) => 'zz'],
|
|
||||||
[r'([cs])hes$', (Match m) => '${m[1]}h'],
|
|
||||||
[r'xes$', (Match m) => 'x'],
|
|
||||||
[r'sses$', (Match m) => 'ss']
|
|
||||||
]
|
|
||||||
.reversed
|
|
||||||
.forEach((rule) => addInflectionRule(rule.first as String, rule.last));
|
|
||||||
}
|
|
||||||
|
|
||||||
void addInflectionRule(String singular, dynamic plural) {
|
|
||||||
_inflectionRules.add([RegExp(singular, caseSensitive: false), plural]);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String convert(String word) {
|
|
||||||
if (word.isNotEmpty) {
|
|
||||||
for (var r in _inflectionRules) {
|
|
||||||
var pattern = r.first as RegExp;
|
|
||||||
if (pattern.hasMatch(word)) {
|
|
||||||
return word.replaceAllMapped(pattern, r.last as MatchToString);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return word;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final Converter<String, String> PLURALVERB = PluralVerbEncoder();
|
|
|
@ -1,95 +0,0 @@
|
||||||
//library inflection2.singular;
|
|
||||||
|
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'uncountable_nouns.dart';
|
|
||||||
import 'irregular_plural_nouns.dart';
|
|
||||||
import 'util.dart';
|
|
||||||
|
|
||||||
class SingularEncoder extends Converter<String, String> {
|
|
||||||
final List<List> _inflectionRules = [];
|
|
||||||
|
|
||||||
SingularEncoder() {
|
|
||||||
irregularPluralNouns.forEach((singular, plural) {
|
|
||||||
addIrregularInflectionRule(singular, plural);
|
|
||||||
});
|
|
||||||
|
|
||||||
[
|
|
||||||
[r's$', (Match m) => ''],
|
|
||||||
[r'(ss)$', (Match m) => m[1]],
|
|
||||||
[r'(n)ews$', (Match m) => '${m[1]}ews'], // TODO: uncountable?
|
|
||||||
[r'([ti])a$', (Match m) => '${m[1]}um'],
|
|
||||||
[
|
|
||||||
r'((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(sis|ses)$',
|
|
||||||
(Match m) => '${m[1]}sis'
|
|
||||||
],
|
|
||||||
[r'(^analy)(sis|ses)$', (Match m) => '${m[1]}sis'], // TODO: not needed?
|
|
||||||
[r'([^f])ves$', (Match m) => '${m[1]}fe'],
|
|
||||||
[r'(hive|tive)s$', (Match m) => m[1]],
|
|
||||||
[r'([lr])ves$', (Match m) => '${m[1]}f'],
|
|
||||||
[r'([^aeiouy]|qu)ies$', (Match m) => '${m[1]}y'],
|
|
||||||
[r'(s)eries$', (Match m) => '${m[1]}eries'], // TODO: uncountable
|
|
||||||
[r'(m)ovies$', (Match m) => '${m[1]}ovie'],
|
|
||||||
[r'(x|ch|ss|sh)es$', (Match m) => m[1]],
|
|
||||||
[r'^(m|l)ice$', (Match m) => '${m[1]}ouse'],
|
|
||||||
[r'(bus)(es)?$', (Match m) => m[1]],
|
|
||||||
[r'(shoe)s$', (Match m) => m[1]],
|
|
||||||
[r'(cris|test)(is|es)$', (Match m) => '${m[1]}is'],
|
|
||||||
[r'^(a)x[ie]s$', (Match m) => '${m[1]}xis'],
|
|
||||||
[r'(octop|vir)(us|i)$', (Match m) => '${m[1]}us'],
|
|
||||||
[r'(alias|status)(es)?$', (Match m) => m[1]],
|
|
||||||
[r'^(ox)en', (Match m) => m[1]],
|
|
||||||
[r'(vert|ind)ices$', (Match m) => '${m[1]}ex'],
|
|
||||||
[r'(matr)ices$', (Match m) => '${m[1]}ix'],
|
|
||||||
[r'(quiz)zes$', (Match m) => m[1]],
|
|
||||||
[r'(database)s$', (Match m) => m[1]]
|
|
||||||
]
|
|
||||||
.reversed
|
|
||||||
.forEach((rule) => addInflectionRule(rule.first as String, rule.last));
|
|
||||||
}
|
|
||||||
|
|
||||||
void addInflectionRule(String plural, dynamic singular) {
|
|
||||||
_inflectionRules.add([RegExp(plural, caseSensitive: false), singular]);
|
|
||||||
}
|
|
||||||
|
|
||||||
void addIrregularInflectionRule(String singular, String plural) {
|
|
||||||
final s0 = singular.substring(0, 1);
|
|
||||||
final srest = singular.substring(1);
|
|
||||||
final p0 = plural.substring(0, 1);
|
|
||||||
final prest = plural.substring(1);
|
|
||||||
|
|
||||||
if (s0.toUpperCase() == p0.toUpperCase()) {
|
|
||||||
addInflectionRule('($s0)$srest\$', (Match m) => '${m[1]}$srest');
|
|
||||||
addInflectionRule('($p0)$prest\$', (Match m) => '${m[1]}$srest');
|
|
||||||
} else {
|
|
||||||
addInflectionRule('${s0.toUpperCase()}(?i)$srest\$',
|
|
||||||
(Match m) => '${s0.toUpperCase()}$srest');
|
|
||||||
addInflectionRule('${s0.toLowerCase()}(?i)$srest\$',
|
|
||||||
(Match m) => '${s0.toUpperCase()}$srest');
|
|
||||||
addInflectionRule('${p0.toUpperCase()}(?i)$prest\$',
|
|
||||||
(Match m) => '${s0.toUpperCase()}$srest');
|
|
||||||
addInflectionRule('${p0.toLowerCase()}(?i)$prest\$',
|
|
||||||
(Match m) => '${s0.toLowerCase()}$srest');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String convert(String word) {
|
|
||||||
if (word.isNotEmpty) {
|
|
||||||
if (uncountableNouns.contains(word.toLowerCase())) {
|
|
||||||
return word;
|
|
||||||
} else {
|
|
||||||
for (var r in _inflectionRules) {
|
|
||||||
var pattern = r.first as RegExp;
|
|
||||||
if (pattern.hasMatch(word)) {
|
|
||||||
return word.replaceAllMapped(pattern, r.last as MatchToString);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return word;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final Converter<String, String> SINGULAR = SingularEncoder();
|
|
|
@ -1,46 +0,0 @@
|
||||||
//library inflection2.singular_verb;
|
|
||||||
|
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'irregular_plural_verbs.dart';
|
|
||||||
import 'util.dart';
|
|
||||||
|
|
||||||
class SingularVerbEncoder extends Converter<String, String> {
|
|
||||||
final List<List> _inflectionRules = [];
|
|
||||||
|
|
||||||
SingularVerbEncoder() {
|
|
||||||
irregularPluralVerbs.forEach((singular, plural) {
|
|
||||||
addInflectionRule(plural, (Match m) => singular);
|
|
||||||
});
|
|
||||||
|
|
||||||
[
|
|
||||||
[r'$', (Match m) => 's'],
|
|
||||||
[r'([^aeiou])y$', (Match m) => '${m[1]}ies'],
|
|
||||||
[r'(z)$', (Match m) => '${m[1]}es'],
|
|
||||||
[r'(ss|zz|x|h|o|us)$', (Match m) => '${m[1]}es'],
|
|
||||||
[r'(ed)$', (Match m) => '${m[1]}']
|
|
||||||
]
|
|
||||||
.reversed
|
|
||||||
.forEach((rule) => addInflectionRule(rule.first as String, rule.last));
|
|
||||||
}
|
|
||||||
|
|
||||||
void addInflectionRule(String singular, dynamic plural) {
|
|
||||||
_inflectionRules.add([RegExp(singular, caseSensitive: false), plural]);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String convert(String word) {
|
|
||||||
if (word.isNotEmpty) {
|
|
||||||
for (var r in _inflectionRules) {
|
|
||||||
var pattern = r.first as RegExp;
|
|
||||||
if (pattern.hasMatch(word)) {
|
|
||||||
return word.replaceAllMapped(pattern, r.last as MatchToString);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return word;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final Converter<String, String> SINGULARVERB = SingularVerbEncoder();
|
|
|
@ -1,24 +0,0 @@
|
||||||
//library inflection2.snake_case;
|
|
||||||
|
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
final _underscoreRE0 = RegExp(r'''([A-Z\d]+)([A-Z][a-z])''');
|
|
||||||
final _underscoreRE1 = RegExp(r'''([a-z\d])([A-Z])''');
|
|
||||||
final _underscoreRE2 = RegExp(r'[-\s]');
|
|
||||||
|
|
||||||
class SnakeCaseEncoder extends Converter<String, String> {
|
|
||||||
const SnakeCaseEncoder();
|
|
||||||
|
|
||||||
/// Converts the input [phrase] to 'spinal case', i.e. a hyphen-delimited,
|
|
||||||
/// lowercase form. Also known as 'kebab case' or 'lisp case'.
|
|
||||||
@override
|
|
||||||
String convert(String phrase) {
|
|
||||||
return phrase
|
|
||||||
.replaceAllMapped(_underscoreRE0, (match) => '${match[1]}_${match[2]}')
|
|
||||||
.replaceAllMapped(_underscoreRE1, (match) => '${match[1]}_${match[2]}')
|
|
||||||
.replaceAll(_underscoreRE2, '_')
|
|
||||||
.toLowerCase();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const Converter<String, String> SNAKE_CASE = SnakeCaseEncoder();
|
|
|
@ -1,24 +0,0 @@
|
||||||
//library inflection2.spinal_case;
|
|
||||||
|
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
final _underscoreRE0 = RegExp(r'''([A-Z\d]+)([A-Z][a-z])''');
|
|
||||||
final _underscoreRE1 = RegExp(r'''([a-z\d])([A-Z])''');
|
|
||||||
final _underscoreRE2 = RegExp(r'[_\s]');
|
|
||||||
|
|
||||||
class SpinalCaseEncoder extends Converter<String, String> {
|
|
||||||
const SpinalCaseEncoder();
|
|
||||||
|
|
||||||
/// Converts the input [phrase] to 'spinal case', i.e. a hyphen-delimited,
|
|
||||||
/// lowercase form. Also known as 'kebab case' or 'lisp case'.
|
|
||||||
@override
|
|
||||||
String convert(String phrase) {
|
|
||||||
return phrase
|
|
||||||
.replaceAllMapped(_underscoreRE0, (match) => '${match[1]}-${match[2]}')
|
|
||||||
.replaceAllMapped(_underscoreRE1, (match) => '${match[1]}-${match[2]}')
|
|
||||||
.replaceAll(_underscoreRE2, '-')
|
|
||||||
.toLowerCase();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const Converter<String, String> SPINAL_CASE = SpinalCaseEncoder();
|
|
|
@ -1,16 +0,0 @@
|
||||||
//library inflection2.uncountable;
|
|
||||||
|
|
||||||
/// Uncountable nouns are substances, concepts etc that we cannot divide into
|
|
||||||
/// separate elements. We cannot 'count' them.
|
|
||||||
final Set<String> uncountableNouns = Set.from(const [
|
|
||||||
'equipment',
|
|
||||||
'information',
|
|
||||||
'rice',
|
|
||||||
'money',
|
|
||||||
'species',
|
|
||||||
'series',
|
|
||||||
'fish',
|
|
||||||
'sheep',
|
|
||||||
'jeans',
|
|
||||||
'police'
|
|
||||||
]);
|
|
|
@ -1 +0,0 @@
|
||||||
typedef MatchToString = String Function(Match m);
|
|
|
@ -1,21 +0,0 @@
|
||||||
//library inflection2.verbs_ending_with_ed;
|
|
||||||
|
|
||||||
/// A collection of verbs ending with -ed.
|
|
||||||
final List<String> verbsEndingWithEd = const [
|
|
||||||
'bed',
|
|
||||||
'bleed',
|
|
||||||
'breed',
|
|
||||||
'embed',
|
|
||||||
'exceed',
|
|
||||||
'feed',
|
|
||||||
'heed',
|
|
||||||
'need',
|
|
||||||
'proceed',
|
|
||||||
'seed',
|
|
||||||
'shred',
|
|
||||||
'speed',
|
|
||||||
'succeed',
|
|
||||||
'ted',
|
|
||||||
'wed',
|
|
||||||
'weed'
|
|
||||||
];
|
|
|
@ -1,9 +0,0 @@
|
||||||
name: inflection3
|
|
||||||
version: 1.0.0
|
|
||||||
description: Grammatical Inflection encoders.
|
|
||||||
homepage: https://github.com/dukefirehawk/angel/tree/angel3/packages/inflection3
|
|
||||||
environment:
|
|
||||||
sdk: '>=2.12.0 <3.0.0'
|
|
||||||
dev_dependencies:
|
|
||||||
test: ^1.17.4
|
|
||||||
pedantic: ^1.11.0
|
|
|
@ -1,21 +0,0 @@
|
||||||
import 'inflection_test.dart' as inflection_test;
|
|
||||||
import 'past_test.dart' as past_test;
|
|
||||||
import 'plural_test.dart' as plural_test;
|
|
||||||
import 'plural_verb_test.dart' as plural_verb_test;
|
|
||||||
import 'singular_test.dart' as singular_test;
|
|
||||||
import 'singular_verb_test.dart' as singular_verb_test;
|
|
||||||
import 'snake_case_test.dart' as snake_case_test;
|
|
||||||
import 'spinal_case_test.dart' as spinal_case_test;
|
|
||||||
import 'is_tense_test.dart' as is_tense_test;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
inflection_test.main();
|
|
||||||
past_test.main();
|
|
||||||
plural_test.main();
|
|
||||||
plural_verb_test.main();
|
|
||||||
singular_test.main();
|
|
||||||
singular_verb_test.main();
|
|
||||||
snake_case_test.main();
|
|
||||||
spinal_case_test.main();
|
|
||||||
is_tense_test.main();
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
library inflection3.test;
|
|
||||||
|
|
||||||
import 'package:test/test.dart';
|
|
||||||
import 'package:inflection3/inflection3.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
group('The inflection library', () {
|
|
||||||
test('provides a few convenient helper functions', () {
|
|
||||||
expect(pluralize('axis'), equals('axes'));
|
|
||||||
expect(convertToPlural('axis'), equals('axes'));
|
|
||||||
expect(singularize('Houses'), equals('House'));
|
|
||||||
expect(convertToSingular('Houses'), equals('House'));
|
|
||||||
expect(convertToSnakeCase('CamelCase'), equals('camel_case'));
|
|
||||||
expect(convertToSpinalCase('CamelCase'), equals('camel-case'));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
library inflection3.is_tense.test;
|
|
||||||
|
|
||||||
import 'package:test/test.dart';
|
|
||||||
import 'package:inflection3/inflection3.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
group('isTense', () {
|
|
||||||
test('correctly identifies if a word is in past tense', () {
|
|
||||||
expect(isPastTense('run'), false);
|
|
||||||
expect(isPastTense('ran'), true);
|
|
||||||
expect(isPastTense('PusHed'), true);
|
|
||||||
expect(isPastTense('PusH'), false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,45 +0,0 @@
|
||||||
library inflection3.past.test;
|
|
||||||
|
|
||||||
import 'package:inflection3/inflection3.dart';
|
|
||||||
import 'package:inflection3/src/irregular_past_verbs.dart';
|
|
||||||
import 'package:test/test.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
group('The PastEncoder', () {
|
|
||||||
test('converts verbs from present or participle to past', () {
|
|
||||||
expect(PAST.convert(''), equals(''));
|
|
||||||
expect(PAST.convert('ask'), equals('asked'));
|
|
||||||
expect(PAST.convert('close'), equals('closed'));
|
|
||||||
expect(PAST.convert('die'), equals('died'));
|
|
||||||
expect(PAST.convert('phone'), equals('phoned'));
|
|
||||||
expect(PAST.convert('play'), equals('played'));
|
|
||||||
expect(PAST.convert('destroy'), equals('destroyed'));
|
|
||||||
expect(PAST.convert('show'), equals('showed'));
|
|
||||||
expect(PAST.convert('marry'), equals('married'));
|
|
||||||
expect(PAST.convert('study'), equals('studied'));
|
|
||||||
expect(PAST.convert('visit'), equals('visited'));
|
|
||||||
expect(PAST.convert('miss'), equals('missed'));
|
|
||||||
expect(PAST.convert('watch'), equals('watched'));
|
|
||||||
expect(PAST.convert('finish'), equals('finished'));
|
|
||||||
expect(PAST.convert('fix'), equals('fixed'));
|
|
||||||
expect(PAST.convert('buzz'), equals('buzzed'));
|
|
||||||
expect(PAST.convert('asked'), equals('asked'));
|
|
||||||
expect(PAST.convert('closed'), equals('closed'));
|
|
||||||
expect(PAST.convert('reopened'), equals('reopened'));
|
|
||||||
expect(PAST.convert('unseed'), equals('unseeded'));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('handles irregular past verbs', () {
|
|
||||||
irregularPastVerbs.forEach((String presentOrParticiple, String past) {
|
|
||||||
expect(PAST.convert(presentOrParticiple), equals(past));
|
|
||||||
});
|
|
||||||
expect(PAST.convert('forgo'), equals('forwent'));
|
|
||||||
expect(PAST.convert('undo'), equals('undid'));
|
|
||||||
expect(PAST.convert('outsell'), equals('outsold'));
|
|
||||||
expect(PAST.convert('rebreed'), equals('rebred'));
|
|
||||||
expect(PAST.convert('arose'), equals('arose'));
|
|
||||||
expect(PAST.convert('backslid'), equals('backslid'));
|
|
||||||
expect(PAST.convert('forbade'), equals('forbade'));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,65 +0,0 @@
|
||||||
library inflection3.plural.test;
|
|
||||||
|
|
||||||
import 'package:inflection3/inflection3.dart';
|
|
||||||
import 'package:inflection3/src/uncountable_nouns.dart';
|
|
||||||
import 'package:test/test.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
group('The PluralEncoder', () {
|
|
||||||
test('converts nouns from singular to plural', () {
|
|
||||||
expect(PLURAL.convert(''), equals(''));
|
|
||||||
expect(PLURAL.convert('House'), equals('Houses'));
|
|
||||||
expect(PLURAL.convert('house'), equals('houses'));
|
|
||||||
expect(PLURAL.convert('dog'), equals('dogs'));
|
|
||||||
expect(PLURAL.convert('axis'), equals('axes'));
|
|
||||||
expect(PLURAL.convert('testis'), equals('testes'));
|
|
||||||
expect(PLURAL.convert('octopus'), equals('octopi'));
|
|
||||||
expect(PLURAL.convert('virus'), equals('viri'));
|
|
||||||
expect(PLURAL.convert('octopi'), equals('octopi'));
|
|
||||||
expect(PLURAL.convert('viri'), equals('viri'));
|
|
||||||
expect(PLURAL.convert('alias'), equals('aliases'));
|
|
||||||
expect(PLURAL.convert('status'), equals('statuses'));
|
|
||||||
expect(PLURAL.convert('bus'), equals('buses'));
|
|
||||||
expect(PLURAL.convert('buffalo'), equals('buffaloes'));
|
|
||||||
expect(PLURAL.convert('tomato'), equals('tomatoes'));
|
|
||||||
expect(PLURAL.convert('ultimatum'), equals('ultimata'));
|
|
||||||
expect(PLURAL.convert('pentium'), equals('pentia'));
|
|
||||||
expect(PLURAL.convert('ultimata'), equals('ultimata'));
|
|
||||||
expect(PLURAL.convert('pentia'), equals('pentia'));
|
|
||||||
expect(PLURAL.convert('nemesis'), equals('nemeses'));
|
|
||||||
expect(PLURAL.convert('hive'), equals('hives'));
|
|
||||||
expect(PLURAL.convert('fly'), equals('flies'));
|
|
||||||
expect(PLURAL.convert('dish'), equals('dishes'));
|
|
||||||
expect(PLURAL.convert('bench'), equals('benches'));
|
|
||||||
expect(PLURAL.convert('matrix'), equals('matrices'));
|
|
||||||
expect(PLURAL.convert('vertex'), equals('vertices'));
|
|
||||||
expect(PLURAL.convert('index'), equals('indices'));
|
|
||||||
expect(PLURAL.convert('mouse'), equals('mice'));
|
|
||||||
expect(PLURAL.convert('louse'), equals('lice'));
|
|
||||||
expect(PLURAL.convert('mice'), equals('mice'));
|
|
||||||
expect(PLURAL.convert('lice'), equals('lice'));
|
|
||||||
expect(PLURAL.convert('ox'), equals('oxen'));
|
|
||||||
expect(PLURAL.convert('ox'), equals('oxen'));
|
|
||||||
expect(PLURAL.convert('oxen'), equals('oxen'));
|
|
||||||
expect(PLURAL.convert('quiz'), equals('quizzes'));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('handles uncountable nouns', () {
|
|
||||||
uncountableNouns.forEach((noun) {
|
|
||||||
expect(PLURAL.convert(noun), equals(noun));
|
|
||||||
});
|
|
||||||
|
|
||||||
uncountableNouns.forEach((noun) {
|
|
||||||
final upperNoun = noun.toUpperCase();
|
|
||||||
expect(PLURAL.convert(upperNoun), equals(upperNoun));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('handles irregular plural nouns', () {
|
|
||||||
expect(PLURAL.convert('person'), equals('people'));
|
|
||||||
expect(PLURAL.convert('Child'), equals('Children'));
|
|
||||||
expect(PLURAL.convert('children'), equals('children'));
|
|
||||||
expect(PLURAL.convert('man'), equals('men'));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
library inflection3.plural_verb.test;
|
|
||||||
|
|
||||||
import 'package:inflection3/inflection3.dart';
|
|
||||||
import 'package:test/test.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
group('The PluralVerbEncoder', () {
|
|
||||||
test('converts verbs from singular to plural', () {
|
|
||||||
expect(PLURALVERB.convert(''), equals(''));
|
|
||||||
expect(PLURALVERB.convert('eats'), equals('eat'));
|
|
||||||
expect(PLURALVERB.convert('goes'), equals('go'));
|
|
||||||
expect(PLURALVERB.convert('boxes'), equals('box'));
|
|
||||||
expect(PLURALVERB.convert('pays'), equals('pay'));
|
|
||||||
expect(PLURALVERB.convert('rides'), equals('ride'));
|
|
||||||
expect(PLURALVERB.convert('writes'), equals('write'));
|
|
||||||
expect(PLURALVERB.convert('wears'), equals('wear'));
|
|
||||||
expect(PLURALVERB.convert('steals'), equals('steal'));
|
|
||||||
expect(PLURALVERB.convert('springs'), equals('spring'));
|
|
||||||
expect(PLURALVERB.convert('speaks'), equals('speak'));
|
|
||||||
expect(PLURALVERB.convert('sings'), equals('sing'));
|
|
||||||
expect(PLURALVERB.convert('buses'), equals('bus'));
|
|
||||||
expect(PLURALVERB.convert('knows'), equals('know'));
|
|
||||||
expect(PLURALVERB.convert('hides'), equals('hide'));
|
|
||||||
expect(PLURALVERB.convert('catches'), equals('catch'));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('handles irregular plural verbs', () {
|
|
||||||
expect(PLURALVERB.convert('am'), equals('are'));
|
|
||||||
expect(PLURALVERB.convert('is'), equals('are'));
|
|
||||||
expect(PLURALVERB.convert('was'), equals('were'));
|
|
||||||
expect(PLURALVERB.convert('has'), equals('have'));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,64 +0,0 @@
|
||||||
library inflection3.singular.test;
|
|
||||||
|
|
||||||
import 'package:inflection3/inflection3.dart';
|
|
||||||
import 'package:inflection3/src/uncountable_nouns.dart';
|
|
||||||
import 'package:test/test.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
group('The SingularEncoder', () {
|
|
||||||
test('converts nouns from plural to singular', () {
|
|
||||||
expect(SINGULAR.convert(''), equals(''));
|
|
||||||
expect(SINGULAR.convert('Houses'), equals('House'));
|
|
||||||
expect(SINGULAR.convert('houses'), equals('house'));
|
|
||||||
expect(SINGULAR.convert('ultimata'), equals('ultimatum'));
|
|
||||||
expect(SINGULAR.convert('pentia'), equals('pentium'));
|
|
||||||
expect(SINGULAR.convert('analyses'), equals('analysis'));
|
|
||||||
expect(SINGULAR.convert('diagnoses'), equals('diagnosis'));
|
|
||||||
expect(SINGULAR.convert('Parentheses'), equals('Parenthesis'));
|
|
||||||
expect(SINGULAR.convert('lives'), equals('life'));
|
|
||||||
expect(SINGULAR.convert('hives'), equals('hive'));
|
|
||||||
expect(SINGULAR.convert('tives'), equals('tive'));
|
|
||||||
expect(SINGULAR.convert('shelves'), equals('shelf'));
|
|
||||||
expect(SINGULAR.convert('qualities'), equals('quality'));
|
|
||||||
expect(SINGULAR.convert('series'), equals('series'));
|
|
||||||
expect(SINGULAR.convert('movies'), equals('movie'));
|
|
||||||
expect(SINGULAR.convert('benches'), equals('bench'));
|
|
||||||
expect(SINGULAR.convert('fishes'), equals('fish'));
|
|
||||||
expect(SINGULAR.convert('mice'), equals('mouse'));
|
|
||||||
expect(SINGULAR.convert('lice'), equals('louse'));
|
|
||||||
expect(SINGULAR.convert('buses'), equals('bus'));
|
|
||||||
expect(SINGULAR.convert('shoes'), equals('shoe'));
|
|
||||||
expect(SINGULAR.convert('testis'), equals('testis'));
|
|
||||||
expect(SINGULAR.convert('crisis'), equals('crisis'));
|
|
||||||
expect(SINGULAR.convert('axes'), equals('axis'));
|
|
||||||
expect(SINGULAR.convert('axis'), equals('axis'));
|
|
||||||
expect(SINGULAR.convert('viri'), equals('virus'));
|
|
||||||
expect(SINGULAR.convert('octopi'), equals('octopus'));
|
|
||||||
expect(SINGULAR.convert('aliases'), equals('alias'));
|
|
||||||
expect(SINGULAR.convert('statuses'), equals('status'));
|
|
||||||
expect(SINGULAR.convert('vertices'), equals('vertex'));
|
|
||||||
expect(SINGULAR.convert('indices'), equals('index'));
|
|
||||||
expect(SINGULAR.convert('Matrices'), equals('Matrix'));
|
|
||||||
expect(SINGULAR.convert('quizzes'), equals('quiz'));
|
|
||||||
expect(SINGULAR.convert('databases'), equals('database'));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('handles uncountable nouns', () {
|
|
||||||
uncountableNouns.forEach((noun) {
|
|
||||||
expect(SINGULAR.convert(noun), equals(noun));
|
|
||||||
});
|
|
||||||
|
|
||||||
uncountableNouns.forEach((noun) {
|
|
||||||
final upperNoun = noun.toUpperCase();
|
|
||||||
expect(SINGULAR.convert(upperNoun), equals(upperNoun));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('handles irregular plural nouns', () {
|
|
||||||
expect(SINGULAR.convert('people'), equals('person'));
|
|
||||||
expect(SINGULAR.convert('Children'), equals('Child'));
|
|
||||||
expect(SINGULAR.convert('child'), equals('child'));
|
|
||||||
expect(SINGULAR.convert('men'), equals('man'));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
library inflection3.singular_verb.test;
|
|
||||||
|
|
||||||
import 'package:inflection3/inflection3.dart';
|
|
||||||
import 'package:test/test.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
group('The SingularVerbEncoder', () {
|
|
||||||
test('converts verbs from singular to plural', () {
|
|
||||||
expect(SINGULARVERB.convert(''), equals(''));
|
|
||||||
expect(SINGULARVERB.convert('eat'), equals('eats'));
|
|
||||||
expect(SINGULARVERB.convert('go'), equals('goes'));
|
|
||||||
expect(SINGULARVERB.convert('box'), equals('boxes'));
|
|
||||||
expect(SINGULARVERB.convert('pay'), equals('pays'));
|
|
||||||
expect(SINGULARVERB.convert('ride'), equals('rides'));
|
|
||||||
expect(SINGULARVERB.convert('write'), equals('writes'));
|
|
||||||
expect(SINGULARVERB.convert('wear'), equals('wears'));
|
|
||||||
expect(SINGULARVERB.convert('steal'), equals('steals'));
|
|
||||||
expect(SINGULARVERB.convert('spring'), equals('springs'));
|
|
||||||
expect(SINGULARVERB.convert('speak'), equals('speaks'));
|
|
||||||
expect(SINGULARVERB.convert('sing'), equals('sings'));
|
|
||||||
expect(SINGULARVERB.convert('bus'), equals('buses'));
|
|
||||||
expect(SINGULARVERB.convert('know'), equals('knows'));
|
|
||||||
expect(SINGULARVERB.convert('hide'), equals('hides'));
|
|
||||||
expect(SINGULARVERB.convert('catch'), equals('catches'));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('handles irregular plural verbs', () {
|
|
||||||
expect(SINGULARVERB.convert('are'), equals('is'));
|
|
||||||
expect(SINGULARVERB.convert('were'), equals('was'));
|
|
||||||
expect(SINGULARVERB.convert('have'), equals('has'));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
library inflection3.snake_case.test;
|
|
||||||
|
|
||||||
import 'package:inflection3/inflection3.dart';
|
|
||||||
import 'package:test/test.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
group('The SnakeCaseEncoder', () {
|
|
||||||
test('converts phrases to "snake_case"', () {
|
|
||||||
expect(SNAKE_CASE.convert(''), equals(''));
|
|
||||||
expect(SNAKE_CASE.convert('CamelCaseName'), equals('camel_case_name'));
|
|
||||||
expect(SNAKE_CASE.convert('propertyName'), equals('property_name'));
|
|
||||||
expect(SNAKE_CASE.convert('property'), equals('property'));
|
|
||||||
expect(SNAKE_CASE.convert('lisp-case'), equals('lisp_case'));
|
|
||||||
expect(SNAKE_CASE.convert('This is a nice article'),
|
|
||||||
equals('this_is_a_nice_article'));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
library inflection3.spinal_case.test;
|
|
||||||
|
|
||||||
import 'package:inflection3/inflection3.dart';
|
|
||||||
import 'package:test/test.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
group('The SpinalCaseEncoder', () {
|
|
||||||
test('converts phrases to "spinal-case"', () {
|
|
||||||
expect(SPINAL_CASE.convert(''), equals(''));
|
|
||||||
expect(SPINAL_CASE.convert('CamelCaseName'), equals('camel-case-name'));
|
|
||||||
expect(SPINAL_CASE.convert('propertyName'), equals('property-name'));
|
|
||||||
expect(SPINAL_CASE.convert('property'), equals('property'));
|
|
||||||
expect(SPINAL_CASE.convert('snake_case'), equals('snake-case'));
|
|
||||||
expect(SPINAL_CASE.convert('This is a nice article'),
|
|
||||||
equals('this-is-a-nice-article'));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
71
packages/json_god/.gitignore
vendored
71
packages/json_god/.gitignore
vendored
|
@ -1,71 +0,0 @@
|
||||||
# See https://www.dartlang.org/tools/private-files.html
|
|
||||||
|
|
||||||
# Files and directories created by pub
|
|
||||||
.dart_tool
|
|
||||||
.packages
|
|
||||||
.pub/
|
|
||||||
build/
|
|
||||||
|
|
||||||
# If you're building an application, you may want to check-in your pubspec.lock
|
|
||||||
pubspec.lock
|
|
||||||
|
|
||||||
# Directory created by dartdoc
|
|
||||||
# If you don't generate documentation locally you can remove this line.
|
|
||||||
doc/api/
|
|
||||||
|
|
||||||
### Dart template
|
|
||||||
# See https://www.dartlang.org/tools/private-files.html
|
|
||||||
|
|
||||||
# Files and directories created by pub
|
|
||||||
|
|
||||||
# SDK 1.20 and later (no longer creates packages directories)
|
|
||||||
|
|
||||||
# Older SDK versions
|
|
||||||
# (Include if the minimum SDK version specified in pubsepc.yaml is earlier than 1.20)
|
|
||||||
.project
|
|
||||||
.buildlog
|
|
||||||
**/packages/
|
|
||||||
|
|
||||||
|
|
||||||
# Files created by dart2js
|
|
||||||
# (Most Dart developers will use pub build to compile Dart, use/modify these
|
|
||||||
# rules if you intend to use dart2js directly
|
|
||||||
# Convention is to use extension '.dart.js' for Dart compiled to Javascript to
|
|
||||||
# differentiate from explicit Javascript files)
|
|
||||||
*.dart.js
|
|
||||||
*.part.js
|
|
||||||
*.js.deps
|
|
||||||
*.js.map
|
|
||||||
*.info.json
|
|
||||||
|
|
||||||
# Directory created by dartdoc
|
|
||||||
|
|
||||||
# Don't commit pubspec lock file
|
|
||||||
# (Library packages only! Remove pattern if developing an application package)
|
|
||||||
### JetBrains template
|
|
||||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
|
|
||||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
|
||||||
|
|
||||||
# User-specific stuff:
|
|
||||||
|
|
||||||
## VsCode
|
|
||||||
.vscode/
|
|
||||||
|
|
||||||
## File-based project format:
|
|
||||||
*.iws
|
|
||||||
|
|
||||||
## Plugin-specific files:
|
|
||||||
|
|
||||||
# IntelliJ
|
|
||||||
.idea/
|
|
||||||
/out/
|
|
||||||
.idea_modules/
|
|
||||||
|
|
||||||
# JIRA plugin
|
|
||||||
atlassian-ide-plugin.xml
|
|
||||||
|
|
||||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
|
||||||
com_crashlytics_export_strings.xml
|
|
||||||
crashlytics.properties
|
|
||||||
crashlytics-build.properties
|
|
||||||
fabric.properties
|
|
|
@ -1,4 +0,0 @@
|
||||||
language: dart
|
|
||||||
dart:
|
|
||||||
- dev
|
|
||||||
- stable
|
|
|
@ -1,12 +0,0 @@
|
||||||
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.
|
|
|
@ -1,25 +0,0 @@
|
||||||
# 4.0.3
|
|
||||||
* Fixed static analysis errors
|
|
||||||
|
|
||||||
# 4.0.2
|
|
||||||
* Updated pubspec description
|
|
||||||
* Fixed static analysis errors
|
|
||||||
|
|
||||||
# 4.0.1
|
|
||||||
* Added example
|
|
||||||
* Updated README
|
|
||||||
|
|
||||||
# 4.0.0
|
|
||||||
* Migrated to support Dart SDK 2.12.x NNBD
|
|
||||||
|
|
||||||
# 3.0.0
|
|
||||||
* Migrated to work with Dart SDK 2.12.x Non NNBD
|
|
||||||
|
|
||||||
# 2.0.0-beta+3
|
|
||||||
* Long-needed updates, ensured Dart 2 compatibility, fixed DDC breakages.
|
|
||||||
* Patches for reflection bugs with typing.
|
|
||||||
|
|
||||||
# 2.0.0-beta+2
|
|
||||||
* This version breaks in certain Dart versions (likely anything *after* `2.0.0-dev.59.0`)
|
|
||||||
until https://github.com/dart-lang/sdk/issues/33594 is resolved.
|
|
||||||
* Removes the reference to `Schema` class.
|
|
|
@ -1,21 +0,0 @@
|
||||||
MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2021 dukefirehawk.com
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
|
@ -1,112 +0,0 @@
|
||||||
# angel3_json_god
|
|
||||||
|
|
||||||
[![version](https://img.shields.io/badge/pub-v4.0.3-brightgreen)](https://pub.dartlang.org/packages/angel3_json_god)
|
|
||||||
[![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety)
|
|
||||||
[![Gitter](https://img.shields.io/gitter/room/angel_dart/discussion)](https://gitter.im/angel_dart/discussion)
|
|
||||||
|
|
||||||
[![License](https://img.shields.io/github/license/dukefirehawk/angel)](https://github.com/dukefirehawk/angel/tree/angel3/packages/json_god/LICENSE)
|
|
||||||
|
|
||||||
**DEPRECATED: Replaced by [`belatuk_json_serializer`](https://pub.dartlang.org/packages/belatuk_json_serializer) package**
|
|
||||||
|
|
||||||
The ***new and improved*** definitive solution for JSON in Dart. It supports synchronously transform an object into a JSON string and also deserialize a JSON string back into an instance of any type.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
dependencies:
|
|
||||||
angel3_json_god: ^4.0.0
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
It is recommended to import the library under an alias, i.e., `god`.
|
|
||||||
|
|
||||||
```dart
|
|
||||||
import 'package:angel3_json_god/angel3_json_god.dart' as god;
|
|
||||||
```
|
|
||||||
|
|
||||||
## Serializing JSON
|
|
||||||
|
|
||||||
Simply call `god.serialize(x)` to synchronously transform an object into a JSON
|
|
||||||
string.
|
|
||||||
|
|
||||||
```dart
|
|
||||||
Map map = {"foo": "bar", "numbers": [1, 2, {"three": 4}]};
|
|
||||||
|
|
||||||
// Output: {"foo":"bar","numbers":[1,2,{"three":4]"}
|
|
||||||
String json = god.serialize(map);
|
|
||||||
print(json);
|
|
||||||
```
|
|
||||||
|
|
||||||
You can easily serialize classes, too. JSON God also supports classes as members.
|
|
||||||
|
|
||||||
```dart
|
|
||||||
|
|
||||||
class A {
|
|
||||||
String foo;
|
|
||||||
A(this.foo);
|
|
||||||
}
|
|
||||||
|
|
||||||
class B {
|
|
||||||
late String hello;
|
|
||||||
late A nested;
|
|
||||||
B(String hello, String foo) {
|
|
||||||
this.hello = hello;
|
|
||||||
this.nested = A(foo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
main() {
|
|
||||||
print(god.serialize( B("world", "bar")));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Output: {"hello":"world","nested":{"foo":"bar"}}
|
|
||||||
```
|
|
||||||
|
|
||||||
If a class has a `toJson` method, it will be called instead.
|
|
||||||
|
|
||||||
## Deserializing JSON
|
|
||||||
|
|
||||||
Deserialization is equally easy, and is provided through `god.deserialize`.
|
|
||||||
|
|
||||||
```dart
|
|
||||||
Map map = god.deserialize('{"hello":"world"}');
|
|
||||||
int three = god.deserialize("3");
|
|
||||||
```
|
|
||||||
|
|
||||||
### Deserializing to Classes
|
|
||||||
|
|
||||||
JSON God lets you deserialize JSON into an instance of any type. Simply pass the
|
|
||||||
type as the second argument to `god.deserialize`.
|
|
||||||
|
|
||||||
If the class has a `fromJson` constructor, it will be called instead.
|
|
||||||
|
|
||||||
```dart
|
|
||||||
class Child {
|
|
||||||
String foo;
|
|
||||||
}
|
|
||||||
|
|
||||||
class Parent {
|
|
||||||
String hello;
|
|
||||||
Child child = Child();
|
|
||||||
}
|
|
||||||
|
|
||||||
main() {
|
|
||||||
God god = God();
|
|
||||||
Parent parent = god.deserialize('{"hello":"world","child":{"foo":"bar"}}', Parent);
|
|
||||||
print(parent);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Any JSON-deserializable classes must initializable without parameters.
|
|
||||||
If `Foo()` would throw an error, then you can't use Foo with JSON.**
|
|
||||||
|
|
||||||
This allows for validation of a sort, as only fields you have declared will be
|
|
||||||
accepted.
|
|
||||||
|
|
||||||
```dart
|
|
||||||
class HasAnInt { int theInt; }
|
|
||||||
|
|
||||||
HasAnInt invalid = god.deserialize('["some invalid input"]', HasAnInt);
|
|
||||||
// Throws an error
|
|
||||||
```
|
|
||||||
|
|
||||||
An exception will be thrown if validation fails.
|
|
|
@ -1,3 +0,0 @@
|
||||||
analyzer:
|
|
||||||
strong-mode:
|
|
||||||
implicit-casts: false
|
|
|
@ -1,19 +0,0 @@
|
||||||
import 'package:angel3_json_god/angel3_json_god.dart' as god;
|
|
||||||
|
|
||||||
class A {
|
|
||||||
String foo;
|
|
||||||
A(this.foo);
|
|
||||||
}
|
|
||||||
|
|
||||||
class B {
|
|
||||||
late String hello;
|
|
||||||
late A nested;
|
|
||||||
B(String hello, String foo) {
|
|
||||||
this.hello = hello;
|
|
||||||
this.nested = A(foo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
print(god.serialize(B("world", "bar")));
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
/// A robust library for JSON serialization and deserialization.
|
|
||||||
library angel3_json_god;
|
|
||||||
|
|
||||||
//import 'package:dart2_constant/convert.dart';
|
|
||||||
import 'dart:convert';
|
|
||||||
import 'package:logging/logging.dart';
|
|
||||||
import 'src/reflection.dart' as reflection;
|
|
||||||
|
|
||||||
part 'src/serialize.dart';
|
|
||||||
part 'src/deserialize.dart';
|
|
||||||
part 'src/validation.dart';
|
|
||||||
part 'src/util.dart';
|
|
||||||
|
|
||||||
/// Instead, listen to [logger].
|
|
||||||
@deprecated
|
|
||||||
bool debug = false;
|
|
||||||
|
|
||||||
final Logger logger = Logger('json_god');
|
|
|
@ -1,44 +0,0 @@
|
||||||
part of angel3_json_god;
|
|
||||||
|
|
||||||
/// Deserializes a JSON string into a Dart datum.
|
|
||||||
///
|
|
||||||
/// You can also provide an output Type to attempt to serialize the JSON into.
|
|
||||||
deserialize(String json, {Type? outputType}) {
|
|
||||||
var deserialized = deserializeJson(json, outputType: outputType);
|
|
||||||
logger.info("Deserialization result: $deserialized");
|
|
||||||
return deserialized;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Deserializes JSON into data, without validating it.
|
|
||||||
deserializeJson(String s, {Type? outputType}) {
|
|
||||||
logger.info("Deserializing the following JSON: $s");
|
|
||||||
|
|
||||||
if (outputType == null) {
|
|
||||||
logger
|
|
||||||
.info("No output type was specified, so we are just using json.decode");
|
|
||||||
return json.decode(s);
|
|
||||||
} else {
|
|
||||||
logger.info("Now deserializing to type: $outputType");
|
|
||||||
return deserializeDatum(json.decode(s), outputType: outputType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Deserializes some JSON-serializable value into a usable Dart value.
|
|
||||||
deserializeDatum(value, {Type? outputType}) {
|
|
||||||
if (outputType != null) {
|
|
||||||
return reflection.deserialize(value, outputType, deserializeDatum);
|
|
||||||
} else if (value is List) {
|
|
||||||
logger.info("Deserializing this List: $value");
|
|
||||||
return value.map(deserializeDatum).toList();
|
|
||||||
} else if (value is Map) {
|
|
||||||
logger.info("Deserializing this Map: $value");
|
|
||||||
Map result = {};
|
|
||||||
value.forEach((k, v) {
|
|
||||||
result[k] = deserializeDatum(v);
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
} else if (_isPrimitive(value)) {
|
|
||||||
logger.info("Value $value is a primitive");
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,189 +0,0 @@
|
||||||
library angel3_json_god.reflection;
|
|
||||||
|
|
||||||
import 'dart:mirrors';
|
|
||||||
import '../angel3_json_god.dart';
|
|
||||||
|
|
||||||
const Symbol hashCodeSymbol = #hashCode;
|
|
||||||
const Symbol runtimeTypeSymbol = #runtimeType;
|
|
||||||
|
|
||||||
typedef Serializer(value);
|
|
||||||
typedef Deserializer(value, {Type? outputType});
|
|
||||||
|
|
||||||
List<Symbol> _findGetters(ClassMirror classMirror) {
|
|
||||||
List<Symbol> result = [];
|
|
||||||
|
|
||||||
classMirror.instanceMembers
|
|
||||||
.forEach((Symbol symbol, MethodMirror methodMirror) {
|
|
||||||
if (methodMirror.isGetter &&
|
|
||||||
symbol != hashCodeSymbol &&
|
|
||||||
symbol != runtimeTypeSymbol) {
|
|
||||||
logger.info("Found getter on instance: $symbol");
|
|
||||||
result.add(symbol);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
serialize(value, Serializer serializer, [@deprecated bool debug = false]) {
|
|
||||||
logger.info("Serializing this value via reflection: $value");
|
|
||||||
Map result = {};
|
|
||||||
InstanceMirror instanceMirror = reflect(value);
|
|
||||||
ClassMirror classMirror = instanceMirror.type;
|
|
||||||
|
|
||||||
// Check for toJson
|
|
||||||
for (Symbol symbol in classMirror.instanceMembers.keys) {
|
|
||||||
if (symbol == #toJson) {
|
|
||||||
logger.info("Running toJson...");
|
|
||||||
var result = instanceMirror.invoke(symbol, []).reflectee;
|
|
||||||
logger.info("Result of serialization via reflection: $result");
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Symbol symbol in _findGetters(classMirror)) {
|
|
||||||
String name = MirrorSystem.getName(symbol);
|
|
||||||
var valueForSymbol = instanceMirror.getField(symbol).reflectee;
|
|
||||||
|
|
||||||
try {
|
|
||||||
result[name] = serializer(valueForSymbol);
|
|
||||||
logger.info("Set $name to $valueForSymbol");
|
|
||||||
} catch (e, st) {
|
|
||||||
logger.severe("Could not set $name to $valueForSymbol", e, st);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info("Result of serialization via reflection: $result");
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
deserialize(value, Type outputType, Deserializer deserializer,
|
|
||||||
[@deprecated bool debug = false]) {
|
|
||||||
logger.info("About to deserialize $value to a $outputType");
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (value is List) {
|
|
||||||
List<TypeMirror> typeArguments = reflectType(outputType).typeArguments;
|
|
||||||
|
|
||||||
Iterable it;
|
|
||||||
|
|
||||||
if (typeArguments.isEmpty) {
|
|
||||||
it = value.map(deserializer);
|
|
||||||
} else {
|
|
||||||
it = value.map((item) =>
|
|
||||||
deserializer(item, outputType: typeArguments[0].reflectedType));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeArguments.isEmpty) return it.toList();
|
|
||||||
logger.info(
|
|
||||||
'Casting list elements to ${typeArguments[0].reflectedType} via List.from');
|
|
||||||
|
|
||||||
var mirror = reflectType(List, [typeArguments[0].reflectedType]);
|
|
||||||
|
|
||||||
if (mirror is ClassMirror) {
|
|
||||||
var output = mirror.newInstance(#from, [it]).reflectee;
|
|
||||||
logger.info('Casted list type: ${output.runtimeType}');
|
|
||||||
return output;
|
|
||||||
} else {
|
|
||||||
throw ArgumentError(
|
|
||||||
'${typeArguments[0].reflectedType} is not a class.');
|
|
||||||
}
|
|
||||||
} else if (value is Map) {
|
|
||||||
return _deserializeFromJsonByReflection(value, deserializer, outputType);
|
|
||||||
} else {
|
|
||||||
return deserializer(value);
|
|
||||||
}
|
|
||||||
} catch (e, st) {
|
|
||||||
logger.severe('Deserialization failed.', e, st);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Uses mirrors to deserialize an object.
|
|
||||||
_deserializeFromJsonByReflection(
|
|
||||||
data, Deserializer deserializer, Type outputType) {
|
|
||||||
// Check for fromJson
|
|
||||||
var typeMirror = reflectType(outputType);
|
|
||||||
|
|
||||||
if (typeMirror is! ClassMirror) {
|
|
||||||
throw ArgumentError('$outputType is not a class.');
|
|
||||||
}
|
|
||||||
|
|
||||||
var type = typeMirror;
|
|
||||||
var fromJson = Symbol('${MirrorSystem.getName(type.simpleName)}.fromJson');
|
|
||||||
|
|
||||||
for (Symbol symbol in type.declarations.keys) {
|
|
||||||
if (symbol == fromJson) {
|
|
||||||
var decl = type.declarations[symbol];
|
|
||||||
|
|
||||||
if (decl is MethodMirror && decl.isConstructor) {
|
|
||||||
logger.info("Running fromJson...");
|
|
||||||
var result = type.newInstance(#fromJson, [data]).reflectee;
|
|
||||||
|
|
||||||
logger.info("Result of deserialization via reflection: $result");
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ClassMirror classMirror = type;
|
|
||||||
InstanceMirror instanceMirror = classMirror.newInstance(Symbol(""), []);
|
|
||||||
|
|
||||||
if (classMirror.isSubclassOf(reflectClass(Map))) {
|
|
||||||
var typeArguments = classMirror.typeArguments;
|
|
||||||
|
|
||||||
if (typeArguments.isEmpty ||
|
|
||||||
classMirror.typeArguments
|
|
||||||
.every((t) => t == currentMirrorSystem().dynamicType)) {
|
|
||||||
return data;
|
|
||||||
} else {
|
|
||||||
var mapType =
|
|
||||||
reflectType(Map, typeArguments.map((t) => t.reflectedType).toList())
|
|
||||||
as ClassMirror;
|
|
||||||
logger.info('Casting this map $data to Map of [$typeArguments]');
|
|
||||||
var output = mapType.newInstance(Symbol(''), []).reflectee;
|
|
||||||
|
|
||||||
for (var key in data.keys) {
|
|
||||||
output[key] = data[key];
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info('Output: $output of type ${output.runtimeType}');
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
data.keys.forEach((key) {
|
|
||||||
try {
|
|
||||||
logger.info("Now deserializing value for $key");
|
|
||||||
logger.info("data[\"$key\"] = ${data[key]}");
|
|
||||||
var deserializedValue = deserializer(data[key]);
|
|
||||||
|
|
||||||
logger.info(
|
|
||||||
"I want to set $key to the following ${deserializedValue.runtimeType}: $deserializedValue");
|
|
||||||
// Get target type of getter
|
|
||||||
Symbol searchSymbol = Symbol(key.toString());
|
|
||||||
Symbol symbolForGetter = classMirror.instanceMembers.keys
|
|
||||||
.firstWhere((x) => x == searchSymbol);
|
|
||||||
Type requiredType = classMirror
|
|
||||||
.instanceMembers[symbolForGetter]!.returnType.reflectedType;
|
|
||||||
if (data[key].runtimeType != requiredType) {
|
|
||||||
logger.info("Currently, $key is a ${data[key].runtimeType}.");
|
|
||||||
logger.info("However, $key must be a $requiredType.");
|
|
||||||
|
|
||||||
deserializedValue =
|
|
||||||
deserializer(deserializedValue, outputType: requiredType);
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info(
|
|
||||||
"Final deserialized value for $key: $deserializedValue <${deserializedValue.runtimeType}>");
|
|
||||||
instanceMirror.setField(Symbol(key.toString()), deserializedValue);
|
|
||||||
|
|
||||||
logger.info("Success! $key has been set to $deserializedValue");
|
|
||||||
} catch (e, st) {
|
|
||||||
logger.severe('Could not set value for field $key.', e, st);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return instanceMirror.reflectee;
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
part of angel3_json_god;
|
|
||||||
|
|
||||||
/// Serializes any arbitrary Dart datum to JSON. Supports schema validation.
|
|
||||||
String serialize(value) {
|
|
||||||
var serialized = serializeObject(value);
|
|
||||||
logger.info('Serialization result: $serialized');
|
|
||||||
return json.encode(serialized);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Transforms any Dart datum into a value acceptable to json.encode.
|
|
||||||
serializeObject(value) {
|
|
||||||
if (_isPrimitive(value)) {
|
|
||||||
logger.info("Serializing primitive value: $value");
|
|
||||||
return value;
|
|
||||||
} else if (value is DateTime) {
|
|
||||||
logger.info("Serializing this DateTime: $value");
|
|
||||||
return value.toIso8601String();
|
|
||||||
} else if (value is Iterable) {
|
|
||||||
logger.info("Serializing this Iterable: $value");
|
|
||||||
return value.map(serializeObject).toList();
|
|
||||||
} else if (value is Map) {
|
|
||||||
logger.info("Serializing this Map: $value");
|
|
||||||
return serializeMap(value);
|
|
||||||
} else {
|
|
||||||
return serializeObject(reflection.serialize(value, serializeObject));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Recursively transforms a Map and its children into JSON-serializable data.
|
|
||||||
Map serializeMap(Map value) {
|
|
||||||
Map outputMap = {};
|
|
||||||
value.forEach((key, value) {
|
|
||||||
outputMap[key] = serializeObject(value);
|
|
||||||
});
|
|
||||||
return outputMap;
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
part of angel3_json_god;
|
|
||||||
|
|
||||||
bool _isPrimitive(value) {
|
|
||||||
return value is num || value is bool || value is String || value == null;
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
part of angel3_json_god;
|
|
||||||
|
|
||||||
/// Thrown when schema validation fails.
|
|
||||||
class JsonValidationError implements Exception {
|
|
||||||
//final Schema schema;
|
|
||||||
final invalidData;
|
|
||||||
final String cause;
|
|
||||||
|
|
||||||
const JsonValidationError(
|
|
||||||
this.cause, this.invalidData); //, Schema this.schema);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Specifies a schema to validate a class with.
|
|
||||||
class WithSchema {
|
|
||||||
final Map schema;
|
|
||||||
|
|
||||||
const WithSchema(this.schema);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Specifies a schema to validate a class with.
|
|
||||||
class WithSchemaUrl {
|
|
||||||
final String schemaUrl;
|
|
||||||
|
|
||||||
const WithSchemaUrl(this.schemaUrl);
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
name: angel3_json_god
|
|
||||||
version: 4.0.3
|
|
||||||
description: Easy JSON to Object serialization and deserialization in Dart.
|
|
||||||
homepage: https://github.com/dukefirehawk/angel/tree/angel3/packages/json_god
|
|
||||||
environment:
|
|
||||||
sdk: '>=2.12.0 <3.0.0'
|
|
||||||
dependencies:
|
|
||||||
#dart2_constant: ^1.0.0
|
|
||||||
logging: ^1.0.1
|
|
||||||
dev_dependencies:
|
|
||||||
stack_trace: ^1.10.0
|
|
||||||
test: ^1.17.4
|
|
|
@ -1,115 +0,0 @@
|
||||||
import 'package:angel3_json_god/angel3_json_god.dart' as god;
|
|
||||||
import 'package:test/test.dart';
|
|
||||||
import 'shared.dart';
|
|
||||||
|
|
||||||
main() {
|
|
||||||
god.logger.onRecord.listen(printRecord);
|
|
||||||
|
|
||||||
group('deserialization', () {
|
|
||||||
test('deserialize primitives', testDeserializationOfPrimitives);
|
|
||||||
|
|
||||||
test('deserialize maps', testDeserializationOfMaps);
|
|
||||||
|
|
||||||
test('deserialize maps + reflection',
|
|
||||||
testDeserializationOfMapsWithReflection);
|
|
||||||
|
|
||||||
test('deserialize lists + reflection',
|
|
||||||
testDeserializationOfListsAsWellAsViaReflection);
|
|
||||||
|
|
||||||
test('deserialize with schema validation',
|
|
||||||
testDeserializationWithSchemaValidation);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
testDeserializationOfPrimitives() {
|
|
||||||
expect(god.deserialize('1'), equals(1));
|
|
||||||
expect(god.deserialize('1.4'), equals(1.4));
|
|
||||||
expect(god.deserialize('"Hi!"'), equals("Hi!"));
|
|
||||||
expect(god.deserialize("true"), equals(true));
|
|
||||||
expect(god.deserialize("null"), equals(null));
|
|
||||||
}
|
|
||||||
|
|
||||||
testDeserializationOfMaps() {
|
|
||||||
String simpleJson =
|
|
||||||
'{"hello":"world", "one": 1, "class": {"hello": "world"}}';
|
|
||||||
String nestedJson =
|
|
||||||
'{"foo": {"bar": "baz", "funny": {"how": "life", "seems": 2, "hate": "us sometimes"}}}';
|
|
||||||
var simple = god.deserialize(simpleJson) as Map;
|
|
||||||
var nested = god.deserialize(nestedJson) as Map;
|
|
||||||
|
|
||||||
expect(simple['hello'], equals('world'));
|
|
||||||
expect(simple['one'], equals(1));
|
|
||||||
expect(simple['class']['hello'], equals('world'));
|
|
||||||
|
|
||||||
expect(nested['foo']['bar'], equals('baz'));
|
|
||||||
expect(nested['foo']['funny']['how'], equals('life'));
|
|
||||||
expect(nested['foo']['funny']['seems'], equals(2));
|
|
||||||
expect(nested['foo']['funny']['hate'], equals('us sometimes'));
|
|
||||||
}
|
|
||||||
|
|
||||||
class Pokedex {
|
|
||||||
Map<String, int>? pokemon;
|
|
||||||
}
|
|
||||||
|
|
||||||
testDeserializationOfMapsWithReflection() {
|
|
||||||
var s = '{"pokemon": {"Bulbasaur": 1, "Deoxys": 382}}';
|
|
||||||
var pokedex = god.deserialize(s, outputType: Pokedex) as Pokedex;
|
|
||||||
expect(pokedex.pokemon, hasLength(2));
|
|
||||||
expect(pokedex.pokemon!['Bulbasaur'], 1);
|
|
||||||
expect(pokedex.pokemon!['Deoxys'], 382);
|
|
||||||
}
|
|
||||||
|
|
||||||
testDeserializationOfListsAsWellAsViaReflection() {
|
|
||||||
String json = '''[
|
|
||||||
{
|
|
||||||
"hello": "world",
|
|
||||||
"nested": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"hello": "dolly",
|
|
||||||
"nested": [
|
|
||||||
{
|
|
||||||
"bar": "baz"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bar": "fight"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
''';
|
|
||||||
|
|
||||||
var list = god.deserialize(json, outputType: (<SampleClass>[]).runtimeType)
|
|
||||||
as List<SampleClass>;
|
|
||||||
SampleClass first = list[0];
|
|
||||||
SampleClass second = list[1];
|
|
||||||
|
|
||||||
expect(list.length, equals(2));
|
|
||||||
expect(first.hello, equals("world"));
|
|
||||||
expect(first.nested.length, equals(0));
|
|
||||||
expect(second.hello, equals("dolly"));
|
|
||||||
expect(second.nested.length, equals(2));
|
|
||||||
|
|
||||||
SampleNestedClass firstNested = second.nested[0];
|
|
||||||
SampleNestedClass secondNested = second.nested[1];
|
|
||||||
|
|
||||||
expect(firstNested.bar, equals("baz"));
|
|
||||||
expect(secondNested.bar, equals("fight"));
|
|
||||||
}
|
|
||||||
|
|
||||||
testDeserializationWithSchemaValidation() async {
|
|
||||||
String babelRcJson =
|
|
||||||
'{"presets":["es2015","stage-0"],"plugins":["add-module-exports"]}';
|
|
||||||
|
|
||||||
var deserialized =
|
|
||||||
god.deserialize(babelRcJson, outputType: BabelRc) as BabelRc;
|
|
||||||
|
|
||||||
print(deserialized.presets.runtimeType);
|
|
||||||
expect(deserialized.presets is List, equals(true));
|
|
||||||
expect(deserialized.presets.length, equals(2));
|
|
||||||
expect(deserialized.presets[0], equals('es2015'));
|
|
||||||
expect(deserialized.presets[1], equals('stage-0'));
|
|
||||||
expect(deserialized.plugins is List, equals(true));
|
|
||||||
expect(deserialized.plugins.length, equals(1));
|
|
||||||
expect(deserialized.plugins[0], equals('add-module-exports'));
|
|
||||||
}
|
|
|
@ -1,133 +0,0 @@
|
||||||
//import 'package:dart2_constant/convert.dart';
|
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:angel3_json_god/angel3_json_god.dart' as god;
|
|
||||||
import 'package:test/test.dart';
|
|
||||||
import 'shared.dart';
|
|
||||||
|
|
||||||
main() {
|
|
||||||
god.logger.onRecord.listen(printRecord);
|
|
||||||
|
|
||||||
group('serialization', () {
|
|
||||||
test('serialize primitives', testSerializationOfPrimitives);
|
|
||||||
|
|
||||||
test('serialize dates', testSerializationOfDates);
|
|
||||||
|
|
||||||
test('serialize maps', testSerializationOfMaps);
|
|
||||||
|
|
||||||
test('serialize lists', testSerializationOfLists);
|
|
||||||
|
|
||||||
test('serialize via reflection', testSerializationViaReflection);
|
|
||||||
|
|
||||||
test('serialize with schema validation',
|
|
||||||
testSerializationWithSchemaValidation);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
testSerializationOfPrimitives() {
|
|
||||||
expect(god.serialize(1), equals("1"));
|
|
||||||
expect(god.serialize(1.4), equals("1.4"));
|
|
||||||
expect(god.serialize("Hi!"), equals('"Hi!"'));
|
|
||||||
expect(god.serialize(true), equals("true"));
|
|
||||||
expect(god.serialize(null), equals("null"));
|
|
||||||
}
|
|
||||||
|
|
||||||
testSerializationOfDates() {
|
|
||||||
DateTime date = DateTime.now();
|
|
||||||
String s = god.serialize({'date': date});
|
|
||||||
|
|
||||||
print(s);
|
|
||||||
|
|
||||||
var deserialized = json.decode(s);
|
|
||||||
expect(deserialized['date'], equals(date.toIso8601String()));
|
|
||||||
}
|
|
||||||
|
|
||||||
testSerializationOfMaps() {
|
|
||||||
var simple = json.decode(god
|
|
||||||
.serialize({'hello': 'world', 'one': 1, 'class': SampleClass('world')}));
|
|
||||||
var nested = json.decode(god.serialize({
|
|
||||||
'foo': {
|
|
||||||
'bar': 'baz',
|
|
||||||
'funny': {'how': 'life', 'seems': 2, 'hate': 'us sometimes'}
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
expect(simple['hello'], equals('world'));
|
|
||||||
expect(simple['one'], equals(1));
|
|
||||||
expect(simple['class']['hello'], equals('world'));
|
|
||||||
|
|
||||||
expect(nested['foo']['bar'], equals('baz'));
|
|
||||||
expect(nested['foo']['funny']['how'], equals('life'));
|
|
||||||
expect(nested['foo']['funny']['seems'], equals(2));
|
|
||||||
expect(nested['foo']['funny']['hate'], equals('us sometimes'));
|
|
||||||
}
|
|
||||||
|
|
||||||
testSerializationOfLists() {
|
|
||||||
List pandorasBox = [
|
|
||||||
1,
|
|
||||||
"2",
|
|
||||||
{"num": 3, "four": SampleClass('five')},
|
|
||||||
SampleClass('six')..nested.add(SampleNestedClass('seven'))
|
|
||||||
];
|
|
||||||
String s = god.serialize(pandorasBox);
|
|
||||||
print(s);
|
|
||||||
|
|
||||||
var deserialized = json.decode(s);
|
|
||||||
|
|
||||||
expect(deserialized is List, equals(true));
|
|
||||||
expect(deserialized.length, equals(4));
|
|
||||||
expect(deserialized[0], equals(1));
|
|
||||||
expect(deserialized[1], equals("2"));
|
|
||||||
expect(deserialized[2] is Map, equals(true));
|
|
||||||
expect(deserialized[2]['num'], equals(3));
|
|
||||||
expect(deserialized[2]['four'] is Map, equals(true));
|
|
||||||
expect(deserialized[2]['four']['hello'], equals('five'));
|
|
||||||
expect(deserialized[3] is Map, equals(true));
|
|
||||||
expect(deserialized[3]['hello'], equals('six'));
|
|
||||||
expect(deserialized[3]['nested'] is List, equals(true));
|
|
||||||
expect(deserialized[3]['nested'].length, equals(1));
|
|
||||||
expect(deserialized[3]['nested'][0] is Map, equals(true));
|
|
||||||
expect(deserialized[3]['nested'][0]['bar'], equals('seven'));
|
|
||||||
}
|
|
||||||
|
|
||||||
testSerializationViaReflection() {
|
|
||||||
SampleClass sample = SampleClass('world');
|
|
||||||
|
|
||||||
for (int i = 0; i < 3; i++) {
|
|
||||||
sample.nested.add(SampleNestedClass('baz'));
|
|
||||||
}
|
|
||||||
|
|
||||||
String s = god.serialize(sample);
|
|
||||||
print(s);
|
|
||||||
|
|
||||||
var deserialized = json.decode(s);
|
|
||||||
expect(deserialized['hello'], equals('world'));
|
|
||||||
expect(deserialized['nested'] is List, equals(true));
|
|
||||||
expect(deserialized['nested'].length == 3, equals(true));
|
|
||||||
expect(deserialized['nested'][0]['bar'], equals('baz'));
|
|
||||||
expect(deserialized['nested'][1]['bar'], equals('baz'));
|
|
||||||
expect(deserialized['nested'][2]['bar'], equals('baz'));
|
|
||||||
}
|
|
||||||
|
|
||||||
testSerializationWithSchemaValidation() async {
|
|
||||||
BabelRc babelRc =
|
|
||||||
BabelRc(presets: ['es2015', 'stage-0'], plugins: ['add-module-exports']);
|
|
||||||
|
|
||||||
String s = god.serialize(babelRc);
|
|
||||||
print(s);
|
|
||||||
|
|
||||||
var deserialized = json.decode(s);
|
|
||||||
|
|
||||||
expect(deserialized['presets'] is List, equals(true));
|
|
||||||
expect(deserialized['presets'].length, equals(2));
|
|
||||||
expect(deserialized['presets'][0], equals('es2015'));
|
|
||||||
expect(deserialized['presets'][1], equals('stage-0'));
|
|
||||||
expect(deserialized['plugins'] is List, equals(true));
|
|
||||||
expect(deserialized['plugins'].length, equals(1));
|
|
||||||
expect(deserialized['plugins'][0], equals('add-module-exports'));
|
|
||||||
|
|
||||||
//Map babelRc2 = {'presets': 'Hello, world!'};
|
|
||||||
|
|
||||||
String json2 = god.serialize(babelRc);
|
|
||||||
print(json2);
|
|
||||||
}
|
|
|
@ -1,51 +0,0 @@
|
||||||
import 'package:logging/logging.dart';
|
|
||||||
import 'package:angel3_json_god/angel3_json_god.dart';
|
|
||||||
import 'package:stack_trace/stack_trace.dart';
|
|
||||||
|
|
||||||
void printRecord(LogRecord rec) {
|
|
||||||
print(rec);
|
|
||||||
if (rec.error != null) print(rec.error);
|
|
||||||
if (rec.stackTrace != null) print(Chain.forTrace(rec.stackTrace!).terse);
|
|
||||||
}
|
|
||||||
|
|
||||||
class SampleNestedClass {
|
|
||||||
String? bar;
|
|
||||||
|
|
||||||
SampleNestedClass([String? this.bar]);
|
|
||||||
}
|
|
||||||
|
|
||||||
class SampleClass {
|
|
||||||
String? hello;
|
|
||||||
List<SampleNestedClass> nested = [];
|
|
||||||
|
|
||||||
SampleClass([String? this.hello]);
|
|
||||||
}
|
|
||||||
|
|
||||||
@WithSchemaUrl(
|
|
||||||
"http://raw.githubusercontent.com/SchemaStore/schemastore/master/src/schemas/json/babelrc.json")
|
|
||||||
class BabelRc {
|
|
||||||
List<String> presets;
|
|
||||||
List<String> plugins;
|
|
||||||
|
|
||||||
BabelRc(
|
|
||||||
{List<String> this.presets: const [],
|
|
||||||
List<String> this.plugins: const []});
|
|
||||||
}
|
|
||||||
|
|
||||||
@WithSchema(const {
|
|
||||||
r"$schema": "http://json-schema.org/draft-04/schema#",
|
|
||||||
"title": "Validated Sample Class",
|
|
||||||
"description": "Sample schema for validation via JSON God",
|
|
||||||
"type": "object",
|
|
||||||
"hello": const {"description": "A friendly greeting.", "type": "string"},
|
|
||||||
"nested": const {
|
|
||||||
"description": "A list of NestedSampleClass items within this instance.",
|
|
||||||
"type": "array",
|
|
||||||
"items": const {
|
|
||||||
"type": "object",
|
|
||||||
"bar": const {"description": "Filler text", "type": "string"}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": const ["hello", "nested"]
|
|
||||||
})
|
|
||||||
class ValidatedSampleClass {}
|
|
|
@ -1,32 +0,0 @@
|
||||||
import 'package:angel3_json_god/angel3_json_god.dart' as god;
|
|
||||||
import 'package:test/test.dart';
|
|
||||||
import 'shared.dart';
|
|
||||||
|
|
||||||
main() {
|
|
||||||
god.logger.onRecord.listen(printRecord);
|
|
||||||
|
|
||||||
test('fromJson', () {
|
|
||||||
var foo = god.deserialize('{"bar":"baz"}', outputType: Foo) as Foo;
|
|
||||||
|
|
||||||
expect(foo is Foo, true);
|
|
||||||
expect(foo.text, equals('baz'));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('toJson', () {
|
|
||||||
var foo = Foo(text: 'baz');
|
|
||||||
var data = god.serializeObject(foo);
|
|
||||||
expect(data, equals({'bar': 'baz', 'foo': 'poobaz'}));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
class Foo {
|
|
||||||
String? text;
|
|
||||||
|
|
||||||
String get foo => 'poo$text';
|
|
||||||
|
|
||||||
Foo({this.text});
|
|
||||||
|
|
||||||
factory Foo.fromJson(Map json) => Foo(text: json['bar'].toString());
|
|
||||||
|
|
||||||
Map toJson() => {'bar': text, 'foo': foo};
|
|
||||||
}
|
|
71
packages/merge_map/.gitignore
vendored
71
packages/merge_map/.gitignore
vendored
|
@ -1,71 +0,0 @@
|
||||||
# See https://www.dartlang.org/tools/private-files.html
|
|
||||||
|
|
||||||
# Files and directories created by pub
|
|
||||||
.dart_tool
|
|
||||||
.packages
|
|
||||||
.pub/
|
|
||||||
build/
|
|
||||||
|
|
||||||
# If you're building an application, you may want to check-in your pubspec.lock
|
|
||||||
pubspec.lock
|
|
||||||
|
|
||||||
# Directory created by dartdoc
|
|
||||||
# If you don't generate documentation locally you can remove this line.
|
|
||||||
doc/api/
|
|
||||||
|
|
||||||
### Dart template
|
|
||||||
# See https://www.dartlang.org/tools/private-files.html
|
|
||||||
|
|
||||||
# Files and directories created by pub
|
|
||||||
|
|
||||||
# SDK 1.20 and later (no longer creates packages directories)
|
|
||||||
|
|
||||||
# Older SDK versions
|
|
||||||
# (Include if the minimum SDK version specified in pubsepc.yaml is earlier than 1.20)
|
|
||||||
.project
|
|
||||||
.buildlog
|
|
||||||
**/packages/
|
|
||||||
|
|
||||||
|
|
||||||
# Files created by dart2js
|
|
||||||
# (Most Dart developers will use pub build to compile Dart, use/modify these
|
|
||||||
# rules if you intend to use dart2js directly
|
|
||||||
# Convention is to use extension '.dart.js' for Dart compiled to Javascript to
|
|
||||||
# differentiate from explicit Javascript files)
|
|
||||||
*.dart.js
|
|
||||||
*.part.js
|
|
||||||
*.js.deps
|
|
||||||
*.js.map
|
|
||||||
*.info.json
|
|
||||||
|
|
||||||
# Directory created by dartdoc
|
|
||||||
|
|
||||||
# Don't commit pubspec lock file
|
|
||||||
# (Library packages only! Remove pattern if developing an application package)
|
|
||||||
### JetBrains template
|
|
||||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
|
|
||||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
|
||||||
|
|
||||||
# User-specific stuff:
|
|
||||||
|
|
||||||
## VsCode
|
|
||||||
.vscode/
|
|
||||||
|
|
||||||
## File-based project format:
|
|
||||||
*.iws
|
|
||||||
|
|
||||||
## Plugin-specific files:
|
|
||||||
|
|
||||||
# IntelliJ
|
|
||||||
.idea/
|
|
||||||
/out/
|
|
||||||
.idea_modules/
|
|
||||||
|
|
||||||
# JIRA plugin
|
|
||||||
atlassian-ide-plugin.xml
|
|
||||||
|
|
||||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
|
||||||
com_crashlytics_export_strings.xml
|
|
||||||
crashlytics.properties
|
|
||||||
crashlytics-build.properties
|
|
||||||
fabric.properties
|
|
|
@ -1,12 +0,0 @@
|
||||||
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.
|
|
|
@ -1,16 +0,0 @@
|
||||||
# 2.0.2
|
|
||||||
* Resolve static analysis warnings
|
|
||||||
|
|
||||||
# 2.0.1
|
|
||||||
* Updated README
|
|
||||||
|
|
||||||
# 2.0.0
|
|
||||||
* Migrated to work with Dart SDK 2.12.x NNBD
|
|
||||||
|
|
||||||
# 1.0.2
|
|
||||||
* Add an example, for Pub's sake.
|
|
||||||
|
|
||||||
# 1.0.1
|
|
||||||
* Add a specific constraint on Dart versions, to prevent Pub from rejecting all packages that depend on
|
|
||||||
`merge_map` (the entire Angel framework).
|
|
||||||
* Add generic type support
|
|
|
@ -1,21 +0,0 @@
|
||||||
MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2021 dukefirehawk.com
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
|
@ -1,28 +0,0 @@
|
||||||
# angel3_merge_map
|
|
||||||
|
|
||||||
[![version](https://img.shields.io/badge/pub-v2.0.2-brightgreen)](https://pub.dartlang.org/packages/angel3_merge_map)
|
|
||||||
[![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety)
|
|
||||||
[![Gitter](https://img.shields.io/gitter/room/angel_dart/discussion)](https://gitter.im/angel_dart/discussion)
|
|
||||||
|
|
||||||
[![License](https://img.shields.io/github/license/dukefirehawk/angel)](https://github.com/dukefirehawk/angel/tree/angel3/packages/merge_map/LICENSE)
|
|
||||||
|
|
||||||
**DEPRECATED: Replaced by [`belatuk_merge_map`](https://pub.dartlang.org/packages/belatuk_merge_map) package**
|
|
||||||
|
|
||||||
Combine multiple Maps into one. Equivalent to
|
|
||||||
[Object.assign](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign)
|
|
||||||
in JS.
|
|
||||||
|
|
||||||
# Example
|
|
||||||
|
|
||||||
```dart
|
|
||||||
import "package:angel3_merge_map/angel3_merge_map.dart";
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
Map map1 = {'hello': 'world'};
|
|
||||||
Map map2 = {'foo': {'bar': 'baz', 'this': 'will be overwritten'}};
|
|
||||||
Map map3 = {'foo': {'john': 'doe', 'this': 'overrides previous maps'}};
|
|
||||||
Map merged = mergeMap(map1, map2, map3);
|
|
||||||
|
|
||||||
// {hello: world, foo: {bar: baz, john: doe, this: overrides previous maps}}
|
|
||||||
}
|
|
||||||
```
|
|
|
@ -1,4 +0,0 @@
|
||||||
include: package:pedantic/analysis_options.yaml
|
|
||||||
analyzer:
|
|
||||||
strong-mode:
|
|
||||||
implicit-casts: false
|
|
|
@ -1,20 +0,0 @@
|
||||||
import 'package:angel3_merge_map/angel3_merge_map.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
// ignore: omit_local_variable_types
|
|
||||||
Map map1 = {'hello': 'world'};
|
|
||||||
|
|
||||||
// ignore: omit_local_variable_types
|
|
||||||
Map map2 = {
|
|
||||||
'foo': {'bar': 'baz', 'this': 'will be overwritten'}
|
|
||||||
};
|
|
||||||
|
|
||||||
// ignore: omit_local_variable_types
|
|
||||||
Map map3 = {
|
|
||||||
'foo': {'john': 'doe', 'this': 'overrides previous maps'}
|
|
||||||
};
|
|
||||||
var merged = mergeMap([map1, map2, map3]);
|
|
||||||
print(merged);
|
|
||||||
|
|
||||||
// {hello: world, foo: {bar: baz, john: doe, this: overrides previous maps}}
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
/// Exposes the [mergeMap] function, which... merges Maps.
|
|
||||||
library angel3_merge_map;
|
|
||||||
|
|
||||||
dynamic _copyValues<K, V>(
|
|
||||||
Map<K, V> from, Map<K, V?>? to, bool recursive, bool acceptNull) {
|
|
||||||
for (var key in from.keys) {
|
|
||||||
if (from[key] is Map<K, V> && recursive) {
|
|
||||||
if (!(to![key] is Map<K, V>)) {
|
|
||||||
to[key] = <K, V>{} as V;
|
|
||||||
}
|
|
||||||
_copyValues(from[key] as Map, to[key] as Map?, recursive, acceptNull);
|
|
||||||
} else {
|
|
||||||
if (from[key] != null || acceptNull) to![key] = from[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Merges the values of the given maps together.
|
|
||||||
///
|
|
||||||
/// `recursive` is set to `true` by default. If set to `true`,
|
|
||||||
/// then nested maps will also be merged. Otherwise, nested maps
|
|
||||||
/// will overwrite others.
|
|
||||||
///
|
|
||||||
/// `acceptNull` is set to `false` by default. If set to `false`,
|
|
||||||
/// then if the value on a map is `null`, it will be ignored, and
|
|
||||||
/// that `null` will not be copied.
|
|
||||||
Map<K, V> mergeMap<K, V>(Iterable<Map<K, V>> maps,
|
|
||||||
{bool recursive = true, bool acceptNull = false}) {
|
|
||||||
var result = <K, V>{};
|
|
||||||
maps.forEach((Map<K, V> map) {
|
|
||||||
_copyValues(map, result, recursive, acceptNull);
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
name: angel3_merge_map
|
|
||||||
version: 2.0.2
|
|
||||||
description: Combine multiple Maps into one. Equivalent to Object.assign in JS.
|
|
||||||
homepage: https://github.com/dukefirehawk/angel/tree/angel3/packages/merge_map
|
|
||||||
environment:
|
|
||||||
sdk: '>=2.12.0 <3.0.0'
|
|
||||||
dev_dependencies:
|
|
||||||
test: ^1.17.4
|
|
||||||
pedantic: ^1.11.0
|
|
|
@ -1,104 +0,0 @@
|
||||||
import 'package:angel3_merge_map/angel3_merge_map.dart';
|
|
||||||
import 'package:test/test.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
test('can merge two simple maps', () {
|
|
||||||
var merged = mergeMap([
|
|
||||||
{'hello': 'world'},
|
|
||||||
{'hello': 'dolly'}
|
|
||||||
]);
|
|
||||||
expect(merged['hello'], equals('dolly'));
|
|
||||||
});
|
|
||||||
|
|
||||||
test("the last map's values supersede those of prior", () {
|
|
||||||
var merged = mergeMap([
|
|
||||||
{'letter': 'a'},
|
|
||||||
{'letter': 'b'},
|
|
||||||
{'letter': 'c'}
|
|
||||||
]);
|
|
||||||
expect(merged['letter'], equals('c'));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('can merge two once-nested maps', () {
|
|
||||||
// ignore: omit_local_variable_types
|
|
||||||
Map map1 = {
|
|
||||||
'hello': 'world',
|
|
||||||
'foo': {'nested': false}
|
|
||||||
};
|
|
||||||
// ignore: omit_local_variable_types
|
|
||||||
Map map2 = {
|
|
||||||
'goodbye': 'sad life',
|
|
||||||
'foo': {'nested': true, 'it': 'works'}
|
|
||||||
};
|
|
||||||
var merged = mergeMap([map1, map2]);
|
|
||||||
|
|
||||||
expect(merged['hello'], equals('world'));
|
|
||||||
expect(merged['goodbye'], equals('sad life'));
|
|
||||||
expect(merged['foo']['nested'], equals(true));
|
|
||||||
expect(merged['foo']['it'], equals('works'));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('once-nested map supersession', () {
|
|
||||||
// ignore: omit_local_variable_types
|
|
||||||
Map map1 = {
|
|
||||||
'hello': 'world',
|
|
||||||
'foo': {'nested': false}
|
|
||||||
};
|
|
||||||
// ignore: omit_local_variable_types
|
|
||||||
Map map2 = {
|
|
||||||
'goodbye': 'sad life',
|
|
||||||
'foo': {'nested': true, 'it': 'works'}
|
|
||||||
};
|
|
||||||
// ignore: omit_local_variable_types
|
|
||||||
Map map3 = {
|
|
||||||
'foo': {'nested': 'supersession'}
|
|
||||||
};
|
|
||||||
|
|
||||||
var merged = mergeMap([map1, map2, map3]);
|
|
||||||
expect(merged['foo']['nested'], equals('supersession'));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('can merge two twice-nested maps', () {
|
|
||||||
// ignore: omit_local_variable_types
|
|
||||||
Map map1 = {
|
|
||||||
'a': {
|
|
||||||
'b': {'c': 'd'}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// ignore: omit_local_variable_types
|
|
||||||
Map map2 = {
|
|
||||||
'a': {
|
|
||||||
'b': {'c': 'D', 'e': 'f'}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
var merged = mergeMap([map1, map2]);
|
|
||||||
|
|
||||||
expect(merged['a']['b']['c'], equals('D'));
|
|
||||||
expect(merged['a']['b']['e'], equals('f'));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('twice-nested map supersession', () {
|
|
||||||
// ignore: omit_local_variable_types
|
|
||||||
Map map1 = {
|
|
||||||
'a': {
|
|
||||||
'b': {'c': 'd'}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// ignore: omit_local_variable_types
|
|
||||||
Map map2 = {
|
|
||||||
'a': {
|
|
||||||
'b': {'c': 'D', 'e': 'f'}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// ignore: omit_local_variable_types
|
|
||||||
Map map3 = {
|
|
||||||
'a': {
|
|
||||||
'b': {'e': 'supersession'}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
var merged = mergeMap([map1, map2, map3]);
|
|
||||||
|
|
||||||
expect(merged['a']['b']['c'], equals('D'));
|
|
||||||
expect(merged['a']['b']['e'], equals('supersession'));
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -82,7 +82,6 @@ class EmployeeQuery extends Query<Employee, EmployeeQueryWhere> {
|
||||||
if (row.every((x) => x == null)) return null;
|
if (row.every((x) => x == null)) return null;
|
||||||
var model = Employee(
|
var model = Employee(
|
||||||
id: row[0].toString(),
|
id: row[0].toString(),
|
||||||
error: (row[1] as String?),
|
|
||||||
createdAt: (row[2] as DateTime?),
|
createdAt: (row[2] as DateTime?),
|
||||||
updatedAt: (row[3] as DateTime?),
|
updatedAt: (row[3] as DateTime?),
|
||||||
uniqueId: (row[4] as String?),
|
uniqueId: (row[4] as String?),
|
||||||
|
@ -187,7 +186,6 @@ class EmployeeQueryValues extends MapQueryValues {
|
||||||
|
|
||||||
set salary(double? value) => values['salary'] = value.toString();
|
set salary(double? value) => values['salary'] = value.toString();
|
||||||
void copyFrom(Employee model) {
|
void copyFrom(Employee model) {
|
||||||
error = model.error;
|
|
||||||
createdAt = model.createdAt;
|
createdAt = model.createdAt;
|
||||||
updatedAt = model.updatedAt;
|
updatedAt = model.updatedAt;
|
||||||
uniqueId = model.uniqueId;
|
uniqueId = model.uniqueId;
|
||||||
|
@ -205,7 +203,6 @@ class EmployeeQueryValues extends MapQueryValues {
|
||||||
class Employee extends _Employee {
|
class Employee extends _Employee {
|
||||||
Employee(
|
Employee(
|
||||||
{this.id,
|
{this.id,
|
||||||
this.error,
|
|
||||||
this.createdAt,
|
this.createdAt,
|
||||||
this.updatedAt,
|
this.updatedAt,
|
||||||
this.uniqueId,
|
this.uniqueId,
|
||||||
|
@ -217,9 +214,6 @@ class Employee extends _Employee {
|
||||||
@override
|
@override
|
||||||
String? id;
|
String? id;
|
||||||
|
|
||||||
@override
|
|
||||||
String? error;
|
|
||||||
|
|
||||||
/// The time at which this item was created.
|
/// The time at which this item was created.
|
||||||
@override
|
@override
|
||||||
DateTime? createdAt;
|
DateTime? createdAt;
|
||||||
|
@ -251,7 +245,6 @@ class Employee extends _Employee {
|
||||||
double? salary}) {
|
double? salary}) {
|
||||||
return Employee(
|
return Employee(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
error: error ?? this.error,
|
|
||||||
createdAt: createdAt ?? this.createdAt,
|
createdAt: createdAt ?? this.createdAt,
|
||||||
updatedAt: updatedAt ?? this.updatedAt,
|
updatedAt: updatedAt ?? this.updatedAt,
|
||||||
uniqueId: uniqueId ?? this.uniqueId,
|
uniqueId: uniqueId ?? this.uniqueId,
|
||||||
|
@ -264,7 +257,6 @@ class Employee extends _Employee {
|
||||||
bool operator ==(other) {
|
bool operator ==(other) {
|
||||||
return other is _Employee &&
|
return other is _Employee &&
|
||||||
other.id == id &&
|
other.id == id &&
|
||||||
other.error == error &&
|
|
||||||
other.createdAt == createdAt &&
|
other.createdAt == createdAt &&
|
||||||
other.updatedAt == updatedAt &&
|
other.updatedAt == updatedAt &&
|
||||||
other.uniqueId == uniqueId &&
|
other.uniqueId == uniqueId &&
|
||||||
|
@ -275,21 +267,13 @@ class Employee extends _Employee {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode {
|
int get hashCode {
|
||||||
return hashObjects([
|
return hashObjects(
|
||||||
id,
|
[id, createdAt, updatedAt, uniqueId, firstName, lastName, salary]);
|
||||||
error,
|
|
||||||
createdAt,
|
|
||||||
updatedAt,
|
|
||||||
uniqueId,
|
|
||||||
firstName,
|
|
||||||
lastName,
|
|
||||||
salary
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'Employee(id=$id, error=$error, createdAt=$createdAt, updatedAt=$updatedAt, uniqueId=$uniqueId, firstName=$firstName, lastName=$lastName, salary=$salary)';
|
return 'Employee(id=$id, createdAt=$createdAt, updatedAt=$updatedAt, uniqueId=$uniqueId, firstName=$firstName, lastName=$lastName, salary=$salary)';
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
|
@ -327,7 +311,6 @@ class EmployeeSerializer extends Codec<Employee, Map> {
|
||||||
static Employee fromMap(Map map) {
|
static Employee fromMap(Map map) {
|
||||||
return Employee(
|
return Employee(
|
||||||
id: map['id'] as String,
|
id: map['id'] as String,
|
||||||
error: map['error'] as String,
|
|
||||||
createdAt: map['created_at'] != null
|
createdAt: map['created_at'] != null
|
||||||
? (map['created_at'] is DateTime
|
? (map['created_at'] is DateTime
|
||||||
? (map['created_at'] as DateTime)
|
? (map['created_at'] as DateTime)
|
||||||
|
@ -347,7 +330,6 @@ class EmployeeSerializer extends Codec<Employee, Map> {
|
||||||
static Map<String, dynamic> toMap(_Employee model) {
|
static Map<String, dynamic> toMap(_Employee model) {
|
||||||
return {
|
return {
|
||||||
'id': model.id,
|
'id': model.id,
|
||||||
'error': model.error,
|
|
||||||
'created_at': model.createdAt?.toIso8601String(),
|
'created_at': model.createdAt?.toIso8601String(),
|
||||||
'updated_at': model.updatedAt?.toIso8601String(),
|
'updated_at': model.updatedAt?.toIso8601String(),
|
||||||
'unique_id': model.uniqueId,
|
'unique_id': model.uniqueId,
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
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.
|
|
|
@ -1,16 +0,0 @@
|
||||||
# 3.0.3
|
|
||||||
* Updated README
|
|
||||||
|
|
||||||
# 3.0.2
|
|
||||||
* Updated README
|
|
||||||
|
|
||||||
# 3.0.1
|
|
||||||
* Fixed invalid homepage url in pubspec.yaml
|
|
||||||
# 3.0.0
|
|
||||||
* Migrated to support Dart SDK 2.12.x NNBD
|
|
||||||
|
|
||||||
# 2.0.0
|
|
||||||
* Migrated to work with Dart SDK 2.12.x Non NNBD
|
|
||||||
|
|
||||||
# 1.0.0
|
|
||||||
* Initial release.
|
|
|
@ -1,21 +0,0 @@
|
||||||
MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2021 dukefirehawk.com
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
|
@ -1,42 +0,0 @@
|
||||||
# Angel3 Petty Logging
|
|
||||||
|
|
||||||
[![version](https://img.shields.io/badge/pub-v3.0.3-brightgreen)](https://pub.dartlang.org/packages/angel3_pretty_logging)
|
|
||||||
[![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety)
|
|
||||||
[![Gitter](https://img.shields.io/gitter/room/angel_dart/discussion)](https://gitter.im/angel_dart/discussion)
|
|
||||||
|
|
||||||
[![License](https://img.shields.io/github/license/dukefirehawk/angel)](https://github.com/dukefirehawk/angel/tree/angel3/packages/pretty_logging/LICENSE)
|
|
||||||
|
|
||||||
**DEPRECATED: Replaced by [`belatuk_pretty_logging`](https://pub.dartlang.org/packages/belatuk_pretty_logging) package**
|
|
||||||
|
|
||||||
Standalone helper for colorful logging output, using pkg:io AnsiCode.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
In your `pubspec.yaml`:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
dependencies:
|
|
||||||
angel3_pretty_logging: ^3.0.0
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
Basic usage is very simple:
|
|
||||||
|
|
||||||
```dart
|
|
||||||
myLogger.onRecord.listen(prettyLog);
|
|
||||||
```
|
|
||||||
|
|
||||||
However, you can conditionally pass logic to omit printing an error, provide colors, or to provide a custom print function:
|
|
||||||
|
|
||||||
```dart
|
|
||||||
var pretty = prettyLog(
|
|
||||||
logColorChooser: (_) => red,
|
|
||||||
printFunction: stderr.writeln,
|
|
||||||
omitError: (r) {
|
|
||||||
var err = r.error;
|
|
||||||
return err is AngelHttpException && err.statusCode != 500;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
myLogger.onRecord.listen(pretty);
|
|
||||||
```
|
|
|
@ -1,4 +0,0 @@
|
||||||
include: package:pedantic/analysis_options.yaml
|
|
||||||
analyzer:
|
|
||||||
strong-mode:
|
|
||||||
implicit-casts: false
|
|
|
@ -1,11 +0,0 @@
|
||||||
import 'package:logging/logging.dart';
|
|
||||||
import 'package:angel3_pretty_logging/angel3_pretty_logging.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
Logger.root
|
|
||||||
..level = Level.ALL
|
|
||||||
..onRecord.listen(prettyLog)
|
|
||||||
..info('Hey!')
|
|
||||||
..finest('Bye!')
|
|
||||||
..severe('Oops!', StateError('Wrong!'), StackTrace.current);
|
|
||||||
}
|
|
|
@ -1,50 +0,0 @@
|
||||||
import 'package:logging/logging.dart';
|
|
||||||
import 'package:io/ansi.dart';
|
|
||||||
|
|
||||||
/// Prints the contents of a [LogRecord] with pretty colors.
|
|
||||||
///
|
|
||||||
/// By passing [omitError], you can omit printing the error of a given
|
|
||||||
/// [LogRecord].
|
|
||||||
///
|
|
||||||
/// You can also pass a custom [printFunction] or [logColorChooser].
|
|
||||||
void prettyLog(LogRecord record,
|
|
||||||
{bool Function(LogRecord)? omitError,
|
|
||||||
void Function(String)? printFunction,
|
|
||||||
AnsiCode Function(Level)? logColorChooser}) {
|
|
||||||
logColorChooser ??= chooseLogColor;
|
|
||||||
omitError ??= (_) => false;
|
|
||||||
printFunction ??= print;
|
|
||||||
|
|
||||||
var code = logColorChooser(record.level);
|
|
||||||
if (record.error == null) printFunction(code.wrap(record.toString())!);
|
|
||||||
|
|
||||||
if (record.error != null) {
|
|
||||||
var err = record.error;
|
|
||||||
if (omitError(record)) return;
|
|
||||||
printFunction(code.wrap(record.toString() + '\n')!);
|
|
||||||
printFunction(code.wrap(err.toString())!);
|
|
||||||
|
|
||||||
if (record.stackTrace != null) {
|
|
||||||
printFunction(code.wrap(record.stackTrace.toString())!);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Chooses a color based on the logger [level].
|
|
||||||
AnsiCode chooseLogColor(Level level) {
|
|
||||||
if (level == Level.SHOUT) {
|
|
||||||
return backgroundRed;
|
|
||||||
} else if (level == Level.SEVERE) {
|
|
||||||
return red;
|
|
||||||
} else if (level == Level.WARNING) {
|
|
||||||
return yellow;
|
|
||||||
} else if (level == Level.INFO) {
|
|
||||||
return cyan;
|
|
||||||
} else if (level == Level.CONFIG ||
|
|
||||||
level == Level.FINE ||
|
|
||||||
level == Level.FINER ||
|
|
||||||
level == Level.FINEST) {
|
|
||||||
return lightGray;
|
|
||||||
}
|
|
||||||
return resetAll;
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
name: angel3_pretty_logging
|
|
||||||
version: 3.0.3
|
|
||||||
description: Standalone helper for colorful logging output, using pkg:io AnsiCode.
|
|
||||||
homepage: https://github.com/dukefirehawk/angel/tree/angel3/packages/pretty_logging
|
|
||||||
environment:
|
|
||||||
sdk: '>=2.12.0 <3.0.0'
|
|
||||||
dependencies:
|
|
||||||
io: ^1.0.0
|
|
||||||
logging: ^1.0.1
|
|
||||||
dev_dependencies:
|
|
||||||
pedantic: ^1.11.0
|
|
|
@ -1 +0,0 @@
|
||||||
language: dart
|
|
|
@ -1,12 +0,0 @@
|
||||||
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.
|
|
|
@ -1,34 +0,0 @@
|
||||||
# Change Log
|
|
||||||
|
|
||||||
## 3.0.3
|
|
||||||
|
|
||||||
* Final release. Replaced by `belatuk_pub_sub` package.
|
|
||||||
|
|
||||||
## 3.0.2
|
|
||||||
|
|
||||||
* Resolved static analysis warnings
|
|
||||||
|
|
||||||
## 3.0.1
|
|
||||||
|
|
||||||
* Resolved static analysis warnings
|
|
||||||
|
|
||||||
## 3.0.0
|
|
||||||
|
|
||||||
* Migrated to work with Dart SDK 2.12.x NNBD
|
|
||||||
|
|
||||||
## 2.3.0
|
|
||||||
|
|
||||||
* Allow `2.x` versions of `stream_channel`.
|
|
||||||
* Apply `package:pedantic` lints.
|
|
||||||
|
|
||||||
## 2.2.0
|
|
||||||
|
|
||||||
* Upgrade `uuid`.
|
|
||||||
|
|
||||||
## 2.1.0
|
|
||||||
|
|
||||||
* Allow for "trusted clients," which are implicitly-registered clients. This makes using `package:pub_sub` easier, as well making it easier to scale.
|
|
||||||
|
|
||||||
## 2.0.0
|
|
||||||
|
|
||||||
* Dart 2 updates.
|
|
|
@ -1,21 +0,0 @@
|
||||||
MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2021 dukefirehawk.com
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
|
@ -1,234 +0,0 @@
|
||||||
# Angel3 Pub Sub
|
|
||||||
|
|
||||||
[![version](https://img.shields.io/badge/pub-v3.0.3-brightgreen)](https://pub.dartlang.org/packages/angel3_pub_sub)
|
|
||||||
[![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety)
|
|
||||||
[![Gitter](https://img.shields.io/gitter/room/angel_dart/discussion)](https://gitter.im/angel_dart/discussion)
|
|
||||||
|
|
||||||
[![License](https://img.shields.io/github/license/dukefirehawk/angel)](https://github.com/dukefirehawk/angel/tree/angel3/packages/pub_sub/LICENSE)
|
|
||||||
|
|
||||||
**DEPRECATED: Replaced by [`belatuk_pub_sub`](https://pub.dartlang.org/packages/belatuk_pub_sub) package**
|
|
||||||
|
|
||||||
Keep application instances in sync with a simple pub/sub API.
|
|
||||||
|
|
||||||
# Installation
|
|
||||||
|
|
||||||
Add `angel3_pub_sub` as a dependency in your `pubspec.yaml` file:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
dependencies:
|
|
||||||
angel3_pub_sub: ^3.0.0
|
|
||||||
```
|
|
||||||
|
|
||||||
Then, be sure to run `pub get` in your terminal.
|
|
||||||
|
|
||||||
# Usage
|
|
||||||
|
|
||||||
`pub_sub` is your typical pub/sub API. However, `angel3_pub_sub` enforces authentication of every
|
|
||||||
request. It is very possible that `angel3_pub_sub` will run on both servers and in the browser,
|
|
||||||
or on a platform angel3_pub_sublike Flutter. Thus, there are provisions available to limit
|
|
||||||
access.
|
|
||||||
|
|
||||||
**Be careful to not leak any `angel3_pub_sub` client ID's if operating over a network.**
|
|
||||||
If you do, you risk malicious users injecting events into your application, which
|
|
||||||
could ultimately spell *disaster*.
|
|
||||||
|
|
||||||
A `angel3_pub_sub` server can operate across multiple *adapters*, which take care of interfacing data over different
|
|
||||||
media. For example, a single server can handle pub/sub between multiple Isolates and TCP Sockets, as well as
|
|
||||||
WebSockets, simultaneously.
|
|
||||||
|
|
||||||
```dart
|
|
||||||
import 'package:angel3_pub_sub/angel3_pub_sub.dart' as pub_sub;
|
|
||||||
|
|
||||||
main() async {
|
|
||||||
var server = pub_sub.Server([
|
|
||||||
FooAdapter(...),
|
|
||||||
BarAdapter(...)
|
|
||||||
]);
|
|
||||||
|
|
||||||
server.addAdapter( BazAdapter(...));
|
|
||||||
|
|
||||||
// Call `start` to activate adapters, and begin handling requests.
|
|
||||||
server.start();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Trusted Clients
|
|
||||||
|
|
||||||
You can use `package:angel3_pub_sub` without explicitly registering
|
|
||||||
clients, *if and only if* those clients come from trusted sources.
|
|
||||||
|
|
||||||
Clients via `Isolate` are always trusted.
|
|
||||||
|
|
||||||
Clients via `package:json_rpc_2` must be explicitly marked
|
|
||||||
as trusted (i.e. using an IP whitelist mechanism):
|
|
||||||
|
|
||||||
```dart
|
|
||||||
JsonRpc2Adapter(..., isTrusted: false);
|
|
||||||
|
|
||||||
// Pass `null` as Client ID when trusted...
|
|
||||||
pub_sub.IsolateClient(null);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Access Control
|
|
||||||
|
|
||||||
The ID's of all *untrusted* clients who will connect to the server must be known at start-up time.
|
|
||||||
You may not register new clients after the server has started. This is mostly a security consideration;
|
|
||||||
if it is impossible to register new clients, then malicious users cannot grant themselves additional
|
|
||||||
privileges within the system.
|
|
||||||
|
|
||||||
```dart
|
|
||||||
import 'package:angel3_pub_sub/angel3_pub_sub.dart' as pub_sub;
|
|
||||||
|
|
||||||
main() async {
|
|
||||||
// ...
|
|
||||||
server.registerClient(const ClientInfo('<client-id>'));
|
|
||||||
|
|
||||||
// Create a user who can subscribe, but not publish.
|
|
||||||
server.registerClient(const ClientInfo('<client-id>', canPublish: false));
|
|
||||||
|
|
||||||
// Create a user who can publish, but not subscribe.
|
|
||||||
server.registerClient(const ClientInfo('<client-id>', canSubscribe: false));
|
|
||||||
|
|
||||||
// Create a user with no privileges whatsoever.
|
|
||||||
server.registerClient(const ClientInfo('<client-id>', canPublish: false, canSubscribe: false));
|
|
||||||
|
|
||||||
server.start();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Isolates
|
|
||||||
|
|
||||||
If you are just running multiple instances of a server,
|
|
||||||
use `package:angel3_pub_sub/isolate.dart`.
|
|
||||||
|
|
||||||
You'll need one isolate to be the master. Typically this is the first isolate you create.
|
|
||||||
|
|
||||||
```dart
|
|
||||||
import 'dart:io';
|
|
||||||
import 'dart:isolate';
|
|
||||||
import 'package:angel3_pub_sub/isolate.dart' as pub_sub;
|
|
||||||
import 'package:angel3_pub_sub/angel3_pub_sub.dart' as pub_sub;
|
|
||||||
|
|
||||||
void main() async {
|
|
||||||
// Easily bring up a server.
|
|
||||||
var adapter = pub_sub.IsolateAdapter();
|
|
||||||
var server = pub_sub.Server([adapter]);
|
|
||||||
|
|
||||||
// You then need to create a client that will connect to the adapter.
|
|
||||||
// Each isolate in your application should contain a client.
|
|
||||||
for (int i = 0; i < Platform.numberOfProcessors - 1; i++) {
|
|
||||||
server.registerClient(pub_sub.ClientInfo('client$i'));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start the server.
|
|
||||||
server.start();
|
|
||||||
|
|
||||||
// Next, let's start isolates that interact with the server.
|
|
||||||
//
|
|
||||||
// Fortunately, we can send SendPorts over Isolates, so this is no hassle.
|
|
||||||
for (int i = 0; i < Platform.numberOfProcessors - 1; i++)
|
|
||||||
Isolate.spawn(isolateMain, [i, adapter.receivePort.sendPort]);
|
|
||||||
|
|
||||||
// It's possible that you're running your application in the server isolate as well:
|
|
||||||
isolateMain([0, adapter.receivePort.sendPort]);
|
|
||||||
}
|
|
||||||
|
|
||||||
void isolateMain(List args) {
|
|
||||||
var client =
|
|
||||||
pub_sub.IsolateClient('client${args[0]}', args[1] as SendPort);
|
|
||||||
|
|
||||||
// The client will connect automatically. In the meantime, we can start subscribing to events.
|
|
||||||
client.subscribe('user::logged_in').then((sub) {
|
|
||||||
// The `ClientSubscription` class extends `Stream`. Hooray for asynchrony!
|
|
||||||
sub.listen((msg) {
|
|
||||||
print('Logged in: $msg');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
## JSON RPC 2.0
|
|
||||||
|
|
||||||
If you are not running on isolates, you need to import
|
|
||||||
`package:angel3_pub_sub/json_rpc_2.dart`. This library leverages `package:json_rpc_2` and
|
|
||||||
`package:stream_channel` to create clients and servers that can hypothetically run on any
|
|
||||||
medium, i.e. WebSockets, or TCP Sockets.
|
|
||||||
|
|
||||||
Check out `test/json_rpc_2_test.dart` for an example of serving `angel3_pub_sub` over TCP sockets.
|
|
||||||
|
|
||||||
# Protocol
|
|
||||||
|
|
||||||
`angel3_pub_sub` is built upon a simple RPC, and this package includes
|
|
||||||
an implementation that runs via `SendPort`s and `ReceivePort`s, as
|
|
||||||
well as one that runs on any `StreamChannel<String>`.
|
|
||||||
|
|
||||||
Data sent over the wire looks like the following:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Sent by a client to initiate an exchange.
|
|
||||||
interface Request {
|
|
||||||
// This is an arbitrary string, assigned by your client, but in every case,
|
|
||||||
// the client uses this to match your requests with asynchronous responses.
|
|
||||||
request_id: string,
|
|
||||||
|
|
||||||
// The ID of the client to authenticate as.
|
|
||||||
//
|
|
||||||
// As you can imagine, this should be kept secret, to prevent breaches.
|
|
||||||
client_id: string,
|
|
||||||
|
|
||||||
// Required for *every* request.
|
|
||||||
params: {
|
|
||||||
// A value to be `publish`ed.
|
|
||||||
value?: any,
|
|
||||||
|
|
||||||
// The name of an event to `publish`.
|
|
||||||
event_name?: string,
|
|
||||||
|
|
||||||
// The ID of a subscription to be cancelled.
|
|
||||||
subscription_id?: string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sent by the server in response to a request.
|
|
||||||
interface Response {
|
|
||||||
// `true` for success, `false` for failures.
|
|
||||||
status: boolean,
|
|
||||||
|
|
||||||
// Only appears if `status` is `false`; explains why an operation failed.
|
|
||||||
error_message?: string,
|
|
||||||
|
|
||||||
// Matches the request_id sent by the client.
|
|
||||||
request_id: string,
|
|
||||||
|
|
||||||
result?: {
|
|
||||||
// The number of other clients to whom an event was `publish`ed.
|
|
||||||
listeners:? number,
|
|
||||||
|
|
||||||
// The ID of a created subscription.
|
|
||||||
subscription_id?: string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
When sending via JSON_RPC 2.0, the `params` of a `Request` are simply folded into the object
|
|
||||||
itself, for simplicity's sake. In this case, a response will be sent as a notification whose
|
|
||||||
name is the `request_id`.
|
|
||||||
|
|
||||||
In the case of Isolate clients/servers, events will be simply sent as Lists:
|
|
||||||
|
|
||||||
```dart
|
|
||||||
['<event-name>', value]
|
|
||||||
```
|
|
||||||
|
|
||||||
Clients can send the following (3) methods:
|
|
||||||
|
|
||||||
* `subscribe` (`event_name`:string): Subscribe to an event.
|
|
||||||
* `unsubscribe` (`subscription_id`:string): Unsubscribe from an event you previously subscribed to.
|
|
||||||
* `publish` (`event_name`:string, `value`:any): Publish an event to all other clients who are subscribed.
|
|
||||||
|
|
||||||
The client and server in `package:angel3_pub_sub/isolate.dart` must make extra
|
|
||||||
provisions to keep track of client ID's. Since `SendPort`s and `ReceivePort`s
|
|
||||||
do not have any sort of guaranteed-unique ID's, new clients must send their
|
|
||||||
`SendPort` to the server before sending any requests. The server then responds
|
|
||||||
with an `id` that must be used to identify a `SendPort` to send a response to.
|
|
|
@ -1,4 +0,0 @@
|
||||||
include: package:pedantic/analysis_options.yaml
|
|
||||||
analyzer:
|
|
||||||
strong-mode:
|
|
||||||
implicit-casts: false
|
|
|
@ -1,44 +0,0 @@
|
||||||
import 'dart:io';
|
|
||||||
import 'dart:isolate';
|
|
||||||
import 'package:angel3_pub_sub/isolate.dart' as pub_sub;
|
|
||||||
import 'package:angel3_pub_sub/angel3_pub_sub.dart' as pub_sub;
|
|
||||||
|
|
||||||
void main() async {
|
|
||||||
// Easily bring up a server.
|
|
||||||
var adapter = pub_sub.IsolateAdapter();
|
|
||||||
var server = pub_sub.Server([adapter]);
|
|
||||||
|
|
||||||
// You then need to create a client that will connect to the adapter.
|
|
||||||
// Every untrusted client in your application should be pre-registered.
|
|
||||||
//
|
|
||||||
// In the case of Isolates, however, those are always implicitly trusted.
|
|
||||||
for (var i = 0; i < Platform.numberOfProcessors - 1; i++) {
|
|
||||||
server.registerClient(pub_sub.ClientInfo('client$i'));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start the server.
|
|
||||||
server.start();
|
|
||||||
|
|
||||||
// Next, let's start isolates that interact with the server.
|
|
||||||
//
|
|
||||||
// Fortunately, we can send SendPorts over Isolates, so this is no hassle.
|
|
||||||
for (var i = 0; i < Platform.numberOfProcessors - 1; i++) {
|
|
||||||
await Isolate.spawn(isolateMain, [i, adapter.receivePort.sendPort]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// It's possible that you're running your application in the server isolate as well:
|
|
||||||
isolateMain([0, adapter.receivePort.sendPort]);
|
|
||||||
}
|
|
||||||
|
|
||||||
void isolateMain(List args) {
|
|
||||||
// Isolates are always trusted, so technically we don't need to pass a client iD.
|
|
||||||
var client = pub_sub.IsolateClient('client${args[0]}', args[1] as SendPort);
|
|
||||||
|
|
||||||
// The client will connect automatically. In the meantime, we can start subscribing to events.
|
|
||||||
client.subscribe('user::logged_in').then((sub) {
|
|
||||||
// The `ClientSubscription` class extends `Stream`. Hooray for asynchrony!
|
|
||||||
sub.listen((msg) {
|
|
||||||
print('Logged in: $msg');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
export 'src/protocol/protocol.dart';
|
|
|
@ -1,2 +0,0 @@
|
||||||
export 'src/isolate/client.dart';
|
|
||||||
export 'src/isolate/server.dart';
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue