diff --git a/packages/poptart_lex_gen/README.md b/packages/poptart_lex_gen/README.md index 6994e5015..fcd898756 100644 --- a/packages/poptart_lex_gen/README.md +++ b/packages/poptart_lex_gen/README.md @@ -12,12 +12,80 @@ depend on `poptart_lex`, which contains the generated output ready to use. dart pub add --dev poptart_lex_gen ``` +## Generate From The CLI + +Add a manifest that maps lexicon ID prefixes to generated Dart packages: + +```yaml +packages: + - name: poptart_lex + output: poptart_lex + roots: + - com.atproto. + - app.bsky. + - chat.bsky. + - tools.ozone. +``` + +Then run the generator: + +```sh +dart run poptart_lex_gen generate \ + --manifest lexicons/manifest.yaml \ + --lexicons lexicons \ + --packages packages +``` + +The default paths match that example, so projects with the same layout can use: + +```sh +dart run poptart_lex_gen generate +``` + +Generated packages that use `freezed` or `json_serializable` should run their +normal build step afterward: + +```sh +cd packages/poptart_lex +dart run build_runner build --delete-conflicting-outputs +``` + +### Multi-package Lexicons + +Each manifest package owns the lexicons whose IDs match one of its `roots`. +When more than one root matches, the generator uses the most specific prefix. +References across package boundaries are emitted as package imports, so a custom +lexicon package can reference shared AT Protocol or Bluesky types without being +generated into the same output package. + +`name` is the Dart package name used in generated package imports. `output` is +the directory under `--packages`; when omitted, it defaults to `name`. + +For example: + +```yaml +packages: + - name: poptart_lex + output: lex + roots: + - com.atproto. + - app.bsky. + - name: spark_lex + output: spark + roots: + - so.sprk. +``` + +With that manifest, `so.sprk.feed.post` is generated into +`packages/spark/lib`, while references to `app.bsky.richtext.facet` import +from `package:poptart_lex/...`. + +## Load Lexicons In Dart + ```dart import 'package:poptart_lex_gen/poptart_lex_gen.dart'; ``` -## Load Lexicons - ```dart import 'package:poptart_lex_gen/poptart_lex_gen.dart'; @@ -32,6 +100,9 @@ void main() { ## Generate Services +The CLI is the preferred entrypoint, but tooling can also build a config and run +the generator directly: + ```dart import 'package:poptart_lex_gen/poptart_lex_gen.dart'; @@ -44,9 +115,9 @@ void main() { namespaceRules: [ LexiconNamespaceRule( prefixes: ['app.', 'com.'], - homeDir: 'packages/poptart_lex', - exportCodegenPath: 'lib', - servicePackagePath: 'lib', + homeDir: 'packages/poptart_lex/lib', + exportCodegenPath: 'package:poptart_lex', + servicePackagePath: 'package:poptart_lex', rootPackageName: 'poptart_lex', ), ], diff --git a/packages/poptart_lex_gen/bin/poptart_lex_gen.dart b/packages/poptart_lex_gen/bin/poptart_lex_gen.dart new file mode 100644 index 000000000..f7081a194 --- /dev/null +++ b/packages/poptart_lex_gen/bin/poptart_lex_gen.dart @@ -0,0 +1,103 @@ +// Copyright (c) 2023-2026, Shinya Kato and Poptart contributors. +// All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// Dart imports: +import 'dart:io'; + +// Package imports: +import 'package:args/args.dart'; + +// Project imports: +import 'package:poptart_lex_gen/poptart_lex_gen.dart'; + +const _defaultManifestPath = 'lexicons/manifest.yaml'; +const _defaultLexiconsPath = 'lexicons'; +const _defaultPackagesPath = 'packages'; + +void main(final List args) { + if (args.isEmpty || args.first == '--help' || args.first == '-h') { + _printUsage(); + return; + } + + final command = args.first; + final commandArgs = args.sublist(1); + + switch (command) { + case 'generate': + _generate(commandArgs); + return; + default: + stderr.writeln('Unknown command: $command'); + _printUsage(); + exitCode = 64; + } +} + +void _generate(final List args) { + final parser = ArgParser() + ..addOption( + 'manifest', + abbr: 'm', + defaultsTo: _defaultManifestPath, + help: 'Path to the lexicon package ownership manifest.', + ) + ..addOption( + 'lexicons', + abbr: 'l', + defaultsTo: _defaultLexiconsPath, + help: 'Path to lexicon JSON files.', + ) + ..addOption( + 'packages', + abbr: 'p', + defaultsTo: _defaultPackagesPath, + help: 'Path to the Dart packages directory.', + ) + ..addFlag( + 'help', + abbr: 'h', + negatable: false, + help: 'Show generate command help.', + ); + + late final ArgResults results; + try { + results = parser.parse(args); + } on FormatException catch (error) { + stderr.writeln(error.message); + stderr.writeln(); + stderr.writeln(parser.usage); + exitCode = 64; + return; + } + + if (results.flag('help')) { + stdout.writeln('Usage: dart run poptart_lex_gen generate [options]'); + stdout.writeln(); + stdout.writeln(parser.usage); + return; + } + + try { + final config = lexGenConfigFromManifest( + manifestPath: results.option('manifest')!, + lexiconsPath: results.option('lexicons')!, + packagesPath: results.option('packages')!, + ); + + ServiceGen(config: config).execute(); + } catch (error, stackTrace) { + stderr.writeln('poptart_lex_gen generate failed: $error'); + stderr.writeln(stackTrace); + exitCode = 1; + } +} + +void _printUsage() { + stdout.writeln('Usage: dart run poptart_lex_gen [options]'); + stdout.writeln(); + stdout.writeln('Commands:'); + stdout.writeln(' generate Generate Dart code from lexicon definitions.'); +} diff --git a/packages/poptart_lex_gen/lib/poptart_lex_gen.dart b/packages/poptart_lex_gen/lib/poptart_lex_gen.dart index 6f7167752..50b695831 100644 --- a/packages/poptart_lex_gen/lib/poptart_lex_gen.dart +++ b/packages/poptart_lex_gen/lib/poptart_lex_gen.dart @@ -1,3 +1,4 @@ export 'package:poptart_lex_gen/src/config.dart'; -export 'package:poptart_lex_gen/src/gen.dart'; export 'package:poptart_lex_gen/src/doc_loader.dart'; +export 'package:poptart_lex_gen/src/gen.dart'; +export 'package:poptart_lex_gen/src/manifest.dart'; diff --git a/packages/poptart_lex_gen/lib/src/config.dart b/packages/poptart_lex_gen/lib/src/config.dart index 4d37328b8..76b1eae7c 100644 --- a/packages/poptart_lex_gen/lib/src/config.dart +++ b/packages/poptart_lex_gen/lib/src/config.dart @@ -28,6 +28,15 @@ final class LexiconNamespaceRule { required this.rootPackageName, }); + String get packageDir { + const suffix = '/lib'; + if (homeDir.endsWith(suffix)) { + return homeDir.substring(0, homeDir.length - suffix.length); + } + + return homeDir; + } + bool matches(final String lexiconId) { for (final prefix in prefixes) { final normalized = _normalizePrefix(prefix); diff --git a/packages/poptart_lex_gen/lib/src/gen.dart b/packages/poptart_lex_gen/lib/src/gen.dart index 71938e9ed..865758ab8 100644 --- a/packages/poptart_lex_gen/lib/src/gen.dart +++ b/packages/poptart_lex_gen/lib/src/gen.dart @@ -1,7 +1,7 @@ // Project imports: +import 'build/lex_build_project.dart'; import 'commands/lex_command_generator.dart'; import 'commands/rule.dart' as command_rule; -import 'build/lex_build_project.dart'; import 'config.dart'; import 'utils.dart'; diff --git a/packages/poptart_lex_gen/lib/src/manifest.dart b/packages/poptart_lex_gen/lib/src/manifest.dart new file mode 100644 index 000000000..d170162ce --- /dev/null +++ b/packages/poptart_lex_gen/lib/src/manifest.dart @@ -0,0 +1,268 @@ +// Copyright (c) 2023-2026, Shinya Kato and Poptart contributors. +// All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// Dart imports: +import 'dart:io'; + +// Package imports: +import 'package:yaml/yaml.dart'; + +// Project imports: +import 'config.dart'; + +final class LexiconManifest { + final List packages; + final List externalPackages; + + const LexiconManifest({ + required this.packages, + this.externalPackages = const [], + }); + + List get services => + packages.expand((package) => package.roots).toSet().toList(); + + LexGenConfig toConfig({ + required final String lexiconsPath, + required final String packagesPath, + }) { + return LexGenConfig( + services: services, + packages: packages + .map((package) => '$packagesPath/${package.output}') + .toList(), + docsProvider: lexiconDocsProviderFromPaths([lexiconsPath]), + serviceRuleConfig: LexServiceRuleConfig( + namespaceRules: [ + for (final package in packages) + LexiconNamespaceRule( + prefixes: package.roots, + homeDir: '$packagesPath/${package.output}/lib', + exportCodegenPath: 'package:${package.name}', + servicePackagePath: 'package:${package.name}', + rootPackageName: package.name, + ), + for (final package in externalPackages) + LexiconNamespaceRule( + prefixes: package.roots, + homeDir: '', + exportCodegenPath: 'package:${package.name}', + servicePackagePath: 'package:${package.name}', + rootPackageName: package.name, + ), + ], + ), + ); + } + + static LexiconManifest load(final String path) { + final file = File(path); + if (!file.existsSync()) { + throw StateError('Lexicon manifest does not exist: $path'); + } + + final dynamic yaml = loadYaml( + file.readAsStringSync(), + sourceUrl: Uri.file(path), + ); + if (yaml is! YamlMap) { + throw FormatException('Lexicon manifest must be a YAML object', path); + } + + return LexiconManifest.fromYaml(yaml, sourceDescription: path); + } + + static LexiconManifest fromYaml( + final YamlMap yaml, { + final String sourceDescription = 'lexicon manifest', + }) { + final packagesYaml = yaml['packages']; + if (packagesYaml is! YamlList) { + throw FormatException( + 'Lexicon manifest must contain a packages list', + sourceDescription, + ); + } + + final packages = []; + for (final packageYaml in packagesYaml) { + if (packageYaml is! YamlMap) { + throw FormatException( + 'Each package entry must be a YAML object', + sourceDescription, + ); + } + + packages.add( + LexiconManifestPackage.fromYaml( + packageYaml, + sourceDescription: sourceDescription, + ), + ); + } + + if (packages.isEmpty) { + throw FormatException( + 'No packages found in lexicon manifest', + sourceDescription, + ); + } + + final externalPackages = []; + final externalPackagesYaml = yaml['externalPackages']; + if (externalPackagesYaml != null) { + if (externalPackagesYaml is! YamlList) { + throw FormatException( + 'Lexicon manifest externalPackages must be a list', + sourceDescription, + ); + } + + for (final packageYaml in externalPackagesYaml) { + if (packageYaml is! YamlMap) { + throw FormatException( + 'Each external package entry must be a YAML object', + sourceDescription, + ); + } + + externalPackages.add( + LexiconManifestExternalPackage.fromYaml( + packageYaml, + sourceDescription: sourceDescription, + ), + ); + } + } + + return LexiconManifest( + packages: List.unmodifiable(packages), + externalPackages: List.unmodifiable(externalPackages), + ); + } +} + +final class LexiconManifestPackage { + final String name; + final String output; + final List roots; + + const LexiconManifestPackage({ + required this.name, + required this.output, + required this.roots, + }); + + static LexiconManifestPackage fromYaml( + final YamlMap yaml, { + required final String sourceDescription, + }) { + final name = yaml['name']; + if (name is! String || name.trim().isEmpty) { + throw FormatException( + 'Lexicon package entries require a non-empty name', + sourceDescription, + ); + } + + final output = yaml['output']; + if (output != null && (output is! String || output.trim().isEmpty)) { + throw FormatException( + 'Lexicon package $name contains an invalid output', + sourceDescription, + ); + } + + final roots = _readRoots( + yaml, + name.trim(), + sourceDescription: sourceDescription, + ); + + return LexiconManifestPackage( + name: name.trim(), + output: output is String ? output.trim() : name.trim(), + roots: List.unmodifiable(roots), + ); + } +} + +final class LexiconManifestExternalPackage { + final String name; + final List roots; + + const LexiconManifestExternalPackage({ + required this.name, + required this.roots, + }); + + static LexiconManifestExternalPackage fromYaml( + final YamlMap yaml, { + required final String sourceDescription, + }) { + final name = yaml['name']; + if (name is! String || name.trim().isEmpty) { + throw FormatException( + 'External lexicon package entries require a non-empty name', + sourceDescription, + ); + } + + final roots = _readRoots( + yaml, + name.trim(), + sourceDescription: sourceDescription, + ); + + return LexiconManifestExternalPackage( + name: name.trim(), + roots: List.unmodifiable(roots), + ); + } +} + +List _readRoots( + final YamlMap yaml, + final String packageName, { + required final String sourceDescription, +}) { + final rootsYaml = yaml['roots']; + if (rootsYaml is! YamlList) { + throw FormatException( + 'Lexicon package $packageName must contain a roots list', + sourceDescription, + ); + } + + final roots = []; + for (final root in rootsYaml) { + if (root is! String || root.trim().isEmpty) { + throw FormatException( + 'Lexicon package $packageName contains an invalid root', + sourceDescription, + ); + } + + roots.add(root.trim()); + } + + if (roots.isEmpty) { + throw FormatException( + 'Lexicon package $packageName has no roots', + sourceDescription, + ); + } + + return roots; +} + +LexGenConfig lexGenConfigFromManifest({ + required final String manifestPath, + required final String lexiconsPath, + required final String packagesPath, +}) { + return LexiconManifest.load( + manifestPath, + ).toConfig(lexiconsPath: lexiconsPath, packagesPath: packagesPath); +} diff --git a/packages/poptart_lex_gen/lib/src/services/lex_entrypoint_generator.dart b/packages/poptart_lex_gen/lib/src/services/lex_entrypoint_generator.dart index 565301b42..5c262075b 100644 --- a/packages/poptart_lex_gen/lib/src/services/lex_entrypoint_generator.dart +++ b/packages/poptart_lex_gen/lib/src/services/lex_entrypoint_generator.dart @@ -66,6 +66,8 @@ final class _LexEntrypointGenerator { leaves[lexiconId] = _LeafEntrypoint( lexiconId: lexiconId, packageName: rule.getRootPackageName(lexiconId), + homeDir: rule.getHomeDir(lexiconId), + packageDir: rule.getPackageDir(lexiconId), publicPath: rule.getPublicFileDir(lexiconId), dependencies: dependencies.toList()..sort(), hasMethodDescriptor: _hasMethodDescriptor(doc), @@ -110,7 +112,7 @@ final class _LexEntrypointGenerator { buffer.writeln("export '$relative';"); } - File('packages/${leaf.packageName}/lib/${leaf.publicPath}.dart') + File('${leaf.homeDir}/${leaf.publicPath}.dart') ..createSync(recursive: true) ..writeAsStringSync(buffer.toString()); } @@ -128,7 +130,6 @@ final class _LexEntrypointGenerator { } for (final entry in aggregates.entries) { - final packageName = entry.value.first.packageName; final buffer = StringBuffer()..writeln(kHeaderHint); final methodLeaves = entry.value @@ -155,7 +156,12 @@ final class _LexEntrypointGenerator { ); } - File(rule.getAggregateEntryPointPath(packageName, entry.key)) + File( + rule.getAggregateEntryPointPathForLexicon( + entry.value.first.lexiconId, + entry.key, + ), + ) ..createSync(recursive: true) ..writeAsStringSync(buffer.toString()); } @@ -169,6 +175,8 @@ final class _LexEntrypointGenerator { final class _LeafEntrypoint { final String lexiconId; final String packageName; + final String homeDir; + final String packageDir; final String publicPath; final List dependencies; final bool hasMethodDescriptor; @@ -176,6 +184,8 @@ final class _LeafEntrypoint { const _LeafEntrypoint({ required this.lexiconId, required this.packageName, + required this.homeDir, + required this.packageDir, required this.publicPath, required this.dependencies, required this.hasMethodDescriptor, diff --git a/packages/poptart_lex_gen/lib/src/services/lex_type_generator.dart b/packages/poptart_lex_gen/lib/src/services/lex_type_generator.dart index 30adc06bf..97e89171c 100644 --- a/packages/poptart_lex_gen/lib/src/services/lex_type_generator.dart +++ b/packages/poptart_lex_gen/lib/src/services/lex_type_generator.dart @@ -149,22 +149,25 @@ final class _LexTypeGenerator { void _cleanWorkspace() { for (final package in packages) { - final dir = Directory('packages/$package/lib/src/services/codegen'); + final packageDir = package.contains('/') ? package : 'packages/$package'; + final dir = Directory('$packageDir/lib/src/services/codegen'); if (dir.existsSync()) dir.deleteSync(recursive: true); - final toolsDir = Directory('packages/$package/lib/src/tools'); + final toolsDir = Directory('$packageDir/lib/src/tools'); if (toolsDir.existsSync()) toolsDir.deleteSync(recursive: true); for (final topLevel in const ['app', 'chat', 'com', 'tools']) { - final generatedDir = Directory('packages/$package/lib/$topLevel'); + final generatedDir = Directory('$packageDir/lib/$topLevel'); if (generatedDir.existsSync()) generatedDir.deleteSync(recursive: true); - final generatedFile = File('packages/$package/lib/$topLevel.dart'); + final generatedFile = File('$packageDir/lib/$topLevel.dart'); if (generatedFile.existsSync()) generatedFile.deleteSync(); } final $services = services.map((e) => e.split('.').join('_')).toList(); - final libDir = Directory('packages/$package/lib/'); + final libDir = Directory('$packageDir/lib/'); + if (!libDir.existsSync()) continue; + for (final file in libDir.listSync()) { if (file is! File) continue; diff --git a/packages/poptart_lex_gen/lib/src/services/object/repo_commit_handler.dart b/packages/poptart_lex_gen/lib/src/services/object/repo_commit_handler.dart index 264d31153..83bd101e5 100644 --- a/packages/poptart_lex_gen/lib/src/services/object/repo_commit_handler.dart +++ b/packages/poptart_lex_gen/lib/src/services/object/repo_commit_handler.dart @@ -26,7 +26,7 @@ final class RepoCommitHandler { import 'dart:async'; -import 'package:poptart_lex/com/atproto/sync/subscribe_repos.dart'; +import '${getPublicPackagePath('com.atproto.sync.subscribeRepos')}'; import 'package:poptart_core/poptart_core.dart'; $imports diff --git a/packages/poptart_lex_gen/lib/src/services/rule.dart b/packages/poptart_lex_gen/lib/src/services/rule.dart index 45024600f..458f7ec73 100644 --- a/packages/poptart_lex_gen/lib/src/services/rule.dart +++ b/packages/poptart_lex_gen/lib/src/services/rule.dart @@ -128,6 +128,13 @@ String getAggregateEntryPointPath( return 'packages/$rootPackageName/lib/$dir.dart'; } +String getAggregateEntryPointPathForLexicon( + final String lexiconId, + final String dir, +) { + return '${_getNamespaceRule(lexiconId).homeDir}/$dir.dart'; +} + String getPublicPackagePath(final String lexiconId) { return 'package:${getRootPackageName(lexiconId)}/${getPublicFileDir(lexiconId)}.dart'; } @@ -229,7 +236,7 @@ String getLexObjectNameFromRef( return getLexObjectName(parts[0], parts[1], mainVariants); } - return getLexObjectName(ref, '', mainVariants); + return getLexObjectName(ref, 'main', mainVariants); } String getLexObjectPackagePathFromRef( @@ -255,9 +262,9 @@ String getLexObjectPackagePathFromRef( } else { if (ref.contains('#')) { final $lexiconId = ref.split('#').first; - return '$relativePath/$fileNamePrefix${getPackageName($lexiconId)}.dart'; + return '$relativePath/${getPublicFileDir($lexiconId)}.dart'; } else { - return '$relativePath/$fileNamePrefix${getPackageName(ref)}.dart'; + return '$relativePath/${getPublicFileDir(ref)}.dart'; } } } @@ -350,6 +357,10 @@ String getRootPackageName(final String lexiconId) { return _getNamespaceRule(lexiconId).rootPackageName; } +String getPackageDir(final String lexiconId) { + return _getNamespaceRule(lexiconId).packageDir; +} + LexiconNamespaceRule _getNamespaceRule(final String lexiconId) { final config = _config; if (config == null) { diff --git a/packages/poptart_lex_gen/pubspec.yaml b/packages/poptart_lex_gen/pubspec.yaml index 3c69a72d3..6ffb84187 100644 --- a/packages/poptart_lex_gen/pubspec.yaml +++ b/packages/poptart_lex_gen/pubspec.yaml @@ -1,6 +1,6 @@ name: poptart_lex_gen description: Code generator for building Dart models and runtime descriptors from AT Protocol lexicons. -version: 0.1.0 +version: 0.2.0 resolution: workspace homepage: https://poptart.xyz documentation: https://poptart.xyz @@ -11,7 +11,9 @@ environment: sdk: ">=3.8.0 <4.0.0" dependencies: + args: ^2.7.0 poptart_lexicon: ^0.1.0 + yaml: ^3.1.3 dev_dependencies: lints: ^6.0.0 diff --git a/packages/poptart_lex_gen/test/src/manifest_test.dart b/packages/poptart_lex_gen/test/src/manifest_test.dart new file mode 100644 index 000000000..604d21f6c --- /dev/null +++ b/packages/poptart_lex_gen/test/src/manifest_test.dart @@ -0,0 +1,108 @@ +// Copyright (c) 2023-2026, Shinya Kato and Poptart contributors. +// All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// Dart imports: +import 'dart:io'; + +// Package imports: +import 'package:test/test.dart'; + +// Project imports: +import 'package:poptart_lex_gen/poptart_lex_gen.dart'; + +void main() { + group('LexiconManifest', () { + test('loads multiple output packages from YAML', () { + final manifestFile = _writeManifest(''' +packages: + - name: poptart_lex + output: lex + roots: + - app.bsky. + - com.atproto. + - name: sprk_lex + roots: + - so.sprk. +'''); + + final manifest = LexiconManifest.load(manifestFile.path); + + expect(manifest.packages, hasLength(2)); + expect(manifest.packages.first.name, 'poptart_lex'); + expect(manifest.packages.first.output, 'lex'); + expect(manifest.packages.first.roots, ['app.bsky.', 'com.atproto.']); + expect(manifest.services, ['app.bsky.', 'com.atproto.', 'so.sprk.']); + }); + + test('builds a LexGenConfig with package ownership rules', () { + final manifestFile = _writeManifest(''' +packages: + - name: poptart_lex + output: bluesky + roots: + - app.bsky. + - name: sprk_lex + roots: + - so.sprk. +externalPackages: + - name: poptart_atproto + roots: + - com.atproto. +'''); + + final config = lexGenConfigFromManifest( + manifestPath: manifestFile.path, + lexiconsPath: 'lexicons', + packagesPath: 'packages', + ); + + expect(config.services, ['app.bsky.', 'so.sprk.']); + expect(config.packages, ['packages/bluesky', 'packages/sprk_lex']); + + final rules = config.serviceRuleConfig.namespaceRules; + expect(rules.first.homeDir, 'packages/bluesky/lib'); + expect(rules.first.rootPackageName, 'poptart_lex'); + expect(rules[1].matches('so.sprk.feed.defs'), isTrue); + expect(rules.last.matches('com.atproto.repo.defs'), isTrue); + expect(rules.last.rootPackageName, 'poptart_atproto'); + expect( + config.serviceRuleConfig.namespaceRules[1].matches( + 'so.spark.feed.defs', + ), + isFalse, + ); + }); + + test('rejects package entries without roots', () { + final manifestFile = _writeManifest(''' +packages: + - name: poptart_lex +'''); + + expect( + () => LexiconManifest.load(manifestFile.path), + throwsA( + isA().having( + (error) => error.message, + 'message', + contains('must contain a roots list'), + ), + ), + ); + }); + }); +} + +File _writeManifest(final String content) { + final directory = Directory.systemTemp.createTempSync( + 'poptart_lex_gen_test_', + ); + addTearDown(() { + if (directory.existsSync()) directory.deleteSync(recursive: true); + }); + + final file = File('${directory.path}/manifest.yaml'); + file.writeAsStringSync(content); + return file; +} diff --git a/pubspec.yaml b/pubspec.yaml index d79c9661e..15e559f29 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -26,7 +26,7 @@ workspace: dependencies: github: ^9.12.0 http: ^1.4.0 - poptart_lex_gen: ^0.1.0 + poptart_lex_gen: ^0.2.0 poptart_lex: ^0.1.0 poptart_lexicon: ^0.1.0 pubspec: ^2.3.0 @@ -92,7 +92,7 @@ melos: run: | dart run ./scripts/gen_lexicon_docs.dart dart run ./scripts/gen_lexicon_ids.dart - dart run ./scripts/gen_codes.dart + dart run poptart_lex_gen generate cd packages/poptart_lex dart run build_runner build --delete-conflicting-outputs dart fix --apply diff --git a/scripts/gen_codes.dart b/scripts/gen_codes.dart index 7bd9ef3fe..370f8b4b5 100644 --- a/scripts/gen_codes.dart +++ b/scripts/gen_codes.dart @@ -3,7 +3,6 @@ // BSD-style license that can be found in the LICENSE file. import 'dart:async'; -import 'dart:convert'; import 'dart:io'; import 'dart:isolate'; @@ -218,96 +217,13 @@ LexGenConfig _buildLexGenConfig({ required String lexiconsPath, required String packagesPath, }) { - final manifest = _LexiconManifest.load(_lexiconManifestPath); - - return LexGenConfig( - services: manifest.services, - packages: manifest.packages.map((e) => e.name).toList(), - docsProvider: lexiconDocsProviderFromPaths([lexiconsPath]), - serviceRuleConfig: LexServiceRuleConfig( - namespaceRules: manifest.packages - .map( - (package) => LexiconNamespaceRule( - prefixes: package.roots, - homeDir: '$packagesPath/${package.name}/lib', - exportCodegenPath: 'package:${package.name}', - servicePackagePath: 'package:${package.name}', - rootPackageName: package.name, - ), - ) - .toList(), - ), + return lexGenConfigFromManifest( + manifestPath: _lexiconManifestPath, + lexiconsPath: lexiconsPath, + packagesPath: packagesPath, ); } -final class _LexiconManifest { - final List<_LexiconPackage> packages; - - const _LexiconManifest({required this.packages}); - - List get services => - packages.expand((package) => package.roots).toSet().toList(); - - static _LexiconManifest load(final String path) { - final file = File(path); - if (!file.existsSync()) { - throw StateError('Lexicon manifest does not exist: $path'); - } - - final packages = <_LexiconPackage>[]; - String? currentName; - final roots = []; - - void flush() { - if (currentName == null) return; - if (roots.isEmpty) { - throw FormatException('Package $currentName has no roots in $path'); - } - packages.add( - _LexiconPackage(name: currentName!, roots: List.unmodifiable(roots)), - ); - currentName = null; - roots.clear(); - } - - for (final rawLine in const LineSplitter().convert( - file.readAsStringSync(), - )) { - final line = rawLine.trim(); - if (line.isEmpty || line.startsWith('#') || line == 'packages:') { - continue; - } - if (line.startsWith('- name:')) { - flush(); - currentName = line.substring('- name:'.length).trim(); - continue; - } - if (line == 'roots:') continue; - if (line.startsWith('- ')) { - if (currentName == null) { - throw FormatException('Root declared before package name in $path'); - } - roots.add(line.substring(2).trim()); - } - } - - flush(); - - if (packages.isEmpty) { - throw FormatException('No packages found in lexicon manifest: $path'); - } - - return _LexiconManifest(packages: List.unmodifiable(packages)); - } -} - -final class _LexiconPackage { - final String name; - final List roots; - - const _LexiconPackage({required this.name, required this.roots}); -} - /// Main entry point for the script. Future main(List args) async { // Handle help request early