Skip to content

Commit

Permalink
Merge pull request #7 from fujidaiti/pattern-matching
Browse files Browse the repository at this point in the history
  • Loading branch information
fujidaiti authored Jul 24, 2023
2 parents dd57839 + 2b68563 commit 7d440e9
Show file tree
Hide file tree
Showing 51 changed files with 1,355 additions and 221 deletions.
54 changes: 45 additions & 9 deletions embed/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,11 @@ flutter pub run build_runner clean

## Examples

You can find many more examples in the [embed/test/src](https://github.com/fujidaiti/embed.dart/tree/master/embed/test/generators) directory and [example/lib/example.dart](https://github.com/fujidaiti/embed.dart/blob/master/example/lib/example.dart).
You can find many more examples in the following resources:

- [embed/test/literal/literal_embedding_generator_test_src.dart](https://github.com/fujidaiti/embed.dart/tree/master/embed/test/literal/literal_embedding_generator_test_src.dart)
- [embed/test/str/str_embedding_generator_test_src.dart](https://github.com/fujidaiti/embed.dart/blob/master/embed/test/str/str_embedding_generator_test_src.dart)
- [example/lib/example.dart](https://github.com/fujidaiti/embed.dart/blob/master/example/lib/example.dart)

<br/>

Expand Down Expand Up @@ -238,7 +242,7 @@ If this doesn't work well with your text content, you can disable this behavior
@EmbedStr("useful_text.txt", raw: false)
```

This will generates a general string literal:
This will generates a regular string literal:

```dart
const _$usefulText = '''
Expand Down Expand Up @@ -284,6 +288,29 @@ You can see that the given JSON data is converted as a [record object](https://d

One more thing, when a [reserved keyword](https://dart.dev/language/keywords) like `if` is used as a JSON key, the code generator automatically adds a `$` sign at the beginning of the key; for example, in the above example, a JSON key `default` is converted to `$default` in the dart code.

#### Preprocessing

In the previous example, all JSON keys are converted to camelCase, and if any reserved Dart keywords are used as JSON keys, they are prefixed with a `$` sign to avoid syntax errors. This processing is done by [Preprocessor](https://pub.dev/documentation/embed_annotation/latest/embed_annotation/Preprocessor-class.html)s. You can specify preprocessors to be applied to the content in the constructor of [EmbedLiteral](https://pub.dev/documentation/embed_annotation/latest/embed_annotation/EmbedLiteral-class.html).

```dart
@EmbedLiteral(
"config.json",
preprocessors = [
Preprocessor.recase, // e.g. converts 'snake_case' to 'snakeCase'
Preprocessor.escapeReservedKeywords, // e.g. converts 'if' to '$if'
Preprocessor.replace("#", "0x"), // e.g. converts "#fff" to "0xfff"
],
)
const config = _$config;
```

These preprocessors are applied recursively to all elements in the content, in the order specified. By default, [Recase](https://pub.dev/documentation/embed_annotation/latest/embed_annotation/Recase-class.html) and [EscapeReservedKeywords](https://pub.dev/documentation/embed_annotation/latest/embed_annotation/EscapeReservedKeywords-class.html) are applied, but you can disable this behavior by explicitly specifying an empty list to the `preprocessors` parameter:

```dart
@EmbedLiteral("config.json", preprocessor = const [])
const config = _$config;
```

#### How is the data type determined?

The code generator tries to represent map-like data as records rather than `Map`s whenever possible. For example, the following JSON file is converted to a record because the all the keys have a valid format as record field names:
Expand Down Expand Up @@ -321,17 +348,26 @@ This rule is applied recursively if the input file contains nestd data structure

#### How to restrict the structure of data to be embedded?

Currently, you can't LITERALLY restrict the types of generated dart objects. Whether a JSON data is embedded as a `Map` literal or a record literal depends on the content of the input file. However, it is easy to tell the Dart compiler what shape of data you really want and have a static error occur if the generated code does not match those types.
You can restrict the types of generated dart objects by specifying concrete types to annotated top level variables.

```dart
// This is what you really want
typedef Config = ({ String url, String apiKey });
// Suppose you are only interested in the 'name' and 'publish_to' fields in the pubspec.yaml
typedef Pubspec = ({ String name, String publishTo });
@EmbedLiteral("config.json")
const Config config = _$config; // Expects `_$config` to be of type `Config`
@EmbedLiteral("/pubspec.yaml")
const Pubspec pubspec = _$pubspec; // Expects `_$pubspec` to be of type `Pubspec`
// Or if you prefer a Map to a Record
@EmbedLiteral("/pubspec.yaml")
const Map pubspecMap = _$pubspecMap;
```

If the generated object `_$config` is not of type `Config`, the dart analyzer will raise a static error telling you that the input file does not meet your requirements.
Then, the build_runner will generates the following:

```dart
const _$pubspec = (name: "ExampleApp", publishTo: "none");
const _$pubspecMap = {"name": "ExampleApp", "publishTo": "none"};
```

<br/>

Expand All @@ -357,7 +393,7 @@ flutter clean && flutter pub run build_runner build

## Roadmap

- [ ] Restrict the type of dart object to embed by giving the corresponding variable a concrete type
- [x] ~~Restrict the type of dart object to embed by giving the corresponding variable a concrete type~~ ➡️ Available from v1.1.0

<br/>

Expand Down
8 changes: 8 additions & 0 deletions embed/example/bin/example.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// ignore_for_file: prefer_typing_uninitialized_variables

import 'package:embed_annotation/embed_annotation.dart';

@EmbedLiteral("/pubspec.yaml")
var pubspec;

void main() {}
12 changes: 12 additions & 0 deletions embed/example/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
name: example
publish_to: none

environment:
sdk: ^3.0.5

dependencies:
embed_annotation:
path: ../../embed_annotation

dev_dependencies:
lints: ^2.0.0
9 changes: 5 additions & 4 deletions embed/lib/embed.dart
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
library embed;

import 'package:build/build.dart';
import 'package:embed/src/generators/embed_literal_generator.dart';
import 'package:embed/src/generators/embed_str_generator.dart';
import 'package:embed/src/literal/literal_embedding_generator.dart';
import 'package:embed/src/str/str_embedding_generator.dart';
import 'package:source_gen/source_gen.dart';

/// Creates a builder for @Embed annotations.
Builder embedBuilder(BuilderOptions options) {
return SharedPartBuilder(
[
EmbedStrGenerator(),
EmbedLiteralGenerator(),
StrEmbeddingGenerator(),
LiteralEmbeddingGenerator(),
],
"embed",
);
Expand Down
41 changes: 0 additions & 41 deletions embed/lib/src/common/dart_identifier.dart

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,17 @@ import 'dart:async';

import 'package:analyzer/dart/element/element.dart';
import 'package:build/build.dart';
import 'package:embed/src/common/embedder.dart';
import 'package:embed/src/common/resolve_content.dart';
import 'package:embed/src/common/usage_error.dart';
import 'package:embed/src/embedders/embedder.dart';
import 'package:embed_annotation/embed_annotation.dart';
import 'package:source_gen/source_gen.dart';

abstract class EmbedGenerator<E extends Embed>
abstract class EmbeddingGenerator<E extends Embed>
extends GeneratorForAnnotation<E> {
@override
FutureOr<String> generateForAnnotatedElement(
Element element, ConstantReader annotation, BuildStep buildStep) async {
if (element.kind != ElementKind.TOP_LEVEL_VARIABLE) {
if (element is! TopLevelVariableElement) {
throw InvalidGenerationSourceError(
"Only top level variables can be annotated with $E",
element: element,
Expand All @@ -23,25 +22,24 @@ abstract class EmbedGenerator<E extends Embed>
try {
return await _run(element, annotation, buildStep);
} on Error catch (error, stackTrace) {
final message = switch (error) {
UsageError error => error.message,
_ => "$error",
};
throw Error.throwWithStackTrace(
InvalidGenerationSourceError(message, element: element),
InvalidGenerationSourceError("$error", element: element),
stackTrace,
);
}
}

Future<String> _run(
Element element, ConstantReader annotation, BuildStep buildStep) async {
TopLevelVariableElement element,
ConstantReader annotation,
BuildStep buildStep,
) async {
String inputSourceFilePath() => buildStep.inputId.path;
final embedder = createEmbedderFrom(annotation);
final content = resolveContent(embedder.config.path, inputSourceFilePath);

final variable = "_\$${element.name}";
final embedding = await embedder.getEmbeddingOf(content);
final embedding = await embedder.getEmbeddingOf(content, element);
return "const $variable = $embedding;";
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import 'dart:async';
import 'dart:io';

import 'package:analyzer/dart/element/element.dart';
import 'package:embed_annotation/embed_annotation.dart';

abstract class Embedder<E extends Embed> {
const Embedder(this.config);

final E config;
FutureOr<String> getEmbeddingOf(File content);

FutureOr<String> getEmbeddingOf(
File content,
TopLevelVariableElement element,
);
}
17 changes: 17 additions & 0 deletions embed/lib/src/common/errors.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
class UsageError extends Error {
UsageError(this.message);
final String message;

@override
String toString() => message;
}

class ShouldNeverBeHappenError extends Error {
ShouldNeverBeHappenError();

@override
String toString() => "This error should never be happen. "
"If you see this message, please report the error with a stacktrace "
"on the GitHub issue page (https://github.com/fujidaiti/embed.dart/issues). "
"It will be very helpful.";
}
2 changes: 1 addition & 1 deletion embed/lib/src/common/resolve_content.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'dart:io';

import 'package:embed/src/common/usage_error.dart';
import 'package:embed/src/common/errors.dart';
import 'package:path/path.dart' as p;

File resolveContent(String path, InputSourceFilePathProvider source) {
Expand Down
4 changes: 0 additions & 4 deletions embed/lib/src/common/usage_error.dart

This file was deleted.

55 changes: 0 additions & 55 deletions embed/lib/src/embedders/primitives/dart_primitives.dart

This file was deleted.

20 changes: 0 additions & 20 deletions embed/lib/src/embedders/primitives/primitives.dart

This file was deleted.

6 changes: 0 additions & 6 deletions embed/lib/src/embedders/primitives/toml_primitives.dart

This file was deleted.

13 changes: 0 additions & 13 deletions embed/lib/src/generators/embed_literal_generator.dart

This file was deleted.

Loading

0 comments on commit 7d440e9

Please sign in to comment.