From 9b3a755add2210a8aadc61531e9287b9cd8b7e9a Mon Sep 17 00:00:00 2001 From: Ming Ming Date: Sun, 16 Jul 2023 18:01:09 +0800 Subject: [PATCH] Generate code to help sorting db rows --- codegen/lib/np_codegen.dart | 1 + .../lib/src/drift_table_sort_annotations.dart | 6 ++ codegen_build/build.yaml | 4 +- codegen_build/lib/builder.dart | 4 + .../lib/src/drift_table_sort_generator.dart | 71 +++++++++++++++ codegen_build/pubspec.yaml | 1 + codegen_build/test/drift_table_sort_test.dart | 88 +++++++++++++++++++ codegen_build/test/src/drift_table_sort.dart | 4 + codegen_build/test/util.dart | 34 +++++++ 9 files changed, 211 insertions(+), 2 deletions(-) create mode 100644 codegen/lib/src/drift_table_sort_annotations.dart create mode 100644 codegen_build/lib/src/drift_table_sort_generator.dart create mode 100644 codegen_build/test/drift_table_sort_test.dart create mode 100644 codegen_build/test/src/drift_table_sort.dart create mode 100644 codegen_build/test/util.dart diff --git a/codegen/lib/np_codegen.dart b/codegen/lib/np_codegen.dart index 67feb773..ed3786f5 100644 --- a/codegen/lib/np_codegen.dart +++ b/codegen/lib/np_codegen.dart @@ -1,3 +1,4 @@ library np_codegen; +export 'src/drift_table_sort_annotations.dart'; export 'src/np_log_annotations.dart'; diff --git a/codegen/lib/src/drift_table_sort_annotations.dart b/codegen/lib/src/drift_table_sort_annotations.dart new file mode 100644 index 00000000..59212282 --- /dev/null +++ b/codegen/lib/src/drift_table_sort_annotations.dart @@ -0,0 +1,6 @@ +class DriftTableSort { + const DriftTableSort(this.dbClass); + + final String dbClass; +} + diff --git a/codegen_build/build.yaml b/codegen_build/build.yaml index 37c266ea..546cdcaa 100644 --- a/codegen_build/build.yaml +++ b/codegen_build/build.yaml @@ -1,9 +1,9 @@ builders: np_log_build: import: "package:np_codegen_build/builder.dart" - builder_factories: ["npLogBuilder"] + builder_factories: ["driftTableSortBuilder", "npLogBuilder"] # The `partId` argument to `SharedPartBuilder` is "some_cool_builder" - build_extensions: {".dart": [".np_log.g.part"]} + build_extensions: {".dart": [".np_codegen.g.part"]} auto_apply: dependents build_to: cache # To copy the `.g.part` content into `.g.dart` in the source tree diff --git a/codegen_build/lib/builder.dart b/codegen_build/lib/builder.dart index 699b99c8..82a548cb 100644 --- a/codegen_build/lib/builder.dart +++ b/codegen_build/lib/builder.dart @@ -1,6 +1,10 @@ import 'package:build/build.dart'; +import 'package:np_codegen_build/src/drift_table_sort_generator.dart'; import 'package:np_codegen_build/src/np_log_generator.dart'; import 'package:source_gen/source_gen.dart'; +Builder driftTableSortBuilder(BuilderOptions options) => + SharedPartBuilder([DriftTableSortGenerator()], "drift_table_sort"); + Builder npLogBuilder(BuilderOptions options) => SharedPartBuilder([NpLogGenerator()], "np_log"); diff --git a/codegen_build/lib/src/drift_table_sort_generator.dart b/codegen_build/lib/src/drift_table_sort_generator.dart new file mode 100644 index 00000000..4c864a6b --- /dev/null +++ b/codegen_build/lib/src/drift_table_sort_generator.dart @@ -0,0 +1,71 @@ +import 'package:analyzer/dart/element/element.dart'; +import 'package:build/build.dart'; +import 'package:np_codegen/np_codegen.dart'; +import 'package:source_gen/source_gen.dart'; + +/// Generate a enum class with all columns to define sorting without exposing +/// drift internals +class DriftTableSortGenerator extends GeneratorForAnnotation { + const DriftTableSortGenerator(); + + @override + dynamic generateForAnnotatedElement( + Element element, + ConstantReader annotation, + BuildStep buildStep, + ) { + if (element is! ClassElement) { + print("Not a class"); + return null; + } + final driftTableSort = + DriftTableSort(annotation.read("dbClass").stringValue); + final clazz = element; + if (!clazz.allSupertypes.any((t) => t.element2.name == "Table")) { + print("Not a drift table"); + return null; + } + final columns = clazz.fields.where((f) => _shouldIncludeField(f)).toList(); + if (columns.isEmpty) { + print("No columns"); + return null; + } + + final sortEnumName = + "${clazz.name.substring(0, clazz.name.length - 1)}Sort"; + final enumValues = columns + .map((f) => "${f.name}Asc, ${f.name}Desc, ") + .reduce((a, b) => a + b); + final cases = columns.map((f) { + return """ +case $sortEnumName.${f.name}Asc: + return OrderingTerm.asc(db.${clazz.name.replaceRange(0, 1, clazz.name[0].toLowerCase())}.${f.name}); +case $sortEnumName.${f.name}Desc: + return OrderingTerm.desc(db.${clazz.name.replaceRange(0, 1, clazz.name[0].toLowerCase())}.${f.name}); +"""; + }).reduce((a, b) => a + b); + return """ +enum $sortEnumName { $enumValues } + +extension ${sortEnumName}IterableExtension on Iterable<$sortEnumName> { + Iterable toOrderingItem(${driftTableSort.dbClass} db) { + return map((s) { + switch (s) { $cases } + }); + } +} +"""; + } + + bool _shouldIncludeField(FieldElement field) { + if (!field.isSynthetic) { + // columns are getters + return false; + } + // it's a very rough way but well... + if (field.type.element2?.name?.endsWith("Column") ?? false) { + return true; + } + return false; + } +} diff --git a/codegen_build/pubspec.yaml b/codegen_build/pubspec.yaml index 03302d20..639c8749 100644 --- a/codegen_build/pubspec.yaml +++ b/codegen_build/pubspec.yaml @@ -17,6 +17,7 @@ dependencies: dev_dependencies: build_test: ^2.1.5 lints: any + path: any test: any code_gen_tester: git: diff --git a/codegen_build/test/drift_table_sort_test.dart b/codegen_build/test/drift_table_sort_test.dart new file mode 100644 index 00000000..56bf53ef --- /dev/null +++ b/codegen_build/test/drift_table_sort_test.dart @@ -0,0 +1,88 @@ +import 'package:np_codegen_build/src/drift_table_sort_generator.dart'; +import 'package:test/test.dart'; + +import 'util.dart'; + +void main() { + resolveCompilationUnit("test/src/drift_table_sort.dart"); + tearDown(() { + // Increment this after each test so the next test has it's own package + _pkgCacheCount++; + }); + + test("DriftTableSort", () { + final src = _genSrc(""" +@DriftTableSort("Database") +class Tests extends Table { + IntColumn get test1 => integer()(); + TextColumn get test2 => text()(); +} + +class Table {} + +class IntColumn {} + +class TextColumn {} + +IntColumn Function() integer() => () => IntColumn(); + +TextColumn Function() text() => () => TextColumn(); +"""); + final expected = _genExpected(r""" +enum TestSort { + test1Asc, + test1Desc, + test2Asc, + test2Desc, +} + +extension TestSortIterableExtension on Iterable { + Iterable toOrderingItem(Database db) { + return map((s) { + switch (s) { + case TestSort.test1Asc: + return OrderingTerm.asc(db.tests.test1); + case TestSort.test1Desc: + return OrderingTerm.desc(db.tests.test1); + case TestSort.test2Asc: + return OrderingTerm.asc(db.tests.test2); + case TestSort.test2Desc: + return OrderingTerm.desc(db.tests.test2); + } + }); + } +} +"""); + return _buildTest(src, expected); + }); +} + +String _genSrc(String src) { + return """ +import 'package:np_codegen/np_codegen.dart'; +part 'test.g.dart'; +$src +"""; +} + +String _genExpected(String src) { + return """// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'test.dart'; + +// ************************************************************************** +// DriftTableSortGenerator +// ************************************************************************** + +$src"""; +} + +Future _buildTest(String src, String expected) => buildTest( + generators: [DriftTableSortGenerator()], + pkgName: _pkgName, + src: src, + expected: expected, + ); + +String get _pkgName => 'pkg$_pkgCacheCount'; +int _pkgCacheCount = 1; diff --git a/codegen_build/test/src/drift_table_sort.dart b/codegen_build/test/src/drift_table_sort.dart new file mode 100644 index 00000000..1f765ef9 --- /dev/null +++ b/codegen_build/test/src/drift_table_sort.dart @@ -0,0 +1,4 @@ +import 'package:np_codegen/np_codegen.dart'; + +@DriftTableSort("") +class Foo {} diff --git a/codegen_build/test/util.dart b/codegen_build/test/util.dart new file mode 100644 index 00000000..4436039d --- /dev/null +++ b/codegen_build/test/util.dart @@ -0,0 +1,34 @@ +import 'dart:io'; + +import 'package:build/build.dart'; +import 'package:build_test/build_test.dart'; +import 'package:path/path.dart' as p; +import 'package:source_gen/source_gen.dart'; + +// Taken from source_gen_test, unclear why this is needed... +Future resolveCompilationUnit(String filePath) async { + final assetId = AssetId.parse('a|lib/${p.basename(filePath)}'); + final files = + Directory(p.dirname(filePath)).listSync().whereType().toList(); + + final fileMap = Map.fromEntries(files.map( + (f) => MapEntry('a|lib/${p.basename(f.path)}', f.readAsStringSync()))); + + await resolveSources(fileMap, (item) async { + return await item.libraryFor(assetId); + }, resolverFor: 'a|lib/${p.basename(filePath)}'); +} + +Future buildTest({ + required List generators, + required String pkgName, + required String src, + required String expected, +}) { + return testBuilder( + PartBuilder(generators, ".g.dart"), + {"$pkgName|lib/test.dart": src}, + generateFor: {'$pkgName|lib/test.dart'}, + outputs: {"$pkgName|lib/test.g.dart": decodedMatches(expected)}, + ); +}