Files
packages.dart/deprecated/flutter_ast/lib/cli/generator.dart
T
2026-01-15 17:36:22 -08:00

270 lines
7.7 KiB
Dart

import 'dart:convert';
import 'dart:io';
import 'package:flutter_ast/src/generator/parser.dart';
import 'package:flutter_ast_core/flutter_ast_core.dart';
import 'package:path/path.dart' as p;
import 'package:build_cli_annotations/build_cli_annotations.dart';
import "package:console/console.dart";
import 'package:flutter_ast/flutter_ast.dart';
import 'package:mustache_template/mustache_template.dart';
import 'package:recase/recase.dart';
part 'generator.g.dart';
@CliOptions()
class Options {
@CliOption(
abbr: 'p',
help: 'Required. The path to the Directory of widgets or Single File',
)
final String path;
@CliOption(
abbr: 'o',
help: 'The path to the Directory output.',
defaultsTo: 'build',
)
final String output;
@CliOption(
negatable: false,
help: 'Prints usage information.',
)
bool help;
Options(this.path, this.output);
}
const kBasePath =
'/Users/rodydavis/Developer/GitHub/protoypes/widget_studio/third_party/flutter_dynamic_widget/third_party/flutter_ast';
final cache = Cache();
// dart ./bin/flutter_gen.dart -p /Users/rodydavis/Developer/GitHub/protoypes/widget_studio/third_party/flutter/packages/flutter/lib/src/material
void main(List<String> args) {
Console.init();
final options = parseOptions(args);
final output = Directory(options.output);
final inputDir = Directory(options.path);
final inputFile = File(options.path);
final _paths = <String>[];
if (inputDir.existsSync()) {
_processDirectory(inputDir, inputDir, output, _paths);
} else if (inputFile.existsSync()) {
_paths.add(inputFile.path);
} else {
throw Exception('Not a valid path!');
}
final template = _getTemplate('widget');
var progress = ProgressBar(complete: _paths.length);
var i = 0;
final parser = GenParser();
for (final path in _paths) {
_processFile(output, File(path), template, parser);
progress.update(++i);
}
_getFile(output.path, 'base.dart').writeAsStringSync(_base);
final files = Directory(p.join(output.path, 'classes')).listSync();
final imports = files
.map((item) => "export 'classes/${p.basename(item.path)}';")
.toList();
imports.sort();
_getFile(output.path, 'index.dart').writeAsStringSync(imports.join('\n'));
final sb = StringBuffer();
sb.writeln("import 'index.dart';");
sb.writeln("import 'base.dart';");
sb.writeln();
sb.writeln("Map<String, BaseWidget> widgetLibrary = {");
final List<String> _names = [];
for (final file in files) {
final _result = cache.getCache(p.basename(file.path));
if (_result == null) continue;
for (final item in _result.file.classes) {
if (item.isValid) _names.add(item.name);
}
}
_names.sort();
for (final name in _names) {
sb.writeln(" '${name}': ${name}Base.readOnly(),");
}
sb.writeln('};');
_getFile(output.path, 'library.dart').writeAsStringSync(sb.toString());
// writeIndex(_paths, output);
print(parser.toString());
}
Template _getTemplate(String name) {
final _typePath = File('$kBasePath/templates/$name.dart.mustache');
final _template = Template(
_typePath.readAsStringSync(),
name: _typePath.path,
lenient: true,
htmlEscapeValues: false,
);
return _template;
}
void _processDirectory(
Directory dir,
Directory input,
Directory output,
List<String> paths,
) {
for (final file in dir.listSync(recursive: true)) {
if (file is Directory) {
_processDirectory(file, input, output, paths);
} else {
paths.add(p.relative(file.path));
}
}
}
void _processFile(
Directory output, File input, Template template, GenParser parser) {
final source = input.readAsStringSync();
parser.merge(source);
final result = parseSource(source, input.path);
cache.setCache(p.basename(input.path), result);
final _base = result.file;
if (_base?.classes != null && _base.classes.isNotEmpty) {
for (final item in _base.classes) {
if (item.isValid) {
if (!cache.addName(item.name)) continue;
final _template = _processClass(item, input);
final name = ReCase(item.name).snakeCase;
final _path = 'classes/' + name + '.dart';
final _file = _getFile(output.path, _path);
final _output = template.renderString(_template);
if (_output.trim().isEmpty) {
_file.deleteSync();
return;
}
_file.writeAsStringSync(_output);
}
}
}
}
Map<String, dynamic> _processClass(
DartClass item,
File input,
) {
if (!item.name.startsWith('_') &&
!input.path.contains('.g.dart') &&
!item.isAbstract) {
final _comments = item.comments?.map((e) => e.comment)?.toList() ?? [];
final _root = <String, dynamic>{
"imports": [
{'path': "import '../base.dart';"},
],
'class': item.name,
'constructors': [],
'fields': [],
'static': [],
'comments': _comments,
'description': _comments.join('/n'),
};
for (final sub in item.constructors) {
final isDefault = item.name == sub.name;
final _name = isDefault ? '${item.name}' : '${item.name}.${sub.name}';
if (_name.startsWith('_') || _name.contains('._')) continue;
_root['constructors'].add(buildConstructor(_name, sub));
}
for (final field in item.fields) {
if (field is DartField) {
_root['fields'].add({
'key': field?.name ?? '',
'type': field?.type ?? 'dynamic',
'value': field?.value?.value ?? 'null',
});
}
}
_root['constructor_divider'] =
List.from(_root['fields']).isEmpty ? '' : ':';
if (List.from(_root['constructors']).isNotEmpty) return _root;
}
return null;
}
Map<String, dynamic> buildConstructor(String name, DartConstructor item) {
return {
'name': name,
'widget': '$name()',
'json': jsonEncode({
'name': '$name',
'params': {},
}),
};
}
class Cache {
final _files = <String, DartResult>{};
final _names = <String>{};
void setCache(String path, DartResult result) => _files[path] = result;
DartResult getCache(String path) => _files[path];
bool addName(String name) => _names.add(name);
List<String> get name => _names.toList();
}
extension on DartClass {
String get extendedClasses {
final _extends = (this?.extendsClause ?? '').replaceAll('extends ', '');
if (_extends.isEmpty) return '';
return _extends;
}
bool get isValid {
if (this.name.startsWith('_')) return false;
if (this.isAbstract) return false;
if (extendedClasses.isEmpty) return false;
if ([
'StatelessWidget',
'StatefulWidget',
'MaterialButton',
'InheritedWidget',
'InheritedTheme',
'InlineSpan',
'RenderObjectWidget',
'BoxScrollView',
'ScrollView',
'SingleChildRenderObjectWidget',
].contains(extendedClasses)) {
return true;
}
return false;
}
}
File _getFile(String output, String filename) {
final metaData = File('$output/$filename');
if (!metaData.existsSync()) metaData.createSync(recursive: true);
return metaData;
}
String get _base => '''
import 'package:flutter/material.dart';
export 'package:flutter/material.dart';
export 'package:flutter/cupertino.dart' hide RefreshCallback;
abstract class BaseWidget extends ValueNotifier<Map<String, dynamic>> implements Base {
BaseWidget() : super({});
Map<String, Object> flavors(BuildContext context);
List<String> get constructors;
Map<String, String> get properties;
String get constructor;
Object render(BuildContext context) => flavors(context)[constructor];
bool isWidget(BuildContext context) => render(context) is Widget;
dynamic getProperty(String key);
setProperty(String key, dynamic value);
}
abstract class Base {
Map<String, dynamic> toJson();
String get description;
}
''';