Generate code to help sorting db rows

This commit is contained in:
Ming Ming 2023-07-16 18:01:09 +08:00
parent 18f5a49db0
commit 9b3a755add
9 changed files with 211 additions and 2 deletions

View file

@ -1,3 +1,4 @@
library np_codegen;
export 'src/drift_table_sort_annotations.dart';
export 'src/np_log_annotations.dart';

View file

@ -0,0 +1,6 @@
class DriftTableSort {
const DriftTableSort(this.dbClass);
final String dbClass;
}

View file

@ -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

View file

@ -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");

View file

@ -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<DriftTableSort> {
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<OrderingTerm> 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;
}
}

View file

@ -17,6 +17,7 @@ dependencies:
dev_dependencies:
build_test: ^2.1.5
lints: any
path: any
test: any
code_gen_tester:
git:

View file

@ -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<TestSort> {
Iterable<OrderingTerm> 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;

View file

@ -0,0 +1,4 @@
import 'package:np_codegen/np_codegen.dart';
@DriftTableSort("")
class Foo {}

View file

@ -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<void> resolveCompilationUnit(String filePath) async {
final assetId = AssetId.parse('a|lib/${p.basename(filePath)}');
final files =
Directory(p.dirname(filePath)).listSync().whereType<File>().toList();
final fileMap = Map<String, String>.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<Generator> 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)},
);
}