adding packages
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
import 'package:build/build.dart';
|
||||
import 'package:settings_gen/settings_gen.dart';
|
||||
import 'package:source_gen/source_gen.dart';
|
||||
|
||||
Builder storeGenerator(BuilderOptions options) =>
|
||||
SharedPartBuilder([StoreGenerator()], 'store_generator');
|
||||
@@ -0,0 +1,5 @@
|
||||
library settings_gen;
|
||||
|
||||
export 'src/settings_gen_base.dart';
|
||||
|
||||
const version = '1.0.1';
|
||||
@@ -0,0 +1,204 @@
|
||||
abstract class CodegenError {
|
||||
bool get hasErrors;
|
||||
String get message;
|
||||
}
|
||||
|
||||
class StoreClassCodegenErrors implements CodegenError {
|
||||
StoreClassCodegenErrors(this.name) {
|
||||
_errorCategories = [
|
||||
nonAbstractStoreMixinDeclarations,
|
||||
invalidComputedAnnotations,
|
||||
invalidObservableAnnotations,
|
||||
invalidActionAnnotations,
|
||||
staticObservables,
|
||||
finalObservables,
|
||||
];
|
||||
}
|
||||
|
||||
final String name;
|
||||
|
||||
final NonAbstractStoreMixinDeclarations nonAbstractStoreMixinDeclarations =
|
||||
NonAbstractStoreMixinDeclarations();
|
||||
|
||||
final PropertyErrors finalObservables = FinalObservableFields();
|
||||
final PropertyErrors staticObservables = StaticObservableFields();
|
||||
final PropertyErrors invalidObservableAnnotations =
|
||||
InvalidObservableAnnotations();
|
||||
|
||||
final PropertyErrors invalidComputedAnnotations =
|
||||
InvalidComputedAnnotations();
|
||||
|
||||
final PropertyErrors invalidActionAnnotations = InvalidActionAnnotations();
|
||||
|
||||
List<CodegenError> _errorCategories;
|
||||
|
||||
@override
|
||||
String get message {
|
||||
final errors = _errorCategories
|
||||
.where((category) => category.hasErrors)
|
||||
.toList(growable: false)
|
||||
.asMap()
|
||||
.map((i, category) => MapEntry(i, ' ${i + 1}. ${category.message}'))
|
||||
.values
|
||||
.join('\n');
|
||||
|
||||
return 'Could not make class "$name" observable. Changes needed:\n$errors';
|
||||
}
|
||||
|
||||
@override
|
||||
bool get hasErrors => _errorCategories.any((category) => category.hasErrors);
|
||||
}
|
||||
|
||||
final _fieldPluralizer = Pluralize('the field', 'fields');
|
||||
final _methodPluralizer = Pluralize('the method', 'methods');
|
||||
final _memberPluralizer = Pluralize('the member', 'members');
|
||||
|
||||
abstract class _InvalidStoreDeclarations implements CodegenError {
|
||||
final NameList _classNames = NameList();
|
||||
|
||||
// ignore: avoid_positional_boolean_parameters
|
||||
bool addIf(bool condition, String className) {
|
||||
if (condition) {
|
||||
_classNames.add(className);
|
||||
}
|
||||
|
||||
return condition;
|
||||
}
|
||||
|
||||
@override
|
||||
bool get hasErrors => _classNames.isNotEmpty;
|
||||
}
|
||||
|
||||
class NonAbstractStoreMixinDeclarations extends _InvalidStoreDeclarations {
|
||||
@override
|
||||
String get message =>
|
||||
'Classes that use the SettingsStore mixin must be marked abstract. Affected classes: $_classNames.';
|
||||
}
|
||||
|
||||
abstract class PropertyErrors implements CodegenError {
|
||||
final NameList _properties = NameList();
|
||||
|
||||
// ignore: avoid_positional_boolean_parameters
|
||||
bool addIf(bool condition, String propertyName) {
|
||||
if (condition) {
|
||||
_properties.add(propertyName);
|
||||
}
|
||||
return condition;
|
||||
}
|
||||
|
||||
String get propertyList => _properties.toString();
|
||||
|
||||
Pluralize propertyPlural = _fieldPluralizer;
|
||||
|
||||
String get property => propertyPlural(_properties.length);
|
||||
|
||||
@override
|
||||
bool get hasErrors => _properties.isNotEmpty;
|
||||
}
|
||||
|
||||
class FinalObservableFields extends PropertyErrors {
|
||||
@override
|
||||
String get message => 'Remove final modifier from $property $propertyList.';
|
||||
}
|
||||
|
||||
class StaticObservableFields extends PropertyErrors {
|
||||
@override
|
||||
String get message => 'Remove static modifier from $property $propertyList.';
|
||||
}
|
||||
|
||||
class AsyncGeneratorActionMethods extends PropertyErrors {
|
||||
@override
|
||||
// ignore: overridden_fields
|
||||
Pluralize propertyPlural = _methodPluralizer;
|
||||
|
||||
@override
|
||||
String get message =>
|
||||
'Replace async* modifier with async from $property $propertyList.';
|
||||
}
|
||||
|
||||
class NonAsyncMethods extends PropertyErrors {
|
||||
@override
|
||||
// ignore: overridden_fields
|
||||
Pluralize propertyPlural = _methodPluralizer;
|
||||
|
||||
@override
|
||||
String get message =>
|
||||
'Return a Future or a Stream from $property $propertyList.';
|
||||
}
|
||||
|
||||
class InvalidComputedAnnotations extends PropertyErrors {
|
||||
@override
|
||||
// ignore: overridden_fields
|
||||
Pluralize propertyPlural = _memberPluralizer;
|
||||
|
||||
@override
|
||||
String get message =>
|
||||
'Remove @computed annotation for $property $propertyList. They only apply to property-getters.';
|
||||
}
|
||||
|
||||
class InvalidObservableAnnotations extends PropertyErrors {
|
||||
@override
|
||||
// ignore: overridden_fields
|
||||
Pluralize propertyPlural = _memberPluralizer;
|
||||
|
||||
@override
|
||||
String get message =>
|
||||
'Remove @observable annotation for $property $propertyList. They only apply to fields.';
|
||||
}
|
||||
|
||||
class InvalidActionAnnotations extends PropertyErrors {
|
||||
@override
|
||||
// ignore: overridden_fields
|
||||
Pluralize propertyPlural = _memberPluralizer;
|
||||
|
||||
@override
|
||||
String get message =>
|
||||
'Remove @action annotation for $property $propertyList. They only apply to methods.';
|
||||
}
|
||||
|
||||
class InvalidStaticMethods extends PropertyErrors {
|
||||
@override
|
||||
// ignore: overridden_fields
|
||||
Pluralize propertyPlural = _methodPluralizer;
|
||||
|
||||
@override
|
||||
String get message => 'Remove static modifier from $property $propertyList.';
|
||||
}
|
||||
|
||||
class NameList {
|
||||
final List<String> _names = [];
|
||||
|
||||
void add(String name) => _names.add(name);
|
||||
|
||||
int get length => _names.length;
|
||||
|
||||
bool get isNotEmpty => _names.isNotEmpty;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
if (_names.length == 1) {
|
||||
return '"${_names[0]}"';
|
||||
}
|
||||
|
||||
final buf = StringBuffer();
|
||||
for (var i = 0; i < _names.length; i++) {
|
||||
final name = _names[i];
|
||||
buf.write('"$name"');
|
||||
if (i < _names.length - 2) {
|
||||
buf.write(', ');
|
||||
} else if (i == _names.length - 2) {
|
||||
buf.write(' and ');
|
||||
}
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
|
||||
class Pluralize {
|
||||
Pluralize(this._single, this._multiple);
|
||||
|
||||
final String _single;
|
||||
final String _multiple;
|
||||
|
||||
String call(int count) => count == 1 ? _single : _multiple;
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:analyzer/dart/element/element.dart';
|
||||
import 'package:analyzer/dart/element/type_system.dart';
|
||||
import 'package:build/build.dart';
|
||||
import 'package:settings_gen/src/store_class_visitor.dart';
|
||||
import 'package:settings_gen/src/template/store_file.dart';
|
||||
import 'package:settings_gen/src/template/store.dart';
|
||||
import 'package:settings_gen/src/type_names.dart';
|
||||
import 'package:source_gen/source_gen.dart';
|
||||
|
||||
class StoreGenerator extends Generator {
|
||||
@override
|
||||
FutureOr<String> generate(LibraryReader library, BuildStep buildStep) async {
|
||||
if (library.allElements.isEmpty) {
|
||||
return '';
|
||||
}
|
||||
|
||||
final typeSystem = await library.allElements.first.session.typeSystem;
|
||||
final file = StoreFileTemplate()
|
||||
..storeSources = _generateCodeForLibrary(library, typeSystem).toSet();
|
||||
return file.toString();
|
||||
}
|
||||
|
||||
Iterable<String> _generateCodeForLibrary(
|
||||
LibraryReader library,
|
||||
TypeSystem typeSystem,
|
||||
) sync* {
|
||||
for (final classElement in library.classes) {
|
||||
if (isMixinStoreClass(classElement)) {
|
||||
yield* _generateCodeForMixinStore(library, classElement, typeSystem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Iterable<String> _generateCodeForMixinStore(
|
||||
LibraryReader library,
|
||||
ClassElement baseClass,
|
||||
TypeSystem typeSystem,
|
||||
) sync* {
|
||||
final typeNameFinder = LibraryScopedNameFinder(library.element);
|
||||
final otherClasses = library.classes.where((c) => c != baseClass);
|
||||
final mixedClass = otherClasses.firstWhere((c) {
|
||||
if (baseClass.typeParameters.length != c.supertype.typeArguments.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return typeSystem.isSubtypeOf(
|
||||
c.type, baseClass.type.instantiate(c.supertype.typeArguments));
|
||||
}, orElse: () => null);
|
||||
|
||||
if (mixedClass != null) {
|
||||
yield _generateCodeFromTemplate(
|
||||
mixedClass.name, baseClass, MixinStoreTemplate(), typeNameFinder);
|
||||
}
|
||||
}
|
||||
|
||||
String _generateCodeFromTemplate(
|
||||
String publicTypeName,
|
||||
ClassElement userStoreClass,
|
||||
StoreTemplate template,
|
||||
LibraryScopedNameFinder typeNameFinder,
|
||||
) {
|
||||
final visitor = StoreClassVisitor(
|
||||
publicTypeName, userStoreClass, template, typeNameFinder);
|
||||
userStoreClass
|
||||
..accept(visitor)
|
||||
..visitChildren(visitor);
|
||||
return visitor.source;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
import 'package:analyzer/dart/element/element.dart';
|
||||
import 'package:analyzer/dart/element/visitor.dart';
|
||||
import 'package:build/build.dart';
|
||||
import 'package:settings_gen/src/template/double_setting.dart';
|
||||
import 'package:settings_gen/src/template/int_setting.dart';
|
||||
import 'package:settings_gen/src/template/string_list_setting.dart';
|
||||
import 'package:settings_manager/settings_manager.dart';
|
||||
import 'package:settings_manager/src/api/annotations.dart'
|
||||
show BoolSetting, SettingsConfig;
|
||||
import 'package:source_gen/source_gen.dart';
|
||||
|
||||
import 'errors.dart';
|
||||
import 'template/bool_setting.dart';
|
||||
import 'template/store.dart';
|
||||
import 'template/string_setting.dart';
|
||||
import 'template/util.dart';
|
||||
import 'type_names.dart';
|
||||
|
||||
class StoreClassVisitor extends SimpleElementVisitor {
|
||||
StoreClassVisitor(
|
||||
String publicTypeName,
|
||||
ClassElement userClass,
|
||||
StoreTemplate template,
|
||||
this.typeNameFinder,
|
||||
) : _errors = StoreClassCodegenErrors(publicTypeName) {
|
||||
_storeTemplate = template
|
||||
..typeParams.templates.addAll(userClass.typeParameters
|
||||
.map((type) => typeParamTemplate(type, typeNameFinder)))
|
||||
..typeArgs.templates.addAll(userClass.typeParameters.map((t) => t.name))
|
||||
..parentTypeName = userClass.name
|
||||
..publicTypeName = publicTypeName;
|
||||
}
|
||||
|
||||
final _boolSettingChecker = const TypeChecker.fromRuntime(BoolSetting);
|
||||
final _stringSettingChecker = const TypeChecker.fromRuntime(StringSetting);
|
||||
final _intSettingChecker = const TypeChecker.fromRuntime(IntSetting);
|
||||
final _doubleSettingChecker = const TypeChecker.fromRuntime(DoubleSetting);
|
||||
final _stringListSettingChecker =
|
||||
const TypeChecker.fromRuntime(StringListSetting);
|
||||
|
||||
StoreTemplate _storeTemplate;
|
||||
|
||||
LibraryScopedNameFinder typeNameFinder;
|
||||
|
||||
final StoreClassCodegenErrors _errors;
|
||||
|
||||
String get source {
|
||||
if (_errors.hasErrors) {
|
||||
log.severe(_errors.message);
|
||||
return '';
|
||||
}
|
||||
return _storeTemplate.toString();
|
||||
}
|
||||
|
||||
@override
|
||||
void visitClassElement(ClassElement element) {
|
||||
if (isMixinStoreClass(element)) {
|
||||
_errors.nonAbstractStoreMixinDeclarations
|
||||
.addIf(!element.isAbstract, element.name);
|
||||
}
|
||||
// if the class is annotated to generate toString() method we add the information to the _storeTemplate
|
||||
_storeTemplate.generateToString = hasGeneratedToString(element);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitFieldElement(FieldElement element) {
|
||||
if (_fieldIsNotValid(element)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_boolSettingChecker.hasAnnotationOfExact(element)) {
|
||||
final annotation = _boolSettingChecker.firstAnnotationOfExact(element);
|
||||
final template = BoolSettingTemplate()
|
||||
..defaultValue = annotation.getField('defaultValue').toBoolValue()
|
||||
..addStream = annotation.getField('addStream').toBoolValue()
|
||||
..addValueNotifier =
|
||||
annotation.getField('addValueNotifier').toBoolValue()
|
||||
..isPrivate = element.isPrivate
|
||||
..name = element.name;
|
||||
_storeTemplate.boolSettings.add(template);
|
||||
}
|
||||
|
||||
if (_stringSettingChecker.hasAnnotationOfExact(element)) {
|
||||
final annotation = _stringSettingChecker.firstAnnotationOfExact(element);
|
||||
final template = StringSettingTemplate()
|
||||
..defaultValue = annotation.getField('defaultValue').toStringValue()
|
||||
..addStream = annotation.getField('addStream').toBoolValue()
|
||||
..addValueNotifier =
|
||||
annotation.getField('addValueNotifier').toBoolValue()
|
||||
..isPrivate = element.isPrivate
|
||||
..name = element.name;
|
||||
_storeTemplate.stringSettings.add(template);
|
||||
}
|
||||
|
||||
if (_intSettingChecker.hasAnnotationOfExact(element)) {
|
||||
final annotation = _intSettingChecker.firstAnnotationOfExact(element);
|
||||
final template = IntSettingTemplate()
|
||||
..defaultValue = annotation.getField('defaultValue').toIntValue()
|
||||
..addStream = annotation.getField('addStream').toBoolValue()
|
||||
..addValueNotifier =
|
||||
annotation.getField('addValueNotifier').toBoolValue()
|
||||
..isPrivate = element.isPrivate
|
||||
..name = element.name;
|
||||
_storeTemplate.intSettings.add(template);
|
||||
}
|
||||
|
||||
if (_doubleSettingChecker.hasAnnotationOfExact(element)) {
|
||||
final annotation = _doubleSettingChecker.firstAnnotationOfExact(element);
|
||||
final template = DoubleSettingTemplate()
|
||||
..defaultValue = annotation.getField('defaultValue').toDoubleValue()
|
||||
..addStream = annotation.getField('addStream').toBoolValue()
|
||||
..addValueNotifier =
|
||||
annotation.getField('addValueNotifier').toBoolValue()
|
||||
..isPrivate = element.isPrivate
|
||||
..name = element.name;
|
||||
_storeTemplate.doubleSettings.add(template);
|
||||
}
|
||||
|
||||
if (_stringListSettingChecker.hasAnnotationOfExact(element)) {
|
||||
final annotation =
|
||||
_stringListSettingChecker.firstAnnotationOfExact(element);
|
||||
final template = StringListSettingTemplate()
|
||||
..defaultValue = annotation
|
||||
.getField('defaultValue')
|
||||
.toListValue()
|
||||
.map((item) => item.toStringValue())
|
||||
.toList()
|
||||
..addStream = annotation.getField('addStream').toBoolValue()
|
||||
..addValueNotifier =
|
||||
annotation.getField('addValueNotifier').toBoolValue()
|
||||
..isPrivate = element.isPrivate
|
||||
..name = element.name;
|
||||
_storeTemplate.stringListSettings.add(template);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
bool _fieldIsNotValid(FieldElement element) => _any([
|
||||
_errors.staticObservables.addIf(element.isStatic, element.name),
|
||||
_errors.finalObservables.addIf(element.isFinal, element.name)
|
||||
]);
|
||||
}
|
||||
|
||||
const _storeMixinChecker = TypeChecker.fromRuntime(SettingsStore);
|
||||
const _toStringAnnotationChecker = TypeChecker.fromRuntime(SettingsConfig);
|
||||
|
||||
bool isMixinStoreClass(ClassElement classElement) =>
|
||||
classElement.mixins.any(_storeMixinChecker.isExactlyType);
|
||||
|
||||
// Checks if the class as a toString annotation
|
||||
bool isStoreConfigAnnotatedStoreClass(ClassElement classElement) =>
|
||||
_toStringAnnotationChecker.hasAnnotationOfExact(classElement);
|
||||
|
||||
bool hasGeneratedToString(ClassElement classElement) {
|
||||
if (isStoreConfigAnnotatedStoreClass(classElement)) {
|
||||
final annotation =
|
||||
_toStringAnnotationChecker.firstAnnotationOfExact(classElement);
|
||||
return annotation.getField('hasToString').toBoolValue();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _any(List<bool> list) => list.any(_identity);
|
||||
|
||||
T _identity<T>(T value) => value;
|
||||
@@ -0,0 +1,77 @@
|
||||
import 'setting_impl.dart';
|
||||
|
||||
class BoolSettingTemplate implements SettingsImpl {
|
||||
bool defaultValue;
|
||||
String name;
|
||||
bool isPrivate;
|
||||
bool addStream = true;
|
||||
bool addValueNotifier = true;
|
||||
|
||||
@override
|
||||
String preInit() {
|
||||
return '${name}Notify($defaultValue);';
|
||||
}
|
||||
|
||||
@override
|
||||
String postInit() {
|
||||
return '${name}Notify($name);';
|
||||
}
|
||||
|
||||
@override
|
||||
String dispose() {
|
||||
final sb = StringBuffer();
|
||||
if (addStream) {
|
||||
sb.writeln(' _${name}Controller.close();');
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
final sb = StringBuffer();
|
||||
if (addStream) {
|
||||
sb.writeln(
|
||||
'final _${name}Controller = StreamController<bool>.broadcast();');
|
||||
sb.writeln(
|
||||
'Stream<bool> get ${name}Stream => _${name}Controller.stream;');
|
||||
}
|
||||
if (addValueNotifier) {
|
||||
sb.writeln('final _${name}Notifier = ValueNotifier<bool>($defaultValue);');
|
||||
sb.writeln('ValueListenable<bool> get ${name}Notifier => _${name}Notifier;');
|
||||
}
|
||||
|
||||
sb.writeln("""
|
||||
|
||||
@override
|
||||
bool get $name {
|
||||
return prefs?.getBool('$name') ?? $defaultValue;
|
||||
}
|
||||
|
||||
@override
|
||||
set $name(bool value) {
|
||||
${name}Async(value);
|
||||
}
|
||||
|
||||
Future<bool> ${name}Async(bool value) async {
|
||||
final success = await prefs.setBool('$name', value);
|
||||
if (success) {
|
||||
${name}Notify(value);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
""");
|
||||
|
||||
sb.writeln('void ${name}Notify(bool value) {');
|
||||
if (addStream) {
|
||||
sb.writeln(' _${name}Controller.add(value);');
|
||||
}
|
||||
if (addValueNotifier) {
|
||||
sb.writeln('_${name}Notifier.value = value;');
|
||||
}
|
||||
sb.writeln('_controller.add(this);');
|
||||
sb.writeln('}');
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import 'package:settings_gen/src/template/util.dart';
|
||||
|
||||
class CommaList<T> {
|
||||
CommaList(this.templates) : assert(templates != null);
|
||||
|
||||
final List<T> templates;
|
||||
|
||||
@override
|
||||
String toString() =>
|
||||
templates.map((t) => t.toString()).where((s) => s.isNotEmpty).join(', ');
|
||||
}
|
||||
|
||||
class SurroundedCommaList<T> {
|
||||
SurroundedCommaList(this.prefix, this.suffix, this.templates)
|
||||
: assert(prefix != null),
|
||||
assert(suffix != null),
|
||||
assert(templates != null);
|
||||
|
||||
final String prefix;
|
||||
final String suffix;
|
||||
final List<T> templates;
|
||||
|
||||
@override
|
||||
String toString() => surroundNonEmpty(prefix, suffix, CommaList(templates));
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
import 'setting_impl.dart';
|
||||
|
||||
class DoubleSettingTemplate implements SettingsImpl {
|
||||
double defaultValue;
|
||||
String name;
|
||||
bool isPrivate;
|
||||
bool addStream = true;
|
||||
bool addValueNotifier = true;
|
||||
|
||||
@override
|
||||
String preInit() {
|
||||
return '${name}Notify($defaultValue);';
|
||||
}
|
||||
|
||||
@override
|
||||
String postInit() {
|
||||
return '${name}Notify($name);';
|
||||
}
|
||||
|
||||
@override
|
||||
String dispose() {
|
||||
final sb = StringBuffer();
|
||||
if (addStream) {
|
||||
sb.writeln(' _${name}Controller.close();');
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
final sb = StringBuffer();
|
||||
if (addStream) {
|
||||
sb.writeln(
|
||||
'final _${name}Controller = StreamController<double>.broadcast();');
|
||||
sb.writeln(
|
||||
'Stream<double> get ${name}Stream => _${name}Controller.stream;');
|
||||
}
|
||||
if (addValueNotifier) {
|
||||
sb.writeln('final _${name}Notifier = ValueNotifier<double>($defaultValue);');
|
||||
sb.writeln(
|
||||
'ValueListenable<double> get ${name}Notifier => _${name}Notifier;');
|
||||
}
|
||||
|
||||
sb.writeln("""
|
||||
|
||||
@override
|
||||
double get $name {
|
||||
return prefs?.getDouble('$name') ?? $defaultValue;
|
||||
}
|
||||
|
||||
@override
|
||||
set $name(double value) {
|
||||
${name}Async(value);
|
||||
}
|
||||
|
||||
Future<bool> ${name}Async(double value) async {
|
||||
final success = await prefs.setDouble('$name', value);
|
||||
if (success) {
|
||||
${name}Notify(value);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
""");
|
||||
|
||||
sb.writeln('void ${name}Notify(double value) {');
|
||||
if (addStream) {
|
||||
sb.writeln(' _${name}Controller.add(value);');
|
||||
}
|
||||
if (addValueNotifier) {
|
||||
sb.writeln('_${name}Notifier.value = value;');
|
||||
}
|
||||
sb.writeln('_controller.add(this);');
|
||||
sb.writeln('}');
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import 'setting_impl.dart';
|
||||
|
||||
class IntSettingTemplate implements SettingsImpl {
|
||||
int defaultValue;
|
||||
String name;
|
||||
bool isPrivate;
|
||||
bool addStream = true;
|
||||
bool addValueNotifier = true;
|
||||
|
||||
@override
|
||||
String preInit() {
|
||||
return '${name}Notify($defaultValue);';
|
||||
}
|
||||
|
||||
@override
|
||||
String postInit() {
|
||||
return '${name}Notify($name);';
|
||||
}
|
||||
|
||||
@override
|
||||
String dispose() {
|
||||
final sb = StringBuffer();
|
||||
if (addStream) {
|
||||
sb.writeln(' _${name}Controller.close();');
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
final sb = StringBuffer();
|
||||
if (addStream) {
|
||||
sb.writeln(
|
||||
'final _${name}Controller = StreamController<int>.broadcast();');
|
||||
sb.writeln('Stream<int> get ${name}Stream => _${name}Controller.stream;');
|
||||
}
|
||||
if (addValueNotifier) {
|
||||
sb.writeln('final _${name}Notifier = ValueNotifier<int>($defaultValue);');
|
||||
sb.writeln('ValueListenable<int> get ${name}Notifier => _${name}Notifier;');
|
||||
}
|
||||
|
||||
sb.writeln("""
|
||||
|
||||
@override
|
||||
int get $name {
|
||||
return prefs?.getInt('$name') ?? $defaultValue;
|
||||
}
|
||||
|
||||
@override
|
||||
set $name(int value) {
|
||||
${name}Async(value);
|
||||
}
|
||||
|
||||
Future<bool> ${name}Async(int value) async {
|
||||
final success = await prefs.setInt('$name', value);
|
||||
if (success) {
|
||||
${name}Notify(value);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
""");
|
||||
|
||||
sb.writeln('void ${name}Notify(int value) {');
|
||||
if (addStream) {
|
||||
sb.writeln(' _${name}Controller.add(value);');
|
||||
}
|
||||
if (addValueNotifier) {
|
||||
sb.writeln('_${name}Notifier.value = value;');
|
||||
}
|
||||
sb.writeln('_controller.add(this);');
|
||||
sb.writeln('}');
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
class ParamTemplate {
|
||||
String name;
|
||||
String type;
|
||||
String defaultValue;
|
||||
bool hasRequiredAnnotation = false;
|
||||
|
||||
String get asArgument => name;
|
||||
|
||||
NamedArgTemplate get asNamedArgument => NamedArgTemplate()..name = name;
|
||||
|
||||
String get metadata => hasRequiredAnnotation ? '@required ' : '';
|
||||
|
||||
@override
|
||||
String toString() => defaultValue == null
|
||||
? '$metadata$type $name'
|
||||
: '$type $name = $defaultValue';
|
||||
}
|
||||
|
||||
class TypeParamTemplate {
|
||||
String name;
|
||||
String bound;
|
||||
|
||||
String get asArgument => name;
|
||||
|
||||
@override
|
||||
String toString() => bound == null ? name : '$name extends $bound';
|
||||
}
|
||||
|
||||
class NamedArgTemplate {
|
||||
String name;
|
||||
|
||||
@override
|
||||
String toString() => '$name: $name';
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
class Rows<T> {
|
||||
final List<T> _templates = [];
|
||||
|
||||
void add(T template) => _templates.add(template);
|
||||
|
||||
bool get isEmpty => _templates.isEmpty;
|
||||
List get templates => _templates;
|
||||
|
||||
@override
|
||||
String toString() =>
|
||||
_templates.map((t) => t.toString()).where((s) => s.isNotEmpty).join('\n');
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
abstract class SettingsImpl {
|
||||
String preInit();
|
||||
String postInit();
|
||||
String dispose();
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
import 'bool_setting.dart';
|
||||
import 'comma_list.dart';
|
||||
import 'double_setting.dart';
|
||||
import 'int_setting.dart';
|
||||
import 'params.dart';
|
||||
import 'rows.dart';
|
||||
import 'setting_impl.dart';
|
||||
import 'string_list_setting.dart';
|
||||
import 'string_setting.dart';
|
||||
|
||||
class MixinStoreTemplate extends StoreTemplate {
|
||||
String get typeName => '_\$$publicTypeName';
|
||||
|
||||
@override
|
||||
String toString() => '''
|
||||
|
||||
mixin $typeName$typeParams on $parentTypeName$typeArgs, SettingsStore {
|
||||
$storeBody
|
||||
}''';
|
||||
}
|
||||
|
||||
abstract class StoreTemplate {
|
||||
final SurroundedCommaList<TypeParamTemplate> typeParams =
|
||||
SurroundedCommaList('<', '>', []);
|
||||
final SurroundedCommaList<String> typeArgs =
|
||||
SurroundedCommaList('<', '>', []);
|
||||
String publicTypeName;
|
||||
String parentTypeName;
|
||||
|
||||
final Rows<BoolSettingTemplate> boolSettings = Rows();
|
||||
final Rows<StringSettingTemplate> stringSettings = Rows();
|
||||
final Rows<IntSettingTemplate> intSettings = Rows();
|
||||
final Rows<DoubleSettingTemplate> doubleSettings = Rows();
|
||||
final Rows<StringListSettingTemplate> stringListSettings = Rows();
|
||||
final List<String> toStringList = [];
|
||||
|
||||
bool generateToString = false;
|
||||
String _actionControllerName;
|
||||
String get actionControllerName =>
|
||||
_actionControllerName ??= '_\$${parentTypeName}ActionController';
|
||||
|
||||
String get storeBody {
|
||||
var toStringMethod = '';
|
||||
|
||||
final sb = StringBuffer();
|
||||
|
||||
sb.writeln('SharedPreferences prefs;');
|
||||
sb.writeln(
|
||||
'final _controller = StreamController<$publicTypeName>.broadcast();');
|
||||
sb.writeln('Stream<$publicTypeName> get stream => _controller.stream;');
|
||||
sb.writeln();
|
||||
List<SettingsImpl> _settingsImpl = [];
|
||||
_settingsImpl.addAll(
|
||||
boolSettings.templates.whereType<SettingsImpl>().map((t) => t).toList(),
|
||||
);
|
||||
_settingsImpl.addAll(
|
||||
stringSettings.templates.whereType<SettingsImpl>().map((t) => t).toList(),
|
||||
);
|
||||
_settingsImpl.addAll(
|
||||
intSettings.templates.whereType<SettingsImpl>().map((t) => t).toList(),
|
||||
);
|
||||
_settingsImpl.addAll(
|
||||
doubleSettings.templates.whereType<SettingsImpl>().map((t) => t).toList(),
|
||||
);
|
||||
_settingsImpl.addAll(
|
||||
stringListSettings.templates
|
||||
.whereType<SettingsImpl>()
|
||||
.map((t) => t)
|
||||
.toList(),
|
||||
);
|
||||
sb.writeln(' Future<bool> init() async {');
|
||||
for (final setting in _settingsImpl) {
|
||||
sb.writeln(setting.preInit());
|
||||
}
|
||||
sb.writeln(' prefs = await SharedPreferences.getInstance();');
|
||||
for (final setting in _settingsImpl) {
|
||||
sb.writeln(setting.postInit());
|
||||
}
|
||||
sb.writeln(' return prefs != null;');
|
||||
sb.writeln(' }');
|
||||
sb.writeln();
|
||||
sb.writeln('$boolSettings');
|
||||
sb.writeln();
|
||||
sb.writeln('$stringSettings');
|
||||
sb.writeln();
|
||||
sb.writeln('$intSettings');
|
||||
sb.writeln();
|
||||
sb.writeln('$doubleSettings');
|
||||
sb.writeln();
|
||||
sb.writeln('$stringListSettings');
|
||||
sb.writeln();
|
||||
sb.writeln(' void dispose() {');
|
||||
for (final setting in _settingsImpl) {
|
||||
sb.writeln(setting.dispose());
|
||||
}
|
||||
sb.writeln('_controller.close();');
|
||||
sb.writeln(' }');
|
||||
sb.writeln();
|
||||
|
||||
final baseBody = sb.toString();
|
||||
|
||||
if (generateToString) {
|
||||
final publicBoolSettings = boolSettings.templates
|
||||
..removeWhere((element) => element.isPrivate);
|
||||
final publicStringSettings = stringSettings.templates
|
||||
..removeWhere((element) => element.isPrivate);
|
||||
final publicIntSettings = intSettings.templates
|
||||
..removeWhere((element) => element.isPrivate);
|
||||
final publicDoubleSettings = doubleSettings.templates
|
||||
..removeWhere((element) => element.isPrivate);
|
||||
final publicStringListSettings = doubleSettings.templates
|
||||
..removeWhere((element) => element.isPrivate);
|
||||
|
||||
toStringList
|
||||
..addAll(publicBoolSettings.map(
|
||||
(current) => '${current.name}: \${${current.name}.toString()}'))
|
||||
..addAll(publicStringSettings.map(
|
||||
(current) => '${current.name}: \${${current.name}.toString()}'))
|
||||
..addAll(publicIntSettings.map(
|
||||
(current) => '${current.name}: \${${current.name}.toString()}'))
|
||||
..addAll(publicDoubleSettings.map(
|
||||
(current) => '${current.name}: \${${current.name}.toString()}'))
|
||||
..addAll(publicStringListSettings.map(
|
||||
(current) => '${current.name}: \${${current.name}.toString()}'));
|
||||
|
||||
toStringMethod = '''
|
||||
@override
|
||||
String toString() {
|
||||
final string = \'${toStringList.join(',')}\';
|
||||
return '{\$string}';
|
||||
}
|
||||
''';
|
||||
}
|
||||
|
||||
return baseBody + toStringMethod;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString();
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
const _analyzerIgnores =
|
||||
'// ignore_for_file: non_constant_identifier_names, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic';
|
||||
|
||||
class StoreFileTemplate {
|
||||
Iterable<String> storeSources;
|
||||
|
||||
@override
|
||||
String toString() => storeSources.isEmpty
|
||||
? ''
|
||||
: '''
|
||||
$_analyzerIgnores
|
||||
|
||||
${storeSources.join('\n\n')}
|
||||
''';
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
import 'setting_impl.dart';
|
||||
|
||||
class StringListSettingTemplate implements SettingsImpl {
|
||||
List<String> defaultValue;
|
||||
String name;
|
||||
bool isPrivate;
|
||||
bool addStream = true;
|
||||
bool addValueNotifier = true;
|
||||
|
||||
@override
|
||||
String preInit() {
|
||||
return "${name}Notify($defaultValue);";
|
||||
}
|
||||
|
||||
@override
|
||||
String postInit() {
|
||||
return '${name}Notify($name);';
|
||||
}
|
||||
|
||||
@override
|
||||
String dispose() {
|
||||
final sb = StringBuffer();
|
||||
if (addStream) {
|
||||
sb.writeln(' _${name}Controller.close();');
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
final sb = StringBuffer();
|
||||
if (addStream) {
|
||||
sb.writeln(
|
||||
'final _${name}Controller = StreamController<List<String>>.broadcast();');
|
||||
sb.writeln(
|
||||
'Stream<List<String>> get ${name}Stream => _${name}Controller.stream;');
|
||||
}
|
||||
if (addValueNotifier) {
|
||||
sb.writeln('final _${name}Notifier = ValueNotifier<List<String>>($defaultValue);');
|
||||
sb.writeln('ValueListenable<List<String>> get ${name}Notifier => _${name}Notifier;');
|
||||
}
|
||||
|
||||
sb.writeln("""
|
||||
|
||||
@override
|
||||
List<String> get $name {
|
||||
return prefs?.getStringList('$name') ?? $defaultValue;
|
||||
}
|
||||
|
||||
@override
|
||||
set $name(List<String> value) {
|
||||
${name}Async(value);
|
||||
}
|
||||
|
||||
Future<bool> ${name}Async(List<String> value) async {
|
||||
final success = await prefs.setStringList('$name', value);
|
||||
if (success) {
|
||||
${name}Notify(value);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
""");
|
||||
|
||||
sb.writeln('void ${name}Notify(List<String> value) {');
|
||||
if (addStream) {
|
||||
sb.writeln(' _${name}Controller.add(value);');
|
||||
}
|
||||
if (addValueNotifier) {
|
||||
sb.writeln('_${name}Notifier.value = value;');
|
||||
}
|
||||
sb.writeln('_controller.add(this);');
|
||||
sb.writeln('}');
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
import 'setting_impl.dart';
|
||||
|
||||
class StringSettingTemplate implements SettingsImpl {
|
||||
String defaultValue;
|
||||
String name;
|
||||
bool isPrivate;
|
||||
bool addStream = true;
|
||||
bool addValueNotifier = true;
|
||||
|
||||
@override
|
||||
String preInit() {
|
||||
return "${name}Notify('$defaultValue');";
|
||||
}
|
||||
|
||||
@override
|
||||
String postInit() {
|
||||
return '${name}Notify($name);';
|
||||
}
|
||||
|
||||
@override
|
||||
String dispose() {
|
||||
final sb = StringBuffer();
|
||||
if (addStream) {
|
||||
sb.writeln(' _${name}Controller.close();');
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
final sb = StringBuffer();
|
||||
if (addStream) {
|
||||
sb.writeln(
|
||||
'final _${name}Controller = StreamController<String>.broadcast();');
|
||||
sb.writeln(
|
||||
'Stream<String> get ${name}Stream => _${name}Controller.stream;');
|
||||
}
|
||||
if (addValueNotifier) {
|
||||
sb.writeln("final _${name}Notifier = ValueNotifier<String>('$defaultValue');");
|
||||
sb.writeln('ValueListenable<String> get ${name}Notifier => _${name}Notifier;');
|
||||
}
|
||||
|
||||
sb.writeln("""
|
||||
|
||||
@override
|
||||
String get $name {
|
||||
return prefs?.getString('$name') ?? '$defaultValue';
|
||||
}
|
||||
|
||||
@override
|
||||
set $name(String value) {
|
||||
${name}Async(value);
|
||||
}
|
||||
|
||||
Future<bool> ${name}Async(String value) async {
|
||||
final success = await prefs.setString('$name', value);
|
||||
if (success) {
|
||||
${name}Notify(value);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
""");
|
||||
|
||||
sb.writeln('void ${name}Notify(String value) {');
|
||||
if (addStream) {
|
||||
sb.writeln(' _${name}Controller.add(value);');
|
||||
}
|
||||
if (addValueNotifier) {
|
||||
sb.writeln('_${name}Notifier.value = value;');
|
||||
}
|
||||
sb.writeln('_controller.add(this);');
|
||||
sb.writeln('}');
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import 'package:analyzer/dart/element/element.dart';
|
||||
import 'package:settings_gen/src/template/params.dart';
|
||||
import 'package:settings_gen/src/type_names.dart';
|
||||
import 'package:source_gen/source_gen.dart';
|
||||
|
||||
// ignore: avoid_annotating_with_dynamic
|
||||
String surroundNonEmpty(String prefix, String suffix, dynamic content) {
|
||||
final contentStr = content.toString();
|
||||
return contentStr.isEmpty ? '' : '$prefix$contentStr$suffix';
|
||||
}
|
||||
|
||||
const _streamChecker = TypeChecker.fromRuntime(Stream);
|
||||
|
||||
class AsyncMethodChecker {
|
||||
AsyncMethodChecker([TypeChecker checkStream]) {
|
||||
_checkStream = checkStream ?? _streamChecker;
|
||||
}
|
||||
|
||||
TypeChecker _checkStream;
|
||||
|
||||
bool returnsFuture(MethodElement method) =>
|
||||
method.returnType.isDartAsyncFuture ||
|
||||
(method.isAsynchronous &&
|
||||
!method.isGenerator &&
|
||||
method.returnType.isDynamic);
|
||||
|
||||
bool returnsStream(MethodElement method) =>
|
||||
_checkStream.isAssignableFromType(method.returnType) ||
|
||||
(method.isAsynchronous &&
|
||||
method.isGenerator &&
|
||||
method.returnType.isDynamic);
|
||||
}
|
||||
|
||||
TypeParamTemplate typeParamTemplate(
|
||||
TypeParameterElement param,
|
||||
LibraryScopedNameFinder typeNameFinder,
|
||||
) =>
|
||||
TypeParamTemplate()
|
||||
..name = param.name
|
||||
..bound = param.bound != null
|
||||
? typeNameFinder.findTypeParameterBoundsTypeName(param)
|
||||
: null;
|
||||
@@ -0,0 +1,140 @@
|
||||
import 'package:analyzer/dart/element/element.dart';
|
||||
import 'package:analyzer/dart/element/type.dart';
|
||||
import 'package:settings_gen/src/template/comma_list.dart';
|
||||
|
||||
/// Determines the names of types within the context of a library, determining
|
||||
/// prefixes if applicable.
|
||||
///
|
||||
/// For example, if a library has been imported with a name, references to types
|
||||
/// contained or exported by that library must be prefixed by that name.
|
||||
///
|
||||
/// ```dart
|
||||
/// import 'dart:io' as io;
|
||||
///
|
||||
/// io.File someFile;
|
||||
/// ```
|
||||
///
|
||||
/// If we had a reference to `someFile`'s [Element], [findVariableTypeName]
|
||||
/// would return `"io.File"`.
|
||||
class LibraryScopedNameFinder {
|
||||
LibraryScopedNameFinder(this.library);
|
||||
|
||||
final LibraryElement library;
|
||||
|
||||
Map<Element, String> _namesByElement;
|
||||
Map<Element, String> get namesByElement {
|
||||
if (_namesByElement != null) {
|
||||
return _namesByElement;
|
||||
}
|
||||
|
||||
_namesByElement = {};
|
||||
|
||||
// Add all of this library's type-defining elements to the name map
|
||||
final libraryElements =
|
||||
library.topLevelElements.whereType<TypeDefiningElement>();
|
||||
for (final element in libraryElements) {
|
||||
_namesByElement[element] = element.name;
|
||||
}
|
||||
|
||||
// Reverse each import's export namespace so we can map elements to their
|
||||
// library-local names. Note that the definedNames include a prefix if there
|
||||
// is one.
|
||||
for (final import in library.imports) {
|
||||
for (final entry in import.namespace.definedNames.entries) {
|
||||
_namesByElement[entry.value] = entry.key;
|
||||
}
|
||||
}
|
||||
|
||||
return _namesByElement;
|
||||
}
|
||||
|
||||
String findVariableTypeName(VariableElement variable) =>
|
||||
_getDartTypeName(variable.type);
|
||||
|
||||
String findGetterTypeName(PropertyAccessorElement getter) {
|
||||
assert(getter.isGetter);
|
||||
return findReturnTypeName(getter);
|
||||
}
|
||||
|
||||
String findParameterTypeName(ParameterElement parameter) =>
|
||||
_getDartTypeName(parameter.type);
|
||||
|
||||
String findReturnTypeName(FunctionTypedElement executable) =>
|
||||
_getDartTypeName(executable.returnType);
|
||||
|
||||
List<String> findReturnTypeArgumentTypeNames(ExecutableElement executable) {
|
||||
final returnType = executable.returnType;
|
||||
return returnType is ParameterizedType
|
||||
? returnType.typeArguments.map(_getDartTypeName).toList()
|
||||
: [];
|
||||
}
|
||||
|
||||
String findTypeParameterBoundsTypeName(TypeParameterElement typeParameter) {
|
||||
assert(typeParameter.bound != null);
|
||||
return _getDartTypeName(typeParameter.bound);
|
||||
}
|
||||
|
||||
/// Calculates a type name, including its type arguments
|
||||
///
|
||||
/// The returned string will include import prefixes on all applicable types.
|
||||
String _getDartTypeName(DartType type) {
|
||||
var typeElement = type.element;
|
||||
if (type is FunctionType) {
|
||||
// If we're dealing with a typedef, we let it undergo the standard name
|
||||
// lookup. Otherwise, we special case the function naming.
|
||||
if (typeElement?.enclosingElement is GenericTypeAliasElement) {
|
||||
typeElement = typeElement.enclosingElement;
|
||||
} else {
|
||||
return _getFunctionTypeName(type);
|
||||
}
|
||||
} else if (
|
||||
// Some types don't have associated elements, like void
|
||||
typeElement == null ||
|
||||
// This is a bare type param, like "T"
|
||||
type is TypeParameterType) {
|
||||
// TODO(shyndman): This ignored deprecation can be removed when we
|
||||
// increase the analyzer dependency's lower bound to 0.39.2, and
|
||||
// migrate to using `DartType.getDisplayString`.
|
||||
// ignore: deprecated_member_use
|
||||
return type.displayName;
|
||||
}
|
||||
|
||||
return _getNamedElementTypeName(typeElement, type);
|
||||
}
|
||||
|
||||
String _getFunctionTypeName(FunctionType type) {
|
||||
final returnTypeName = _getDartTypeName(type.returnType);
|
||||
|
||||
final normalParameterTypeNames =
|
||||
CommaList(type.normalParameterTypes.map(_getDartTypeName).toList());
|
||||
final optionalParameterTypeNames = SurroundedCommaList(
|
||||
'[', ']', type.optionalParameterTypes.map(_getDartTypeName).toList());
|
||||
final namedParameterPairs = type.namedParameterTypes.entries
|
||||
.map((entry) => '${_getDartTypeName(entry.value)} ${entry.key}')
|
||||
.toList();
|
||||
final namedParameterTypeNames =
|
||||
SurroundedCommaList('{', '}', namedParameterPairs);
|
||||
|
||||
final parameterTypeNames = CommaList([
|
||||
normalParameterTypeNames,
|
||||
optionalParameterTypeNames,
|
||||
namedParameterTypeNames,
|
||||
]);
|
||||
|
||||
return '$returnTypeName Function($parameterTypeNames)';
|
||||
}
|
||||
|
||||
String _getNamedElementTypeName(Element typeElement, DartType type) {
|
||||
// Determine the name of the type, without type arguments.
|
||||
assert(namesByElement.containsKey(typeElement));
|
||||
|
||||
// If the type is parameterized, we recursively name its type arguments
|
||||
if (type is ParameterizedType && type.typeArguments.isNotEmpty) {
|
||||
final typeArgNames = SurroundedCommaList(
|
||||
'<', '>', type.typeArguments.map(_getDartTypeName).toList());
|
||||
return '${namesByElement[typeElement]}$typeArgNames';
|
||||
}
|
||||
|
||||
return namesByElement[typeElement];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user