diff --git a/CHANGELOG.md b/CHANGELOG.md index be73234..6e87b71 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - *BREAKING CHANGE*: The way enums are defined has changed, see readme for more information. You can now add properties to enums, optional and default values are supported. - *BREAKING CHANGE*: Enums are now by default not uppercase anymore, you can still enable this my adding 'uppercase_enums: true' to your pubspec or enum configuration - Logs of build runner now get shown in real time. +- You are now allowed to have no properties configured for a class. ## [6.3.0] - 2023-06-05 - Fixed the deprecated `ignore` field. Added diff --git a/bin/model_generator.dart b/bin/model_generator.dart index df0a25d..172b4f1 100644 --- a/bin/model_generator.dart +++ b/bin/model_generator.dart @@ -1,17 +1,5 @@ -import 'dart:async'; -import 'dart:io'; - import 'package:args/args.dart'; -import 'package:model_generator/config/pubspec_config.dart'; -import 'package:model_generator/config/yml_generator_config.dart'; -import 'package:model_generator/model/model/custom_model.dart'; -import 'package:model_generator/model/model/enum_model.dart'; -import 'package:model_generator/model/model/json_converter_model.dart'; -import 'package:model_generator/model/model/object_model.dart'; -import 'package:model_generator/run_process/run_process.dart'; -import 'package:model_generator/writer/enum_model_writer.dart'; -import 'package:model_generator/writer/object_model_writer.dart'; -import 'package:path/path.dart'; +import 'package:model_generator/main.dart'; Future main(List args) async { final argParser = ArgParser() @@ -26,123 +14,5 @@ Future main(List args) async { print(argParser.usage); return; } - - final pubspecYaml = File(join(Directory.current.path, 'pubspec.yaml')); - if (!pubspecYaml.existsSync()) { - throw Exception( - 'This program should be run from the root of a flutter/dart project'); - } - final pubspecContent = pubspecYaml.readAsStringSync(); - final pubspecConfig = PubspecConfig(pubspecContent); - - final configPath = results['path'] ?? pubspecConfig.configPath; - String absolutePath; - if (isAbsolute(configPath)) { - absolutePath = configPath; - } else { - absolutePath = join(Directory.current.path, configPath); - } - final FileSystemEntity configEntity; - switch (FileSystemEntity.typeSync(absolutePath)) { - case FileSystemEntityType.directory: - configEntity = Directory(absolutePath); - break; - case FileSystemEntityType.file: - default: - configEntity = File(absolutePath); - break; - } - - if (!configEntity.existsSync()) { - throw Exception( - 'This program requires a config file/dir. `$configPath` does not exist'); - } - final YmlGeneratorConfig modelGeneratorConfig; - if (configEntity is Directory) { - modelGeneratorConfig = - readConfigFilesInDirectory(pubspecConfig, configEntity, configPath); - } else { - final modelGeneratorContent = (configEntity as File).readAsStringSync(); - modelGeneratorConfig = - YmlGeneratorConfig(pubspecConfig, modelGeneratorContent, configPath); - } - modelGeneratorConfig.checkIfTypesAvailable(); - if (modelGeneratorConfig.models.isEmpty) { - print('No models defined in config files, skipping generation'); - } - - writeToFiles(pubspecConfig, modelGeneratorConfig); - await generateJsonGeneratedModels(useFvm: pubspecConfig.useFvm); - print('Done!!!'); -} - -YmlGeneratorConfig readConfigFilesInDirectory( - PubspecConfig config, Directory configEntity, String directoryPath) { - final configFiles = configEntity - .listSync(recursive: true) - .whereType() - .where((element) => - extension(element.path) == '.yaml' || - extension(element.path) == '.yml'); - final configs = configFiles.map((e) => - YmlGeneratorConfig(config, e.readAsStringSync(), relative(e.path))); - return YmlGeneratorConfig.merge(configs, directoryPath); -} - -void writeToFiles( - PubspecConfig pubspecConfig, YmlGeneratorConfig modelGeneratorConfig) { - for (final model in modelGeneratorConfig.models) { - final modelDirectory = Directory(join('lib', model.baseDirectory)); - if (!modelDirectory.existsSync()) { - modelDirectory.createSync(recursive: true); - } - String? content; - if (model is ObjectModel) { - content = ObjectModelWriter( - pubspecConfig, - model, - modelGeneratorConfig, - ).write(); - } else if (model is EnumModel) { - content = EnumModelWriter(model).write(); - } else if (model is JsonConverterModel) { - continue; - } else if (model is CustomModel) { - continue; - } - if (content == null) { - throw Exception( - 'content is null for ${model.name}. File a bug report on github. This is not normal. https://github.com/icapps/flutter-model-generator/issues'); - } - File file; - if (model.path == null) { - file = File(join('lib', model.baseDirectory, '${model.fileName}.dart')); - } else { - file = File(join( - 'lib', model.baseDirectory, model.path, '${model.fileName}.dart')); - } - if (!file.existsSync()) { - file.createSync(recursive: true); - } - file.writeAsStringSync(content); - } -} - -Future generateJsonGeneratedModels({required bool useFvm}) async { - final arguments = [ - if (useFvm) ...[ - 'fvm', - ], - 'flutter', - 'packages', - 'pub', - 'run', - 'build_runner', - 'build', - '--delete-conflicting-outputs', - ]; - await ProcessRunner.runProcessVerbose( - arguments.first, - arguments.skip(1).toList(), - ); + ModelGenerator.generate(results); } diff --git a/example/lib/model/user/project/sub_project.dart b/example/lib/model/user/project/sub_project.dart new file mode 100644 index 0000000..d931c62 --- /dev/null +++ b/example/lib/model/user/project/sub_project.dart @@ -0,0 +1,46 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +import 'package:flutter/foundation.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:model_generator_example/model/status/status.dart'; +import 'package:model_generator_example/model/user/project/project.dart'; + +part 'sub_project.g.dart'; + +@JsonSerializable(explicitToJson: true) +@immutable +class SubProject extends Project { + const SubProject({ + String name = 'test', + double? cost = 0.2, + Status? status, + }) : super( + name: name, + cost: cost, + status: status, + ); + + factory SubProject.fromJson(Map json) => + _$SubProjectFromJson(json); + + @override + Map toJson() => _$SubProjectToJson(this); + + @override + String toString() => 'SubProject{' + 'name: $name, ' + 'cost: $cost, ' + 'status: $status' + '}'; +} + +const deserializeSubProject = SubProject.fromJson; + +Map serializeSubProject(SubProject object) => object.toJson(); + +List deserializeSubProjectList( + List> jsonList) => + jsonList.map(SubProject.fromJson).toList(); + +List> serializeSubProjectList(List objects) => + objects.map((object) => object.toJson()).toList(); diff --git a/example/lib/model/user/project/sub_project.g.dart b/example/lib/model/user/project/sub_project.g.dart new file mode 100644 index 0000000..50c8c68 --- /dev/null +++ b/example/lib/model/user/project/sub_project.g.dart @@ -0,0 +1,37 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'sub_project.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +SubProject _$SubProjectFromJson(Map json) => SubProject( + name: json['name'] as String? ?? 'test', + cost: (json['cost'] as num?)?.toDouble() ?? 0.2, + status: $enumDecodeNullable(_$StatusEnumMap, json['status'], + unknownValue: Status.status0), + ); + +Map _$SubProjectToJson(SubProject instance) { + final val = { + 'name': instance.name, + }; + + void writeNotNull(String key, dynamic value) { + if (value != null) { + val[key] = value; + } + } + + writeNotNull('cost', instance.cost); + writeNotNull('status', _$StatusEnumMap[instance.status]); + return val; +} + +const _$StatusEnumMap = { + Status.status0: 0, + Status.status1: 1, + Status.status2: 2, + Status.status3: 3, +}; diff --git a/example/model_generator/config.yaml b/example/model_generator/config.yaml index d276b2b..955b9b5 100644 --- a/example/model_generator/config.yaml +++ b/example/model_generator/config.yaml @@ -151,6 +151,11 @@ Project: type: Status? unknown_enum_value: status0 +SubProject: + path: user/project/ + extends: Project + properties: + ProjectWrapper: path: user/project/ type: object diff --git a/lib/config/yml_generator_config.dart b/lib/config/yml_generator_config.dart index 09e0650..755c57d 100644 --- a/lib/config/yml_generator_config.dart +++ b/lib/config/yml_generator_config.dart @@ -90,9 +90,6 @@ class YmlGeneratorConfig { )); return; } - if (properties == null && type != 'enum') { - throw Exception('Properties can not be null. model: $key'); - } if (properties is! YamlMap?) { throw Exception( 'Properties should be a map, right now you are using a ${properties.runtimeType}. model: $key'); diff --git a/lib/main.dart b/lib/main.dart new file mode 100644 index 0000000..6a69b47 --- /dev/null +++ b/lib/main.dart @@ -0,0 +1,140 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:args/args.dart'; +import 'package:model_generator/config/pubspec_config.dart'; +import 'package:model_generator/config/yml_generator_config.dart'; +import 'package:model_generator/model/model/custom_model.dart'; +import 'package:model_generator/model/model/enum_model.dart'; +import 'package:model_generator/model/model/json_converter_model.dart'; +import 'package:model_generator/model/model/object_model.dart'; +import 'package:model_generator/run_process/run_process.dart'; +import 'package:model_generator/writer/enum_model_writer.dart'; +import 'package:model_generator/writer/object_model_writer.dart'; +import 'package:path/path.dart'; + +class ModelGenerator { + ModelGenerator._(); + + static void generate(ArgResults results) async { + final pubspecYaml = File(join(Directory.current.path, 'pubspec.yaml')); + if (!pubspecYaml.existsSync()) { + throw Exception( + 'This program should be run from the root of a flutter/dart project'); + } + final pubspecContent = pubspecYaml.readAsStringSync(); + final pubspecConfig = PubspecConfig(pubspecContent); + + final configPath = results['path'] ?? pubspecConfig.configPath; + String absolutePath; + if (isAbsolute(configPath)) { + absolutePath = configPath; + } else { + absolutePath = join(Directory.current.path, configPath); + } + final FileSystemEntity configEntity; + switch (FileSystemEntity.typeSync(absolutePath)) { + case FileSystemEntityType.directory: + configEntity = Directory(absolutePath); + break; + case FileSystemEntityType.file: + default: + configEntity = File(absolutePath); + break; + } + + if (!configEntity.existsSync()) { + throw Exception( + 'This program requires a config file/dir. `$configPath` does not exist'); + } + final YmlGeneratorConfig modelGeneratorConfig; + if (configEntity is Directory) { + modelGeneratorConfig = + _readConfigFilesInDirectory(pubspecConfig, configEntity, configPath); + } else { + final modelGeneratorContent = (configEntity as File).readAsStringSync(); + modelGeneratorConfig = + YmlGeneratorConfig(pubspecConfig, modelGeneratorContent, configPath); + } + modelGeneratorConfig.checkIfTypesAvailable(); + if (modelGeneratorConfig.models.isEmpty) { + print('No models defined in config files, skipping generation'); + } + + _writeToFiles(pubspecConfig, modelGeneratorConfig); + await _generateJsonGeneratedModels(useFvm: pubspecConfig.useFvm); + print('Done!!!'); + } + + static YmlGeneratorConfig _readConfigFilesInDirectory( + PubspecConfig config, Directory configEntity, String directoryPath) { + final configFiles = configEntity + .listSync(recursive: true) + .whereType() + .where((element) => + extension(element.path) == '.yaml' || + extension(element.path) == '.yml'); + final configs = configFiles.map((e) => + YmlGeneratorConfig(config, e.readAsStringSync(), relative(e.path))); + return YmlGeneratorConfig.merge(configs, directoryPath); + } + + static void _writeToFiles( + PubspecConfig pubspecConfig, YmlGeneratorConfig modelGeneratorConfig) { + for (final model in modelGeneratorConfig.models) { + final modelDirectory = Directory(join('lib', model.baseDirectory)); + if (!modelDirectory.existsSync()) { + modelDirectory.createSync(recursive: true); + } + String? content; + if (model is ObjectModel) { + content = ObjectModelWriter( + pubspecConfig, + model, + modelGeneratorConfig, + ).write(); + } else if (model is EnumModel) { + content = EnumModelWriter(model).write(); + } else if (model is JsonConverterModel) { + continue; + } else if (model is CustomModel) { + continue; + } + if (content == null) { + throw Exception( + 'content is null for ${model.name}. File a bug report on github. This is not normal. https://github.com/icapps/flutter-model-generator/issues'); + } + File file; + if (model.path == null) { + file = File(join('lib', model.baseDirectory, '${model.fileName}.dart')); + } else { + file = File(join( + 'lib', model.baseDirectory, model.path, '${model.fileName}.dart')); + } + if (!file.existsSync()) { + file.createSync(recursive: true); + } + file.writeAsStringSync(content); + } + } + + static Future _generateJsonGeneratedModels( + {required bool useFvm}) async { + final arguments = [ + if (useFvm) ...[ + 'fvm', + ], + 'flutter', + 'packages', + 'pub', + 'run', + 'build_runner', + 'build', + '--delete-conflicting-outputs', + ]; + await ProcessRunner.runProcessVerbose( + arguments.first, + arguments.skip(1).toList(), + ); + } +} diff --git a/lib/writer/object_model_writer.dart b/lib/writer/object_model_writer.dart index 337ec5a..37d86e2 100644 --- a/lib/writer/object_model_writer.dart +++ b/lib/writer/object_model_writer.dart @@ -155,9 +155,13 @@ class ObjectModelWriter { final anyNonFinal = jsonModel.fields.any((element) => element.nonFinal) || extendsFields.any((element) => element.nonFinal); + sb ..writeln() - ..writeln(' ${anyNonFinal ? '' : 'const '}${jsonModel.name}({'); + ..write(' ${anyNonFinal ? '' : 'const '}${jsonModel.name}('); + if (jsonModel.fields.isNotEmpty || extendsModel != null) { + sb.writeln('{'); + } for (final key in jsonModel.fields .where((key) => (key.isRequired && !key.hasDefaultValue))) { @@ -177,8 +181,11 @@ class ObjectModelWriter { sb.writeln( ' ${_getKeyType(key)} ${key.name}${_fillDefaulValue(key)},'); } + if (jsonModel.fields.isNotEmpty || extendsModel != null) { + sb.write(' }'); + } if (extendsModel != null) { - sb.writeln(' }) : super('); + sb.writeln(') : super('); for (final key in extendsFields) { sb.writeln(' ${key.name}: ${key.name},'); } @@ -187,9 +194,10 @@ class ObjectModelWriter { ..writeln(); } else { sb - ..writeln(' });') + ..writeln(');') ..writeln(); } + if (jsonModel.generateForGenerics) { sb.writeln( ' factory ${jsonModel.name}.fromJson(Object? json) => _\$${jsonModel.name}FromJson(json as Map); // ignore: avoid_as'); @@ -212,7 +220,8 @@ class ObjectModelWriter { ' static ${jsonModel.name} create(${jsonModel.generateForGenerics ? 'Object? json' : 'Map json'}) => ${jsonModel.name}.fromJson(json);'); } - if (jsonModel.equalsAndHashCode ?? pubspecConfig.equalsHashCode) { + if ((jsonModel.equalsAndHashCode ?? pubspecConfig.equalsHashCode) && + jsonModel.fields.isNotEmpty) { sb ..writeln() ..writeln(' @override') @@ -244,7 +253,9 @@ class ObjectModelWriter { if (c == 0) sb.write(' 0'); sb.writeln(';'); } - if (jsonModel.generateToString ?? pubspecConfig.generateToString) { + if (jsonModel.generateToString ?? + pubspecConfig.generateToString && + (jsonModel.fields.isNotEmpty || extendsModel != null)) { sb ..writeln() ..writeln(' @override') diff --git a/test/config/yml_generator_config_test.dart b/test/config/yml_generator_config_test.dart index c66e501..1fa51d8 100644 --- a/test/config/yml_generator_config_test.dart +++ b/test/config/yml_generator_config_test.dart @@ -129,11 +129,10 @@ void main() { expect(errorMessage, 'Exception: firstName has no defined type'); }); - test('Error no properties', () { + test('no Error no properties', () { final pubspecConfig = PubspecConfig(ConfigTestHelper.getPubspecConfig('normal')); var hasError = false; - var errorMessage = ''; try { YmlGeneratorConfig( pubspecConfig, @@ -143,11 +142,8 @@ void main() { .checkIfTypesAvailable(); } catch (e) { hasError = true; - errorMessage = e.toString(); } - expect(hasError, true); - expect(errorMessage, - 'Exception: Properties can not be null. model: Person'); + expect(hasError, false); }); }); group('Custom', () { diff --git a/test/writer/object_model_writer/no_fields/config.txt b/test/writer/object_model_writer/no_fields/config.txt new file mode 100644 index 0000000..0c48afa --- /dev/null +++ b/test/writer/object_model_writer/no_fields/config.txt @@ -0,0 +1,4 @@ +Person: + path: user/person/ + type: object + properties: \ No newline at end of file diff --git a/test/writer/object_model_writer/no_fields/output.txt b/test/writer/object_model_writer/no_fields/output.txt new file mode 100644 index 0000000..ad9c207 --- /dev/null +++ b/test/writer/object_model_writer/no_fields/output.txt @@ -0,0 +1,16 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +import 'package:json_annotation/json_annotation.dart'; + +part 'person.g.dart'; + +@JsonSerializable(explicitToJson: true) +class Person { + + const Person(); + + factory Person.fromJson(Map json) => _$PersonFromJson(json); + + Map toJson() => _$PersonToJson(this); + +} diff --git a/test/writer/object_model_writer/no_fields/pubspec.txt b/test/writer/object_model_writer/no_fields/pubspec.txt new file mode 100644 index 0000000..95f3114 --- /dev/null +++ b/test/writer/object_model_writer/no_fields/pubspec.txt @@ -0,0 +1,4 @@ +name: model_generator_example + +model_generator: + config_path: model_generator/config.yaml diff --git a/test/writer/object_model_writer/no_fields_inheritance/config.txt b/test/writer/object_model_writer/no_fields_inheritance/config.txt new file mode 100644 index 0000000..a908982 --- /dev/null +++ b/test/writer/object_model_writer/no_fields_inheritance/config.txt @@ -0,0 +1,14 @@ +Person: + path: user/person/ + type: object + extends: BasePerson + properties: + +BasePerson: + path: user/person/ + type: object + properties: + firstName: + description: A good description + type: String? + default_value: "'test'" diff --git a/test/writer/object_model_writer/no_fields_inheritance/output.txt b/test/writer/object_model_writer/no_fields_inheritance/output.txt new file mode 100644 index 0000000..40c6f84 --- /dev/null +++ b/test/writer/object_model_writer/no_fields_inheritance/output.txt @@ -0,0 +1,22 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +import 'package:json_annotation/json_annotation.dart'; +import 'package:model_generator_example/model/user/person/base_person.dart'; + +part 'person.g.dart'; + +@JsonSerializable(explicitToJson: true) +class Person extends BasePerson { + + const Person({ + String? firstName = 'test', + }) : super( + firstName: firstName, + ); + + factory Person.fromJson(Map json) => _$PersonFromJson(json); + + @override + Map toJson() => _$PersonToJson(this); + +} diff --git a/test/writer/object_model_writer/no_fields_inheritance/pubspec.txt b/test/writer/object_model_writer/no_fields_inheritance/pubspec.txt new file mode 100644 index 0000000..95f3114 --- /dev/null +++ b/test/writer/object_model_writer/no_fields_inheritance/pubspec.txt @@ -0,0 +1,4 @@ +name: model_generator_example + +model_generator: + config_path: model_generator/config.yaml