generic_reader 0.1.0

  • Readme
  • Changelog
  • Example
  • Installing
  • 64

Generic Reader #

Build Status

Introduction #

Source code generation has become an integral software development tool when building and maintaining a large number of data models, data access object, widgets, etc. Setting up the build system initially takes time and effort but subsequent maintenance is often easier, less error prone, and certainly less repetitive compared to applying manual modifications.

The premise of source code generation is that we can somehow specify (hopefully few) details and flesh out the rest of the classes, methods, and variables during the build process.

Dart's static analyzer provides access to libraries, classes, class fields, class methods, functions, variables, etc in the form of Elements.

Source code generation relies heavily on constants (instantiated by a constructor prefixed with the keyword const) since constants are known at compile time. Compile-time constant expressions are represented by a DartObject and can be accessed by using the method computeConstantValue() (available for elements representing a variable).

For built-in types, DartObject has methods that allow reading the underlying constant object. For example, it is an easy task to retrieve a value of type String:

// Let 'nameFieldElement' be a FieldElement containing a String.
final constantObject = nameFieldElement.computeConstantValue();
final String name = constantObject.toStringValue();

It can be a sightly more difficult task to read the underlying constant value of user defined data-types. These are often a composition of other types, as illustrated in the example below.

Click to show source-code.
class Age{
  const Age(this.age);
  final int age;
  bool get isAdult => age > 21;
}

class Name{
  const Name({this.firstName, this.lastName, this.middleName});
  final String firstName;
  final String lastName;
  final String middleName;
}

class User{
  const User({this.name, this.id, this.age});
  final Name name;
  final Age age;
  final int id;
}

In order to retrieve a constant value of type User one has to retrieve its components of type int, Name, and Age first.

Usage #

To use this library the following steps are required:

  1. Include generic_reader and source_gen as dependencies in your pubspec.yaml file.
  2. Create an instance of GenericReader (e.g. within a source code generator function):
    final reader = GenericReader(); // Note: [reader] is a singleton.
    
  3. Register a Decoder function for each data-type that needs to be handled. The built-in types bool, double, int, String, Type, and Symbol have pre-registered decoder functions.
  4. Retrieve the constant values that are required using the methods get<T>, getList<T>, getSet<T>:
    ...
    // Retrieving a constant of type [User]
    final user = get<User>(userConstantReader);
    
    // Retrieving a list with entries of type [Name].
    final names = getList<Name>(namesConstantReader);
    
    // Retrieving a set with entries of type [double].
    final values = getSet<double>(valuesConstantReader);
    
    
  5. Process the runtime constants and generate the required source code.

Decoder Functions #

GenericReader provides a systematic method of retrieving constants of arbitrary data-types by allowing users to register Decoder functions (for lack of better a word).

Decoders functions know how to decode a specific data-type and have the following signature:

typedef T Decoder<T>(ConstantReader constantReader);

The input argument is of type ConstantReader (a wrapper around DartObject) and the function returns an object of type T. It is presumed that the input argument constantReader represents an object of type T and this is checked and enforced.

The following shows how to register decoder functions for the types Age, Name, and User. Note that each decoder knows the field-names and field-types of the class it handles. For example, the decoder for User knows that age is of type Age and that the field-name is age.

In principle, decoded instructions on how to re-create a constant at runtime can be obtained by using the class Revivable. However, in the context of writing decoder functions, the source-code might be easier to read if the field-names are specified manually when using the function peek (see below).

...

// ConstantReader representing an object of type [User].
final userCR = ConstantReader(userFieldElement.computeConstantValue());

// The reader instance. (It is a singleton).
final reader = GenericReader();

// Adding decoders.
reader.addDecoder<Age>((constantReader) => Age(constantReader.peek('age').intValue));

reader.addDecoder<Name>((constantReader) {
  final firstName = constantReader.peek('firstName').stringValue;
  final lastName = constantReader.peek('lastName').stringValue;
  final middleName = constantReader.peek('middleName').stringValue;
  return Name(firstName: firstName, lastName: lastName, middleName: middleName);
});

reader.addDecoder<User>((constantReader){
  final id = constantReader.peek('id').intValue;
  final age = reader.get<Age>(constantReader.peek('age'));
  final name = reader.get<Name>(constantReader.peek('name'));
  return User(name: name, age: age, id: id,);
});

// Retrieving a constant value of type User:
final User user = reader.get<User>(userCR);

Note: The method peek returns an instance of ConstantReader representing the class field specified by the input String. It returns null if the field was not initialized or not present. Moreover, peek will recursively scan the super classes if the field could not be found in the current context.

Limitations #

Defining decoder functions for each data-type has its obvious limitiations when it comes to generic types. Programming the logic for reading generic constant values is made more difficult by the fact that Dart does not allow variables of data-type Type but only type-literals to be used as type arguments.

In practice, however, generic classes are often designed in such a manner that only few type parameters are valid or likely to be useful. A demonstration on how to retrieve constant values with generic type is presented in example.

Last but not least, constants that need to be retrieved during the source-generation process are most likely annotations and simple data-types that convey information to source code generators.

Examples #

For further information on how to use GenericReader to retrieve constants of arbitrary type see example.

Features and bugs #

Please file feature requests and bugs at the issue tracker.

0.0.1 #

Initial version of the library.

0.0.2 #

Changed Dart SDK version to >=2.8.1

0.0.3 #

Amended docs.

0.0.4 #

Fixed test 'get<>()'.

0.0.5 #

Amended README.md

0.0.6 #

The method addDecoder<> now returns an instance of the reader to allow method chaining.

0.0.7 #

Added method holdsA<>(). Deprecated method isA<>().

0.0.8 #

Removed pre-registered decoder for type Type.

0.0.9 #

Removed debug print statement. Updated dependencies.

0.1.0 #

Added condition to handle null input in methods getList<>() and getSet<>().Restructured folder example.

example/README.md

Generic Reader - Example #

Build Status

Retrieving Constants with Parameterized Type #

The file player_example.dart demonstrates how to use generic_reader to read the value of a constant with parameterized type from a static representation of a compile-time constant expression. The program also shows how to register Decoder functions for the types Column and SqliteType.

The constant values that are going to be read are the fields of the const class Player:

Click to show player.dart.
import 'package:generic_reader/src/test_types/column.dart';
import 'package:generic_reader/src/test_types/sponsor.dart';
import 'package:generic_reader/src/test_types/sqlite_type.dart';
import 'package:generic_reader/src/test_types/unregistered_test_type.dart';

/// Class modelling a player.
class Player {
  const Player();

  /// Column name
  final columnName = 'Player';

  /// Column storing player id.
  final id = const Column<Integer>();

  /// Column storing first name of player.
  final firstName = const Column<Text>(
    defaultValue: Text('Thomas'),
  );

  /// List of sponsors
  final List<Sponsor> sponsors = const [
    Sponsor('Johnson\'s'),
    Sponsor('Smith Brothers'),
  ];

  /// Test unregistered type.
  final unregistered = const UnRegisteredTestType();

  /// Test [Set<int>].
  final Set<int> primeNumbers = const {1, 3, 5, 7, 11, 13};

In the simple example below, the function initializeLibraryReaderForDirectory provided by source_gen_test is used to load the source code and initialize objects of type LibraryReader.

In a standard setting this task is delegated to a builder that reads a builder configuration and loads the relevant assets.

Click to show player_example.dart.
import 'package:ansicolor/ansicolor.dart';
import 'package:generic_reader/generic_reader.dart';
import 'package:source_gen/source_gen.dart' show ConstantReader;
import 'package:source_gen_test/src/init_library_reader.dart';
import 'package:generic_reader/src/test_types/column.dart';
import 'package:generic_reader/src/test_types/sqlite_type.dart';
import 'package:generic_reader/src/test_types/sponsor.dart';

/// To run this program navigate to the folder: /example
/// in your local copy the package [generic_reader] and
/// use the command:
///
/// # dart bin/player_example.dart

/// Demonstrates how to use [GenericReader] to read constants
/// with parameterized type from a static representation
/// of a compile-time constant expression
/// represented by a [ConstantReader].
Future<void> main() async {
  /// Reading libraries.
  final playerLib = await initializeLibraryReaderForDirectory(
    'lib/src',
    'player.dart',
  );

  // ConstantReader representing field 'columnName'.
  final columnNameCR =
      ConstantReader(playerLib.classes.first.fields[0].computeConstantValue());

  final idCR =
      ConstantReader(playerLib.classes.first.fields[1].computeConstantValue());

  // ConstantReade representing field 'firstName'.
  final firstNameCR =
      ConstantReader(playerLib.classes.first.fields[2].computeConstantValue());

  final sponsorsCR =
      ConstantReader(playerLib.classes.first.fields[3].computeConstantValue());

  // Get singleton instance of the reader.
  final reader = GenericReader();

  // Decoders for [SqliteType] and its derived types.
  final Decoder<Integer> integerDecoder =
      (cr) => (cr == null) ? null : Integer(cr.peek('value')?.intValue);

  final Decoder<Real> realDecoder =
      (cr) => (cr == null) ? null : Real(cr.peek('value')?.doubleValue);

  final Decoder<Boolean> booleanDecoder =
      (cr) => (cr == null) ? null : Boolean(cr.peek('value')?.boolValue);

  final Decoder<Text> textDecoder =
      (cr) => (cr == null) ? null : Text(cr.peek('value')?.stringValue);

  final Decoder<SqliteType> sqliteTypeDecoder = ((cr) {
    if (cr == null) return null;
    if (reader.holdsA<Integer>(cr)) return reader.get<Integer>(cr);
    if (reader.holdsA<Text>(cr)) return reader.get<Text>(cr);
    if (reader.holdsA<Real>(cr)) return reader.get<Real>(cr);
    return reader.get<Boolean>(cr);
  });

  // Registering decoders.
  reader
      .addDecoder<Integer>(integerDecoder)
      .addDecoder<Boolean>(booleanDecoder)
      .addDecoder<Text>(textDecoder)
      .addDecoder<Real>(realDecoder)
      .addDecoder<SqliteType>(sqliteTypeDecoder);

  // Adding a decoder for constants of type [Column].
  reader.addDecoder<Column>((cr) {
    if (cr == null) return null;
    final defaultValueCR = cr.peek('defaultValue');
    final defaultValue = reader.get<SqliteType>(defaultValueCR);

    final nameCR = cr.peek('name');
    final name = reader.get<String>(nameCR);

    Column<T> columnFactory<T extends SqliteType>() {
      return Column<T>(
        defaultValue: defaultValue,
        name: name,
      );
    }

    if (reader.holdsA<Column>(cr, typeArgs: [Text]))
      return columnFactory<Text>();
    if (reader.holdsA<Column>(cr, typeArgs: [Real]))
      return columnFactory<Real>();
    if (reader.holdsA<Column>(cr, typeArgs: [Integer]))
      return columnFactory<Integer>();
    return columnFactory<Boolean>();
  });

  AnsiPen green = AnsiPen()..green(bold: true);

  // Retrieve an instance of [String].
  final columnName = reader.get<String>(columnNameCR);
  print(green('Retrieving a [String]'));
  print('columnName = \'$columnName\'');
  print('');
  // Prints:
  // Retrieving a [String]
  // columnName = 'Player'

  // Retrieve an instance of [Column<Text>].
  final columnFirstName = reader.get<Column>(firstNameCR);
  print(green('Retrieving a [Column<Text>]:'));
  print(columnFirstName);
  // Prints:
  // Retrieving a [Column<Text>]:
  // Column<Text>(
  //   defaultValue: Text('Thomas')
  // )

  // Adding a decoder function for type [Sponsor].
  reader.addDecoder<Sponsor>((cr) => Sponsor(cr.peek('name').stringValue));

  final sponsors = reader.getList<Sponsor>(sponsorsCR);

  print('');
  print(green('Retrieving a [List<Sponsor>]:'));
  print(sponsors);
  // Prints:
  // Retrieving a [List<Sponsor>]:
  // [Sponsor: Johnson's, Sponsor: Smith Brothers]

  final id = reader.get<Column>(idCR);
  print('');
  print(green('Retrieving a [Column<Integer>]:'));
  print(id);
  // Prints:
  // Retrieving a [Column<Integer>]:
  // Column<Integer>(
  // )

Retrieving Constants with Arbitrary Type #

The example in the section above demonstrates how to retrieve constants with known parameterized type. The program presented below shows how to proceed if the constant has an arbitrary type parameter.

For this purpose consider the following generic class that wraps a value of type T:

/// Wraps a variable of type [T].
class Wrapper<T> {
  const Wrapper(this.value);

  /// Value of type [T].
  final T value;

  @override
  String toString() => 'Wrapper<$T>(value: $value)';
}

The type argument T can assume any data-type and it is impractical to handle all available types manually in the decoder function of Wrapper.

Instead, one can use the method get with the type dynamic. This signals to the reader to match the static type of the ConstantReader input to a registered data-type. If a match is found get<dynamic>(constantReader) returns a constant with the appropriate value, otherwise a ReaderError is thrown.

The program below retrieves the constant wrappedVariable defined in wrapper_test.dart. Note the use of the method get<dynamic>() when defining the Decoder function for the data-type Wrapper.

Click to show wrapper_example.dart.
import 'package:ansicolor/ansicolor.dart';
import 'package:example/src/sqlite_type.dart';
import 'package:example/src/wrapper.dart';
import 'package:generic_reader/generic_reader.dart';
import 'package:source_gen/source_gen.dart' show ConstantReader;
import 'package:source_gen_test/src/init_library_reader.dart';

/// To run this program navigate to the folder: /example
/// in your local copy the package [generic_reader] and
/// use the command:
///
/// # dart bin/wrapper_example.dart

/// Demonstrates how use [GenericReader] to read constants
/// with parameterized type from a static representation
/// of a compile-time constant expression
/// represented by a [ConstantReader].
Future<void> main() async {
  /// Reading libraries.
  final wrapperTestLib = await initializeLibraryReaderForDirectory(
    'lib/src',
    'wrapper_test.dart',
  );

  final wrappedCR = ConstantReader(
      wrapperTestLib.classes.first.fields[0].computeConstantValue());

  // Get singleton instance of the reader.
  final reader = GenericReader();

  AnsiPen green = AnsiPen()..green(bold: true);

  // Adding a decoder function for type [Wrapper].
  reader.addDecoder<Wrapper>((cr) {
    valueType = reader.findType(cr.objectValue.);

    final valueCR = cr.peek('value') as type;
    final value = reader.get<dynamic>(valueCR);
    return Wrapper(value);
  });

  final wrapped = reader.get<Wrapper>(wrappedCR);
  print(green('Retrieving a [Wrapper<dynamic>]:'));
  print(wrapped);
  // Prints:
  // Retrieving a [Wrapper<dynamic>]:
  // Wrapper<dynamic>(value: 27.9)
}

Features and bugs #

Please file feature requests and bugs at the issue tracker.

Use this package as a library

1. Depend on it

Add this to your package's pubspec.yaml file:


dependencies:
  generic_reader: ^0.1.0

2. Install it

You can install packages from the command line:

with pub:


$ pub get

Alternatively, your editor might support pub get. Check the docs for your editor to learn more.

3. Import it

Now in your Dart code, you can use:


import 'package:generic_reader/generic_reader.dart';
  
Popularity:
Describes how popular the package is relative to other packages. [more]
28
Health:
Code health derived from static analysis. [more]
100
Maintenance:
Reflects how tidy and up-to-date the package is. [more]
100
Overall:
Weighted score of the above. [more]
64
Learn more about scoring.

We analyzed this package on Jul 7, 2020, and provided a score, details, and suggestions below. Analysis was completed with status completed using:

  • Dart: 2.8.4
  • pana: 0.13.13

Analysis suggestions

Package not compatible with SDK flutter

Because it is not compatible with any of the supported runtimes: flutter-native, flutter-web

Package not compatible with runtime flutter-native on android

Because of the import of dart:mirrors via the import chain package:generic_reader/generic_reader.dartpackage:generic_reader/src/readers/generic_reader.dartpackage:source_gen/source_gen.dartpackage:source_gen/src/type_checker.dartdart:mirrors

Package not compatible with runtime flutter-native on ios

Because of the import of dart:mirrors via the import chain package:generic_reader/generic_reader.dartpackage:generic_reader/src/readers/generic_reader.dartpackage:source_gen/source_gen.dartpackage:source_gen/src/type_checker.dartdart:mirrors

Package not compatible with runtime flutter-native on linux

Because of the import of dart:mirrors via the import chain package:generic_reader/generic_reader.dartpackage:generic_reader/src/readers/generic_reader.dartpackage:source_gen/source_gen.dartpackage:source_gen/src/type_checker.dartdart:mirrors

Package not compatible with runtime flutter-native on macos

Because of the import of dart:mirrors via the import chain package:generic_reader/generic_reader.dartpackage:generic_reader/src/readers/generic_reader.dartpackage:source_gen/source_gen.dartpackage:source_gen/src/type_checker.dartdart:mirrors

Package not compatible with runtime flutter-native on windows

Because of the import of dart:mirrors via the import chain package:generic_reader/generic_reader.dartpackage:generic_reader/src/readers/generic_reader.dartpackage:source_gen/source_gen.dartpackage:source_gen/src/type_checker.dartdart:mirrors

Package not compatible with runtime flutter-web on web

Because of the import of dart:io via the import chain package:generic_reader/generic_reader.dartpackage:generic_reader/src/readers/generic_reader.dartpackage:source_gen/source_gen.dartpackage:source_gen/src/utils.dartpackage:build/build.dartpackage:build/src/generate/run_post_process_builder.dartpackage:build/src/builder/post_process_builder.dartpackage:build/src/builder/builder.dartpackage:build/src/builder/build_step.dartpackage:build/src/asset/reader.dartpackage:glob/glob.dartpackage:glob/src/list_tree.dartpackage:glob/src/io.dartpackage:glob/src/io_export.dartdart:io

Package not compatible with runtime native-aot

Because of the import of dart:mirrors via the import chain package:generic_reader/generic_reader.dartpackage:generic_reader/src/readers/generic_reader.dartpackage:source_gen/source_gen.dartpackage:source_gen/src/type_checker.dartdart:mirrors

Package not compatible with runtime web

Because of the import of dart:io via the import chain package:generic_reader/generic_reader.dartpackage:generic_reader/src/readers/generic_reader.dartpackage:source_gen/source_gen.dartpackage:source_gen/src/utils.dartpackage:build/build.dartpackage:build/src/generate/run_post_process_builder.dartpackage:build/src/builder/post_process_builder.dartpackage:build/src/builder/builder.dartpackage:build/src/builder/build_step.dartpackage:build/src/asset/reader.dartpackage:glob/glob.dartpackage:glob/src/list_tree.dartpackage:glob/src/io.dartpackage:glob/src/io_export.dartdart:io

Dependencies

Package Constraint Resolved Available
Direct dependencies
Dart SDK >=2.8.1 <3.0.0
analyzer ^0.39.10 0.39.12
meta ^1.1.8 1.2.1
source_gen ^0.9.5 0.9.5
Transitive dependencies
_fe_analyzer_shared 5.0.0
args 1.6.0
async 2.4.2
build 1.3.0
charcode 1.1.3
collection 1.14.13
convert 2.1.1
crypto 2.1.5
csslib 0.16.1
dart_style 1.3.6
glob 1.2.0
html 0.14.0+3
js 0.6.2
logging 0.11.4
node_interop 1.1.1
node_io 1.1.1
package_config 1.9.3
path 1.7.0
pedantic 1.9.1
pub_semver 1.4.4
source_span 1.7.0
string_scanner 1.0.5
term_glyph 1.1.0
typed_data 1.2.0
watcher 0.9.7+15
yaml 2.2.1
Dev dependencies
ansicolor ^1.0.2
source_gen_test ^0.1.1
test ^1.14.7