#! /bin/sh
EXE=tools/meta_tool
test ! -x $EXE && ./ReCreateMetaTool
-$EXE $*
+cd metatool
+../$EXE $*
#! /bin/bash
-APP=meta_tool
-dart compile exe bin/$APP.dart -o tools/$APP
+cd metatool
+./Compile
+
+++ /dev/null
-import 'dart:io';
-
-import 'package:dart_bones/dart_bones.dart';
-import 'package:exhibition/meta/module_meta_data.dart';
-import 'package:exhibition/meta/modules.dart';
-import 'package:path/path.dart';
-
-/// Converts a [className] into a file name using Dart conventions.
-/// Example: "UserData" is converted to "user_data"
-String classToFilename(String className) {
- String upperCase = className.toUpperCase();
- String rc = '';
- for (var ix = 0; ix < className.length; ix++) {
- if (className[ix] == upperCase[ix] && ix > 0) {
- rc += '_';
- }
- rc += className[ix];
- }
- return rc.toLowerCase();
-}
-
-/// Converts a [filename] into a class name using Dart conventions.
-/// Example: "user_data.dart" is converted to "UserData"
-String filenameToClass(String filename) {
- final parts = basenameWithoutExtension(filename).split('_');
- String rc = '';
- for (var part in parts) {
- if (part.isNotEmpty) {
- rc += part[0].toUpperCase() + part.substring(1);
- }
- }
- return rc;
-}
-
-class Generator {
- BaseLogger logger = globalLogger;
- Generator(this.logger);
-
- /// Appends a [string] at the end of a [buffer].
- /// If [buffer] is null a new instance is created.
- /// If the last line in [buffer] has a length greater than [maxLength]
- /// a newline is inserted and [indent] spaces.
- /// If [separator] is not null that is inserted above the [string].
- /// Returns the buffer (for chaining).
- StringBuffer addToBuffer(String string,
- {required int maxLength,
- String? separator,
- int indent = 0,
- StringBuffer? buffer}) {
- buffer ??= StringBuffer();
- if (separator != null) {
- buffer.write(separator);
- }
- final last = buffer.toString().lastIndexOf('\n');
- if (buffer.length - (last < 0 ? 0 : last) + string.length > maxLength) {
- buffer.write('\n' + (' ' * indent));
- }
- buffer.write(string);
- return buffer;
- }
-
- ModuleMetaData? checkModule(List<String> args, int index) {
- ModuleMetaData? rc;
- if (index >= args.length) {
- logger.error('+++ too few arguments. Missing MODULE.');
- } else if ((rc = moduleByName(args[index])) == null) {
- logger.error('+++ unknown module: ${args[index]}');
- rc = null;
- }
- return rc;
- }
-
- /// Returns a DDL statement creating the database table of the [module].
- String createDbTable(ModuleMetaData module) {
- final tableName = module.tableName;
- final list = module.propertyList;
- final buffer = StringBuffer();
- buffer.write('-- DO NOT CHANGE. This file is created by the meta_tool\n');
- buffer.write('CREATE TABLE $tableName (\n');
- String? primary;
- for (var item in list) {
- if (item.options.contains('primary')) {
- primary = item.columnName;
- }
- buffer.write(' ${item.columnName}');
- buffer.write(' ' + module.mySqlType(item.dataType, item.size));
- buffer.write(module.dbOptions(item) + ',\n');
- }
- buffer.write(' PRIMARY KEY ($primary)\n');
- buffer.write(');\n');
- return buffer.toString();
- }
-
- /// Returns a Dart class definition of the [module].
- String createModuleData(ModuleMetaData module) {
- final buffer = StringBuffer();
- PropertyMetaData? primary = module.primaryOf();
- buffer.writeln('// DO NOT CHANGE. This file is created by the meta_tool');
- buffer.writeln("import '../../base/defines.dart';");
- buffer.writeln("import '../../base/helper.dart';");
- buffer.writeln("import '../../persistence/data_record.dart';");
- final type = 'int';
- buffer.writeln(
- 'class ${module.moduleNameSingular}Data extends DataRecord<$type>{');
- for (var item in module.propertyList) {
- buffer.writeln(' ' + module.dartType(item.dataType) + '? ${item.name};');
- }
- buffer.writeln(' ${module.moduleNameSingular}Data({');
- String separator = '';
- for (var item in module.propertyList) {
- buffer.write('$separator this.${item.name}');
- separator = ',';
- }
- buffer.writeln('});');
- buffer.writeln(' ${module.moduleNameSingular}Data.createFromMap(DataMap map) {');
- buffer.writeln(' fromMap(map);');
- buffer.writeln(' }');
- buffer.writeln(' @override');
- buffer.writeln(' void fromMap(DataMap map) {');
- for (var item in module.propertyList) {
- final name = item.columnName;
- final type = item.dataType.toString();
- //id = data.containsKey('id') ? int.tryParse(data['id']) : null;
- buffer.writeln(
- " ${item.name} = map.containsKey('$name') ? valueOf($type, "
- "map['$name']) : null;");
- }
- buffer.writeln(' }');
- final name = primary == null ? 'id' : primary.name;
- final name2 = primary == null ? 'id' : primary.columnName;
- buffer.write(''' @override
- int keyOf() {
- return $name ?? 0;
- }
- @override
- String nameOfKey(){
- return '$name2';
- }
-''');
- buffer.writeln(' static DataType? dataTypeOf(String name) {');
- buffer.writeln(' DataType? rc;');
- buffer.writeln(' switch(name){');
- for (var item in module.propertyList) {
- buffer.writeln(" case '${item.name}':");
- buffer.writeln(' rc = ${item.dataType};');
- buffer.writeln(' break;');
- }
- buffer.write(''' default:
- break;
- }
- return rc;
- }
-''');
- buffer.write(''' @override
- DataMap toMap({DataMap? map, bool clear = true}) {
- map ??= DataMap();
- if (clear) {
- map.clear();
- }
-''');
- for (var item in module.propertyList) {
- final name1 = item.columnName;
- final name2 = item.name;
- buffer.writeln(" map['$name1'] = $name2;");
- }
- buffer.write(''' return map;
- }
-}
-''');
- return buffer.toString();
- }
-
- /// Creates the file modules.dart.
- String createModules() {
- final buffer = StringBuffer();
- buffer.write('// DO NOT CHANGE. This file is created by the meta_tool\n');
- buffer.write("import 'module_meta_data.dart';\n");
- final modules = <String>[];
- final files = <String>[];
- final fileOfModule = <String, String>{};
-
- for (var item in Directory('lib/meta').listSync()) {
- final name = basename(item.path);
- if (name.endsWith('_meta.dart')) {
- final moduleName = filenameToClass(name.replaceFirst('_meta.dart', ''));
- fileOfModule[moduleName] = name;
- modules.add(moduleName);
- files.add(name);
- }
- }
- modules.sort();
- files.sort();
- for (var file in files) {
- buffer.write("import '$file';\n");
- }
- buffer.write('''
-/// Returns the meta data of the module given by [name].
-/// Returns null if not found.
-ModuleMetaData? moduleByName(String name) {
- ModuleMetaData? rc;
- switch (name) {
-''');
- for (var module in modules) {
- buffer.write(" case '$module':\n");
- final className = findClass(fileOfModule[module]!);
- buffer.write(" rc = $className();\n");
- buffer.write(" break;\n");
- }
- buffer.write(''' default:
- break;
- }
- return rc;
-}
-/// Returns the module names as string list.
-List<String> moduleNames(){
- return [
-''');
- for (var module in modules) {
- buffer.write(" '$module',\n");
- }
- buffer.write(''' ];
-}
-''');
- return buffer.toString();
- }
-
- /// Returns the SQL statements for insert, update, delete...
- /// for a given [module].
- /// This yaml file is a configuration for the rest_server.
- String createSqlStatements(ModuleMetaData module) {
- final moduleName = module.moduleName;
- final tableName = module.tableName;
- final list = module.propertyList;
- final buffer = StringBuffer();
- buffer.write('---\n');
- buffer.write('# DO NOT CHANGE. This file is created by the meta_tool\n');
- buffer.write('''# SQL statements of the module "$moduleName":\n
-module: $moduleName
-list:
- type: list
- parameters: []
- sql: "select * from $tableName;"
-byId:
- type: record
- parameters: [ "${list[0].name}" ]
- sql: "select * from $tableName where ${list[0].columnName}=:${list[0].name};"
-delete:
- type: delete
- parameters: [ "${list[0].name}" ]
- sql: "delete * from $tableName where ${list[0].columnName}=:${list[0].name};"
-update:
- type: update
-''');
- var items = module.standardColumns('changedBy');
- var parameters = addToBuffer(' parameters: [', maxLength: 80);
- var assignments = addToBuffer(' ', maxLength: 80);
- var first = true;
- for (var item in items) {
- addToBuffer('":${item.name}"',
- maxLength: 80,
- separator: first ? null : ',',
- indent: 4,
- buffer: parameters);
- addToBuffer('${item.columnName}=:${item.name}',
- maxLength: 80,
- separator: first ? null : ',',
- indent: 4,
- buffer: assignments);
- first = false;
- }
- parameters.write(']');
- var item = module.properties['changed']!;
- addToBuffer('${item.columnName}=NOW()',
- maxLength: 80, separator: ',', indent: 4, buffer: assignments);
- // parameters: [":id", ":name", ":displayname", ":email", ":changedby"]
- buffer.writeln(parameters);
- buffer.write(' sql: "UPDATE $tableName SET\n');
- buffer.writeln(assignments);
- //user_name=:name, user_displayname=:displayname, user_email=:email, user_changed=NOW(), user_changedby=:changedby
- buffer.write(' WHERE ${list[0].columnName}=:${list[0].name};"\n');
-
- items = module.standardColumns('createdBy');
- parameters = addToBuffer(' parameters: [', maxLength: 80);
- final sql1 = addToBuffer(' sql: "INSERT INTO $tableName(', maxLength: 80);
- final sql2 = addToBuffer(' VALUES(', maxLength: 80);
- first = true;
- for (var item in items) {
- addToBuffer('":${item.name}"',
- maxLength: 80,
- separator: first ? null : ',',
- indent: 4,
- buffer: parameters);
- addToBuffer('${item.columnName}',
- maxLength: 80,
- separator: first ? null : ',',
- indent: 6,
- buffer: sql1);
- addToBuffer(':${item.name}',
- maxLength: 80,
- separator: first ? null : ',',
- indent: 6,
- buffer: sql2);
- first = false;
- }
- parameters.write(']');
- item = module.properties['created']!;
- addToBuffer('${item.columnName})',
- maxLength: 80, separator: ',', indent: 4, buffer: sql1);
- addToBuffer('NOW());"',
- maxLength: 80, separator: ',', indent: 4, buffer: sql2);
- // parameters: [":id", ":name", ":displayname", ":email", ":changedby"]
- buffer.write('''insert:
- type: insert
-''');
- buffer.writeln(parameters);
- buffer.writeln(sql1);
- buffer.writeln(sql2);
- return buffer.toString();
- }
-
- /// Finds the class name of a module meta class given by the file [node].
- String findClass(String node) {
- final contents = File('lib/meta/$node').readAsStringSync();
- RegExpMatch? match;
- String rc;
- if ((match = RegExp(r'class\s+(\w+)\s').firstMatch(contents)) != null) {
- rc = match!.group(1)!;
- } else {
- logger.error('+++ missing class in $node');
- rc = filenameToClass(node.replaceFirst('.dart', ''));
- }
- return rc;
- }
-
- /// Replaces print().
- void out(String line) {
- logger.log(line);
- }
-
- /// Generates all modules defined in the meta data.
- void updateModules(Generator generator) {
- writeFile('lib/meta/modules.dart', createModules());
- final modules = moduleNames();
- for (var name in modules) {
- ModuleMetaData? module = moduleByName(name);
- if (module == null) {
- logger.error('+++ unknown module: $name');
- } else {
- String filename =
- classToFilename(module.moduleNameSingular) + '_data.dart';
- final directory = name.toLowerCase();
- writeFile('lib/page/$directory/$filename',
- generator.createModuleData(module));
- }
- }
- }
-
- /// Generates the modules.dart.
- void updateModulesList(Generator generator) {
- writeFile('lib/meta/modules.dart', createModules());
- }
-
- /// Generates the Sql statement file for each module defined in the meta data.
- void updateSql(Generator generator) {
- final modules = moduleNames();
- for (var name in modules) {
- ModuleMetaData? module = moduleByName(name);
- if (module == null) {
- logger.error('+++ unknown module: $name');
- } else {
- logger.log('current directory: ${Directory.current.path}');
- String filename = 'rest_server/data/sql/${name.toLowerCase()}.sql.yaml';
- writeFile(filename, generator.createSqlStatements(module));
- }
- }
- }
-
- /// Writes a given [contents] into a file named [filename];
- void writeFile(String filename, String contents) {
- logger.log('creating $filename ...');
- final file = File(filename);
- final parent = Directory(dirname(filename));
- if (!parent.existsSync()) {
- parent.createSync(recursive: true);
- }
- file.writeAsStringSync(contents);
- }
-}
+++ /dev/null
-import 'package:dart_bones/dart_bones.dart';
-import 'package:exhibition/base/i18n_io.dart';
-import 'package:exhibition/meta/module_meta_data.dart';
-import 'package:exhibition/meta/modules.dart';
-
-import 'generator.dart';
-
-void main(List<String> args) {
- final logger = MemoryLogger(LEVEL_DETAIL);
- final generator = Generator(logger);
- I18nIo.internal('data/i18n', logger);
- if (args.isEmpty) {
- usage(generator);
- generator.out('+++ missing arguments.');
- } else {
- ModuleMetaData? metaData;
- switch (args[0]) {
- case 'all-modules':
- generator.out(moduleNames().join('\n'));
- break;
- case 'print-modules-list':
- generator.out(generator.createModules());
- break;
- case 'print-table':
- if ((metaData = generator.checkModule(args, 1)) != null) {
- generator.out(generator.createDbTable(metaData!));
- }
- break;
- case 'print-sql':
- if ((metaData = generator.checkModule(args, 1)) != null) {
- generator.out(generator.createSqlStatements(metaData!));
- }
- break;
- case 'print-data':
- if ((metaData = generator.checkModule(args, 1)) != null) {
- generator.out(generator.createModuleData(metaData!));
- }
- break;
- case 'update-modules':
- generator.updateModules(generator);
- break;
- case 'update-modules-list':
- generator.updateModulesList(generator);
- break;
- case 'update-sql':
- generator.updateSql(generator);
- break;
- default:
- usage(generator);
- generator.out('+++ unknown MODE: ${args[0]}.');
- break;
- }
- }
-}
-
-void usage(Generator generator) {
- generator.out('''Usage: meta_tool MODE MODULE
- Generate code specified by MODE for the module MODULE.
-MODE:
- all-modules
- Prints the module names.
- print-modules-list
- Prints the file lib/meta/modules.dart.
- That works without of meta-data: It can be called before
- creating a the meta_tool.
- print-table MODULE
- Prints the SQL table definition of MODULE.
- print-data MODULE
- Creates the data storage class of MODULE
- print-sql MODULE
- Creates the SQL statements (for the rest_server) of MODULE
- update-modules-list
- Generates the modules.dart file in the correct directory.
- That works without of meta-data: It can be called before
- creating a the meta_tool.
- update-modules
- Generates all module files depending on the meta data.
- update-sql
- Generates all Sql statement files depending on the meta data.
-MODULE:
- The name of the module, e.g. "Users"
-Example:
-meta_tool print-table Users
-''');
-}
enum EventSource { undef, localData, remoteData }
enum EventCardinality { undef, record, list }
enum DataType {
+ undefined,
bool,
currency,
date,
import 'defines.dart';
+String toCamelCase(String name) {
+ final rc = name.isEmpty ? '' : (name[0].toUpperCase() + name.substring(1));
+ return rc;
+}
+
/// Returns the [dataType] specific value of [data].
dynamic valueOf(DataType dataType, String data) {
dynamic rc;
case DataType.string:
rc = data;
break;
+ case DataType.undefined:
+ throw FormatException('valueOf(): data type is undefined');
}
return rc;
}
-import 'package:flutter/material.dart';
+import 'package:dart_bones/dart_bones.dart';
import '../base/defines.dart';
-typedef MenuItemBuilder = List<DropdownMenuItem<T>> Function<T>(
+typedef MetaMenuItemBuilder = List<dynamic> Function<T>(
PropertyMetaData propertyMetaData);
/// Describes a button of a page.
case DataType.string:
rc = 'String';
break;
+ case DataType.undefined:
+ throw FormatException('dartType(): data type is undefined');
}
return rc;
}
rc = 'LARGE TEXT';
}
break;
+ case DataType.undefined:
+ throw FormatException('mySqlType(): data type is undefined');
}
return rc;
}
+ /// Will be called after the constructor.
+ ///
+ /// Override it if needed.
+ void onInitialized() {
+ // do nothing
+ }
+
+ /// Returns the meta data of a page given by its [name] or null if missing.
+ PageMetaData? pageByName(String name) {
+ final rc =
+ pageList.firstWhere((element) => element.name == name, orElse: null);
+ return rc;
+ }
+
/// Returns the primary key of the relation.
PropertyMetaData? primaryOf() {
final rc = propertyList.firstWhere(
return rc;
}
+ /// Returns the meta data of a property given by its [name] or null if missing.
+ PropertyMetaData? propertyByName(String name) {
+ final rc = propertyList.singleWhere((element) => element.name == name,
+ orElse: null);
+ return rc;
+ }
+
/// Returns the properties that are not in [metaColumns].
/// : if the name of the property is [included] the property is always part of
/// the result.
}
class PageMetaData {
- final String name;
+ String name = '';
+ final String label;
final PageType pageType;
ModuleMetaData module = DummyModule();
- List<FieldMetaData>? fields;
- PageMetaData(
- this.name,
- this.pageType,
- this.fields,
- );
+ final List<FieldMetaData>? field = [];
+ PageMetaData(this.label, this.pageType, {String name = ''}) {
+ if (name.isEmpty) {
+ name = enumToString(pageType);
+ }
+ this.name = name;
+ }
}
enum PageType { create, custom, delete, edit, list }
final String options;
final DisplayType displayType;
final DataType dataType;
- MenuItemBuilder? menuItemBuilder;
+ MetaMenuItemBuilder? menuItemBuilder;
/// The size if dataType is DataType.string.
final int size;
'changedBy', i18n.tr('Changed by'), DataType.string, ':hidden:',
size: 32),
], [
- PageMetaData('New Role', PageType.create, []),
- PageMetaData('Change Role', PageType.edit, []),
- PageMetaData('Roles Overview', PageType.list, []),
+ PageMetaData('New Role', PageType.create),
+ PageMetaData('Change Role', PageType.edit),
+ PageMetaData('Roles Overview', PageType.list),
]);
+ @override
+ void onInitialized() {}
}
'changedBy', i18n.tr('Changed by'), DataType.string, ':hidden:',
size: 32),
], [
- PageMetaData('New User', PageType.create, []),
- PageMetaData('Change User', PageType.edit, []),
- PageMetaData('Delete User', PageType.delete, []),
- PageMetaData('Users Overview', PageType.list, []),
+ PageMetaData('New User', PageType.create),
+ PageMetaData('Change User', PageType.edit),
+ PageMetaData('Delete User', PageType.delete),
+ PageMetaData('Users Overview', PageType.list),
]);
}
+// DO NOT CHANGE. This file is created by the meta_tool!
import 'package:flutter/material.dart';
import '../setting/global_data.dart';
+import 'page_collection_custom.dart';
-import 'users/users_edit_page.dart';
+import 'roles/create_role_page.dart';
+import 'roles/edit_role_page.dart';
+import 'roles/list_role_page.dart';
+import 'users/create_user_page.dart';
+import 'users/edit_user_page.dart';
+import 'users/delete_user_page.dart';
+import 'users/list_user_page.dart';
/// Manages all meta data driven pages of the program.
class PageCollection {
StatefulWidget? rc;
final globalData = GlobalData();
switch (route) {
+ case '/roles/create':
+ rc = CreateRolePage(globalData);
+ break;
+ case '/roles/edit':
+ rc = EditRolePage(globalData);
+ break;
+ case '/roles/list':
+ rc = ListRolePage(globalData);
+ break;
+ case '/users/create':
+ rc = CreateUserPage(globalData);
+ break;
case '/users/edit':
- rc = UsersEditPage(globalData);
+ rc = EditUserPage(globalData);
+ break;
+ case '/users/delete':
+ rc = DeleteUserPage(globalData);
+ break;
+ case '/users/list':
+ rc = ListUserPage(globalData);
+ break;
+ default:
+ rc = customPageByRoute(route, globalData);
break;
}
if (rc != null) {
--- /dev/null
+// This file is created by the meta_tool. But it can be customized.
+// It will never overridden by the meta_tool.
+import 'package:flutter/material.dart';
+import '../setting/global_data.dart';
+import 'info_page.dart';
+import 'log_page.dart';
+
+/// Creates a page defined by a [route].
+///
+/// Note: only pages without meta data definitions should be handled here.
+StatefulWidget? customPageByRoute(String route, GlobalData globalData) {
+ StatefulWidget? rc;
+ switch (route) {
+ case '/info':
+ rc = InfoPage(globalData);
+ break;
+ case '/log':
+ rc = LogPage(globalData);
+ break;
+ default:
+ break;
+ }
+ return rc;
+}
--- /dev/null
+// This file is created by the meta_tool. But it can be customized.
+// It will never overridden by the meta_tool.
+import 'package:flutter/material.dart';
+
+import '../../base/i18n.dart';
+import '../../setting/global_data.dart';
+import '../../widget/attended_page.dart';
+import '../../widget/widget_form.dart';
+import 'create_role_page.dart';
+
+final i18n = I18N();
+
+class CreateRoleCustom extends State<CreateRolePage> {
+ final globalData = GlobalData();
+ AttendedPage? attendedPage;
+
+ @override
+ Widget build(BuildContext context) {
+ final padding = 16.0;
+ final rc = Scaffold(
+ appBar: globalData.appBarBuilder(i18n.tr('Change Role data')),
+ drawer: globalData.drawerBuilder(context),
+ body: SafeArea(
+ child: WidgetForm.flexibleGrid(attendedPage!.attendedWidgets(),
+ screenWidth: attendedPage!.pageStates.screenWidth,
+ padding: padding)));
+ return rc;
+ }
+
+ @override
+ void initState() {
+ super.initState();
+ }
+}
--- /dev/null
+// DO NOT CHANGE. This file is created by the meta_tool!
+import 'package:flutter/material.dart';
+
+import '../../meta/roles_meta.dart';
+import '../../setting/global_data.dart';
+import '../../widget/attended_page.dart';
+import 'create_role_custom.dart';
+
+class CreateRolePage extends StatefulWidget {
+ final GlobalData globalData;
+ final PageStates pageStates = PageStates();
+ CreateRolePage(this.globalData) : super();
+ @override
+ _CreateRolePageState createState() {
+ final rc = _CreateRolePageState();
+ rc.attendedPage =
+ AttendedPage(this, rc, globalData, RoleMeta.instance, pageStates);
+ pageStates.attendedPage = rc.attendedPage;
+ return rc;
+ }
+}
+
+class _CreateRolePageState extends CreateRoleCustom {
+ @override
+ void didChangeDependencies() {
+ final size = MediaQuery.of(context).size;
+ attendedPage!.pageStates.screenWidth = size.width;
+ attendedPage!.pageStates.screenHeight = size.height;
+ super.didChangeDependencies();
+ }
+}
--- /dev/null
+// This file is created by the meta_tool. But it can be customized.
+// It will never overridden by the meta_tool.
+import 'package:flutter/material.dart';
+
+import '../../base/i18n.dart';
+import '../../setting/global_data.dart';
+import '../../widget/attended_page.dart';
+import '../../widget/widget_form.dart';
+import 'edit_role_page.dart';
+
+final i18n = I18N();
+
+class EditRoleCustom extends State<EditRolePage> {
+ final globalData = GlobalData();
+ AttendedPage? attendedPage;
+
+ @override
+ Widget build(BuildContext context) {
+ final padding = 16.0;
+ final rc = Scaffold(
+ appBar: globalData.appBarBuilder(i18n.tr('Change Role data')),
+ drawer: globalData.drawerBuilder(context),
+ body: SafeArea(
+ child: WidgetForm.flexibleGrid(attendedPage!.attendedWidgets(),
+ screenWidth: attendedPage!.pageStates.screenWidth,
+ padding: padding)));
+ return rc;
+ }
+
+ @override
+ void initState() {
+ super.initState();
+ }
+}
--- /dev/null
+// DO NOT CHANGE. This file is created by the meta_tool!
+import 'package:flutter/material.dart';
+
+import '../../meta/roles_meta.dart';
+import '../../setting/global_data.dart';
+import '../../widget/attended_page.dart';
+import 'edit_role_custom.dart';
+
+class EditRolePage extends StatefulWidget {
+ final GlobalData globalData;
+ final PageStates pageStates = PageStates();
+ EditRolePage(this.globalData) : super();
+ @override
+ _EditRolePageState createState() {
+ final rc = _EditRolePageState();
+ rc.attendedPage =
+ AttendedPage(this, rc, globalData, RoleMeta.instance, pageStates);
+ pageStates.attendedPage = rc.attendedPage;
+ return rc;
+ }
+}
+
+class _EditRolePageState extends EditRoleCustom {
+ @override
+ void didChangeDependencies() {
+ final size = MediaQuery.of(context).size;
+ attendedPage!.pageStates.screenWidth = size.width;
+ attendedPage!.pageStates.screenHeight = size.height;
+ super.didChangeDependencies();
+ }
+}
--- /dev/null
+// This file is created by the meta_tool. But it can be customized.
+// It will never overridden by the meta_tool.
+import 'package:flutter/material.dart';
+
+import '../../base/i18n.dart';
+import '../../setting/global_data.dart';
+import '../../widget/attended_page.dart';
+import '../../widget/widget_form.dart';
+import 'list_role_page.dart';
+
+final i18n = I18N();
+
+class ListRoleCustom extends State<ListRolePage> {
+ final globalData = GlobalData();
+ AttendedPage? attendedPage;
+
+ @override
+ Widget build(BuildContext context) {
+ final padding = 16.0;
+ final rc = Scaffold(
+ appBar: globalData.appBarBuilder(i18n.tr('Change Role data')),
+ drawer: globalData.drawerBuilder(context),
+ body: SafeArea(
+ child: WidgetForm.flexibleGrid(attendedPage!.attendedWidgets(),
+ screenWidth: attendedPage!.pageStates.screenWidth,
+ padding: padding)));
+ return rc;
+ }
+
+ @override
+ void initState() {
+ super.initState();
+ }
+}
--- /dev/null
+// DO NOT CHANGE. This file is created by the meta_tool!
+import 'package:flutter/material.dart';
+
+import '../../meta/roles_meta.dart';
+import '../../setting/global_data.dart';
+import '../../widget/attended_page.dart';
+import 'list_role_custom.dart';
+
+class ListRolePage extends StatefulWidget {
+ final GlobalData globalData;
+ final PageStates pageStates = PageStates();
+ ListRolePage(this.globalData) : super();
+ @override
+ _ListRolePageState createState() {
+ final rc = _ListRolePageState();
+ rc.attendedPage =
+ AttendedPage(this, rc, globalData, RoleMeta.instance, pageStates);
+ pageStates.attendedPage = rc.attendedPage;
+ return rc;
+ }
+}
+
+class _ListRolePageState extends ListRoleCustom {
+ @override
+ void didChangeDependencies() {
+ final size = MediaQuery.of(context).size;
+ attendedPage!.pageStates.screenWidth = size.width;
+ attendedPage!.pageStates.screenHeight = size.height;
+ super.didChangeDependencies();
+ }
+}
--- /dev/null
+// This file is created by the meta_tool. But it can be customized.
+// It will never overridden by the meta_tool.
+import 'package:flutter/material.dart';
+
+import '../../base/i18n.dart';
+import '../../setting/global_data.dart';
+import '../../widget/attended_page.dart';
+import '../../widget/widget_form.dart';
+import 'create_user_page.dart';
+
+final i18n = I18N();
+
+class CreateUserCustom extends State<CreateUserPage> {
+ final globalData = GlobalData();
+ AttendedPage? attendedPage;
+
+ @override
+ Widget build(BuildContext context) {
+ final padding = 16.0;
+ final rc = Scaffold(
+ appBar: globalData.appBarBuilder(i18n.tr('Change User data')),
+ drawer: globalData.drawerBuilder(context),
+ body: SafeArea(
+ child: WidgetForm.flexibleGrid(attendedPage!.attendedWidgets(),
+ screenWidth: attendedPage!.pageStates.screenWidth,
+ padding: padding)));
+ return rc;
+ }
+
+ @override
+ void initState() {
+ super.initState();
+ }
+}
--- /dev/null
+// DO NOT CHANGE. This file is created by the meta_tool!
+import 'package:flutter/material.dart';
+
+import '../../meta/users_meta.dart';
+import '../../setting/global_data.dart';
+import '../../widget/attended_page.dart';
+import 'create_user_custom.dart';
+
+class CreateUserPage extends StatefulWidget {
+ final GlobalData globalData;
+ final PageStates pageStates = PageStates();
+ CreateUserPage(this.globalData) : super();
+ @override
+ _CreateUserPageState createState() {
+ final rc = _CreateUserPageState();
+ rc.attendedPage =
+ AttendedPage(this, rc, globalData, UserMeta.instance, pageStates);
+ pageStates.attendedPage = rc.attendedPage;
+ return rc;
+ }
+}
+
+class _CreateUserPageState extends CreateUserCustom {
+ @override
+ void didChangeDependencies() {
+ final size = MediaQuery.of(context).size;
+ attendedPage!.pageStates.screenWidth = size.width;
+ attendedPage!.pageStates.screenHeight = size.height;
+ super.didChangeDependencies();
+ }
+}
--- /dev/null
+// This file is created by the meta_tool. But it can be customized.
+// It will never overridden by the meta_tool.
+import 'package:flutter/material.dart';
+
+import '../../base/i18n.dart';
+import '../../setting/global_data.dart';
+import '../../widget/attended_page.dart';
+import '../../widget/widget_form.dart';
+import 'delete_user_page.dart';
+
+final i18n = I18N();
+
+class DeleteUserCustom extends State<DeleteUserPage> {
+ final globalData = GlobalData();
+ AttendedPage? attendedPage;
+
+ @override
+ Widget build(BuildContext context) {
+ final padding = 16.0;
+ final rc = Scaffold(
+ appBar: globalData.appBarBuilder(i18n.tr('Change User data')),
+ drawer: globalData.drawerBuilder(context),
+ body: SafeArea(
+ child: WidgetForm.flexibleGrid(attendedPage!.attendedWidgets(),
+ screenWidth: attendedPage!.pageStates.screenWidth,
+ padding: padding)));
+ return rc;
+ }
+
+ @override
+ void initState() {
+ super.initState();
+ }
+}
--- /dev/null
+// DO NOT CHANGE. This file is created by the meta_tool!
+import 'package:flutter/material.dart';
+
+import '../../meta/users_meta.dart';
+import '../../setting/global_data.dart';
+import '../../widget/attended_page.dart';
+import 'delete_user_custom.dart';
+
+class DeleteUserPage extends StatefulWidget {
+ final GlobalData globalData;
+ final PageStates pageStates = PageStates();
+ DeleteUserPage(this.globalData) : super();
+ @override
+ _DeleteUserPageState createState() {
+ final rc = _DeleteUserPageState();
+ rc.attendedPage =
+ AttendedPage(this, rc, globalData, UserMeta.instance, pageStates);
+ pageStates.attendedPage = rc.attendedPage;
+ return rc;
+ }
+}
+
+class _DeleteUserPageState extends DeleteUserCustom {
+ @override
+ void didChangeDependencies() {
+ final size = MediaQuery.of(context).size;
+ attendedPage!.pageStates.screenWidth = size.width;
+ attendedPage!.pageStates.screenHeight = size.height;
+ super.didChangeDependencies();
+ }
+}
--- /dev/null
+// This file is created by the meta_tool. But it can be customized.
+// It will never overridden by the meta_tool.
+import 'package:flutter/material.dart';
+
+import '../../base/i18n.dart';
+import '../../setting/global_data.dart';
+import '../../widget/attended_page.dart';
+import '../../widget/widget_form.dart';
+import 'edit_user_page.dart';
+
+final i18n = I18N();
+
+class EditUserCustom extends State<EditUserPage> {
+ final globalData = GlobalData();
+ AttendedPage? attendedPage;
+
+ @override
+ Widget build(BuildContext context) {
+ final padding = 16.0;
+ final rc = Scaffold(
+ appBar: globalData.appBarBuilder(i18n.tr('Change User data')),
+ drawer: globalData.drawerBuilder(context),
+ body: SafeArea(
+ child: WidgetForm.flexibleGrid(attendedPage!.attendedWidgets(),
+ screenWidth: attendedPage!.pageStates.screenWidth,
+ padding: padding)));
+ return rc;
+ }
+
+ @override
+ void initState() {
+ super.initState();
+ }
+}
--- /dev/null
+// DO NOT CHANGE. This file is created by the meta_tool!
+import 'package:flutter/material.dart';
+
+import '../../meta/users_meta.dart';
+import '../../setting/global_data.dart';
+import '../../widget/attended_page.dart';
+import 'edit_user_custom.dart';
+
+class EditUserPage extends StatefulWidget {
+ final GlobalData globalData;
+ final PageStates pageStates = PageStates();
+ EditUserPage(this.globalData) : super();
+ @override
+ _EditUserPageState createState() {
+ final rc = _EditUserPageState();
+ rc.attendedPage =
+ AttendedPage(this, rc, globalData, UserMeta.instance, pageStates);
+ pageStates.attendedPage = rc.attendedPage;
+ return rc;
+ }
+}
+
+class _EditUserPageState extends EditUserCustom {
+ @override
+ void didChangeDependencies() {
+ final size = MediaQuery.of(context).size;
+ attendedPage!.pageStates.screenWidth = size.width;
+ attendedPage!.pageStates.screenHeight = size.height;
+ super.didChangeDependencies();
+ }
+}
--- /dev/null
+// This file is created by the meta_tool. But it can be customized.
+// It will never overridden by the meta_tool.
+import 'package:flutter/material.dart';
+
+import '../../base/i18n.dart';
+import '../../setting/global_data.dart';
+import '../../widget/attended_page.dart';
+import '../../widget/widget_form.dart';
+import 'list_user_page.dart';
+
+final i18n = I18N();
+
+class ListUserCustom extends State<ListUserPage> {
+ final globalData = GlobalData();
+ AttendedPage? attendedPage;
+
+ @override
+ Widget build(BuildContext context) {
+ final padding = 16.0;
+ final rc = Scaffold(
+ appBar: globalData.appBarBuilder(i18n.tr('Change User data')),
+ drawer: globalData.drawerBuilder(context),
+ body: SafeArea(
+ child: WidgetForm.flexibleGrid(attendedPage!.attendedWidgets(),
+ screenWidth: attendedPage!.pageStates.screenWidth,
+ padding: padding)));
+ return rc;
+ }
+
+ @override
+ void initState() {
+ super.initState();
+ }
+}
--- /dev/null
+// DO NOT CHANGE. This file is created by the meta_tool!
+import 'package:flutter/material.dart';
+
+import '../../meta/users_meta.dart';
+import '../../setting/global_data.dart';
+import '../../widget/attended_page.dart';
+import 'list_user_custom.dart';
+
+class ListUserPage extends StatefulWidget {
+ final GlobalData globalData;
+ final PageStates pageStates = PageStates();
+ ListUserPage(this.globalData) : super();
+ @override
+ _ListUserPageState createState() {
+ final rc = _ListUserPageState();
+ rc.attendedPage =
+ AttendedPage(this, rc, globalData, UserMeta.instance, pageStates);
+ pageStates.attendedPage = rc.attendedPage;
+ return rc;
+ }
+}
+
+class _ListUserPageState extends ListUserCustom {
+ @override
+ void didChangeDependencies() {
+ final size = MediaQuery.of(context).size;
+ attendedPage!.pageStates.screenWidth = size.width;
+ attendedPage!.pageStates.screenHeight = size.height;
+ super.didChangeDependencies();
+ }
+}
+++ /dev/null
-import 'package:flutter/material.dart';
-
-import '../../base/i18n.dart';
-import '../../meta/users_meta.dart';
-import '../../setting/global_data.dart';
-import '../../widget/attended_page.dart';
-import '../../widget/widget_form.dart';
-
-final i18n = I18N();
-
-class UsersEditPage extends StatefulWidget {
- final GlobalData globalData;
- final PageStates pageStates = PageStates();
- UsersEditPage(this.globalData) : super();
- @override
- _UsersEditPageState createState() {
- final rc = _UsersEditPageState();
- rc.attendedPage =
- AttendedPage(this, rc, globalData, UserMeta.instance, pageStates);
- pageStates.attendedPage = rc.attendedPage;
- return rc;
- }
-}
-
-class _UsersEditPageState extends State<UsersEditPage> {
- AttendedPage? attendedPage;
-
- final nameController = TextEditingController();
- final globalData = GlobalData();
- @override
- Widget build(BuildContext context) {
- final padding = 16.0;
- final rc = Scaffold(
- appBar: globalData.appBarBuilder(i18n.tr('Change User data')),
- drawer: globalData.drawerBuilder(context),
- body: SafeArea(
- child: WidgetForm.flexibleGrid(attendedPage!.attendedWidgets(),
- screenWidth: attendedPage!.pageStates.screenWidth,
- padding: padding)));
- return rc;
- }
-
- @override
- void didChangeDependencies() {
- final size = MediaQuery.of(context).size;
- attendedPage!.pageStates.screenWidth = size.width;
- attendedPage!.pageStates.screenHeight = size.height;
- super.didChangeDependencies();
- }
-
- @override
- void initState() {
- super.initState();
- }
-}
import '../meta/module_meta_data.dart';
import '../widget/attended_page.dart';
+typedef MenuItemBuilder = List<DropdownMenuItem<T>> Function<T>(
+ PropertyMetaData propertyMetaData);
+
/// Manages a widget controlled by the meta data.
abstract class AttendedWidget {
final AttendedPage attendedPage;
--- /dev/null
+#! /bin/bash
+APP=meta_tool
+/usr/bin/dart compile exe bin/$APP.dart -o ../tools/$APP
+
+
--- /dev/null
+import 'dart:io';
+
+import 'package:dart_bones/dart_bones.dart';
+import 'package:path/path.dart';
+
+import '../lib/meta/module_meta_data.dart';
+import '../lib/meta/modules.dart';
+import 'generator_base.dart';
+import 'page_generator.dart';
+
+class Generator extends PageGenerator {
+ Generator(BaseLogger logger) : super(logger);
+
+ ModuleMetaData? checkModule(List<String> args, int index) {
+ ModuleMetaData? rc;
+ if (index >= args.length) {
+ logger.error('+++ too few arguments. Missing MODULE.');
+ } else if ((rc = moduleByName(args[index])) == null) {
+ logger.error('+++ unknown module: ${args[index]}');
+ rc = null;
+ }
+ return rc;
+ }
+
+ /// Returns a Dart class definition of the [module].
+ String createModuleData(ModuleMetaData module) {
+ final buffer = StringBuffer();
+ PropertyMetaData? primary = module.primaryOf();
+ buffer.writeln('// DO NOT CHANGE. This file is created by the meta_tool');
+ buffer.writeln("import '../../base/defines.dart';");
+ buffer.writeln("import '../../base/helper.dart';");
+ buffer.writeln("import '../../persistence/data_record.dart';");
+ final type = 'int';
+ buffer.writeln(
+ 'class ${module.moduleNameSingular}Data extends DataRecord<$type>{');
+ for (var item in module.propertyList) {
+ buffer.writeln(' ' + module.dartType(item.dataType) + '? ${item.name};');
+ }
+ buffer.writeln(' ${module.moduleNameSingular}Data({');
+ String separator = '';
+ for (var item in module.propertyList) {
+ buffer.write('$separator this.${item.name}');
+ separator = ',';
+ }
+ buffer.writeln('});');
+ buffer.writeln(
+ ' ${module.moduleNameSingular}Data.createFromMap(DataMap map) {');
+ buffer.writeln(' fromMap(map);');
+ buffer.writeln(' }');
+ buffer.writeln(' @override');
+ buffer.writeln(' void fromMap(DataMap map) {');
+ for (var item in module.propertyList) {
+ final name = item.columnName;
+ final type = item.dataType.toString();
+ //id = data.containsKey('id') ? int.tryParse(data['id']) : null;
+ buffer.writeln(
+ " ${item.name} = map.containsKey('$name') ? valueOf($type, "
+ "map['$name']) : null;");
+ }
+ buffer.writeln(' }');
+ final name = primary == null ? 'id' : primary.name;
+ final name2 = primary == null ? 'id' : primary.columnName;
+ buffer.write(''' @override
+ int keyOf() {
+ return $name ?? 0;
+ }
+ @override
+ String nameOfKey(){
+ return '$name2';
+ }
+''');
+ buffer.writeln(' static DataType? dataTypeOf(String name) {');
+ buffer.writeln(' DataType? rc;');
+ buffer.writeln(' switch(name){');
+ for (var item in module.propertyList) {
+ buffer.writeln(" case '${item.name}':");
+ buffer.writeln(' rc = ${item.dataType};');
+ buffer.writeln(' break;');
+ }
+ buffer.write(''' default:
+ break;
+ }
+ return rc;
+ }
+''');
+ buffer.write(''' @override
+ DataMap toMap({DataMap? map, bool clear = true}) {
+ map ??= DataMap();
+ if (clear) {
+ map.clear();
+ }
+''');
+ for (var item in module.propertyList) {
+ final name1 = item.columnName;
+ final name2 = item.name;
+ buffer.writeln(" map['$name1'] = $name2;");
+ }
+ buffer.write(''' return map;
+ }
+}
+''');
+ return buffer.toString();
+ }
+
+ /// Creates the file modules.dart.
+ String createModules() {
+ final buffer = StringBuffer();
+ buffer.write('// DO NOT CHANGE. This file is created by the meta_tool\n');
+ buffer.write("import 'module_meta_data.dart';\n");
+ final modules = <String>[];
+ final files = <String>[];
+ final fileOfModule = <String, String>{};
+
+ for (var item in Directory('lib/meta').listSync()) {
+ final name = basename(item.path);
+ if (name.endsWith('_meta.dart')) {
+ final moduleName =
+ GeneratorBase.filenameToClass(name.replaceFirst('_meta.dart', ''));
+ fileOfModule[moduleName] = name;
+ modules.add(moduleName);
+ files.add(name);
+ }
+ }
+ modules.sort();
+ files.sort();
+ for (var file in files) {
+ buffer.write("import '$file';\n");
+ }
+ buffer.write('''
+/// Returns the meta data of the module given by [name].
+/// Returns null if not found.
+ModuleMetaData? moduleByName(String name) {
+ ModuleMetaData? rc;
+ switch (name) {
+''');
+ for (var module in modules) {
+ buffer.write(" case '$module':\n");
+ final className = findClass(fileOfModule[module]!);
+ buffer.write(" rc = $className();\n");
+ buffer.write(" break;\n");
+ }
+ buffer.write(''' default:
+ break;
+ }
+ return rc;
+}
+/// Returns the module names as string list.
+List<String> moduleNames(){
+ return [
+''');
+ for (var module in modules) {
+ buffer.write(" '$module',\n");
+ }
+ buffer.write(''' ];
+}
+''');
+ return buffer.toString();
+ }
+
+ /// Finds the class name of a module meta class given by the file [node].
+ String findClass(String node) {
+ final contents = File('lib/meta/$node').readAsStringSync();
+ RegExpMatch? match;
+ String rc;
+ if ((match = RegExp(r'class\s+(\w+)\s').firstMatch(contents)) != null) {
+ rc = match!.group(1)!;
+ } else {
+ logger.error('+++ missing class in $node');
+ rc = GeneratorBase.filenameToClass(node.replaceFirst('.dart', ''));
+ }
+ return rc;
+ }
+
+ /// Generates all modules defined in the meta data.
+ void updateModules(Generator generator) {
+ writeFile('lib/meta/modules.dart', createModules());
+ final modules = moduleNames();
+ for (var name in modules) {
+ ModuleMetaData? module = moduleByName(name);
+ if (module == null) {
+ logger.error('+++ unknown module: $name');
+ } else {
+ String filename =
+ GeneratorBase.classToFilename(module.moduleNameSingular) +
+ '_data.dart';
+ final directory = name.toLowerCase();
+ writeFile('../../lib/page/$directory/$filename',
+ generator.createModuleData(module));
+ }
+ }
+ }
+
+ /// Generates the modules.dart.
+ void updateModulesList(Generator generator) {
+ writeFile('lib/meta/modules.dart', createModules());
+ }
+}
--- /dev/null
+import 'dart:io';
+
+import 'package:dart_bones/dart_bones.dart';
+import 'package:path/path.dart';
+
+/// The base class of all [Generator]s.
+class GeneratorBase {
+ final BaseLogger logger;
+ GeneratorBase(this.logger);
+
+ /// Appends a [string] at the end of a [buffer].
+ /// If [buffer] is null a new instance is created.
+ /// If the last line in [buffer] has a length greater than [maxLength]
+ /// a newline is inserted and [indent] spaces.
+ /// If [separator] is not null that is inserted above the [string].
+ /// Returns the buffer (for chaining).
+ StringBuffer addToBuffer(String string,
+ {required int maxLength,
+ String? separator,
+ int indent = 0,
+ StringBuffer? buffer}) {
+ buffer ??= StringBuffer();
+ if (separator != null) {
+ buffer.write(separator);
+ }
+ final last = buffer.toString().lastIndexOf('\n');
+ if (buffer.length - (last < 0 ? 0 : last) + string.length > maxLength) {
+ buffer.write('\n' + (' ' * indent));
+ }
+ buffer.write(string);
+ return buffer;
+ }
+
+ /// Replaces print().
+ void out(String line) {
+ logger.log(line);
+ }
+
+ String toCamelCase(String name) {
+ final rc = name.isEmpty ? '' : (name[0].toUpperCase() + name.substring(1));
+ return rc;
+ }
+
+ /// Writes a given [contents] into a file named [filename];
+ void writeFile(String filename, String contents) {
+ logger.log('creating $filename ...');
+ final file = File(filename);
+ final parent = Directory(dirname(filename));
+ if (!parent.existsSync()) {
+ parent.createSync(recursive: true);
+ }
+ file.writeAsStringSync(contents);
+ }
+
+ /// Converts a [className] into a file name using Dart conventions.
+ /// Example: "UserData" is converted to "user_data"
+ static String classToFilename(String className) {
+ String upperCase = className.toUpperCase();
+ String rc = '';
+ for (var ix = 0; ix < className.length; ix++) {
+ if (className[ix] == upperCase[ix] && ix > 0) {
+ rc += '_';
+ }
+ rc += className[ix];
+ }
+ return rc.toLowerCase();
+ }
+
+ /// Converts a [filename] into a class name using Dart conventions.
+ /// Example: "user_data.dart" is converted to "UserData"
+ static String filenameToClass(String filename) {
+ final parts = basenameWithoutExtension(filename).split('_');
+ String rc = '';
+ for (var part in parts) {
+ if (part.isNotEmpty) {
+ rc += part[0].toUpperCase() + part.substring(1);
+ }
+ }
+ return rc;
+ }
+}
--- /dev/null
+import 'package:dart_bones/dart_bones.dart';
+import '../lib/base/i18n_io.dart';
+import '../lib/meta/module_meta_data.dart';
+import '../lib/meta/modules.dart';
+
+import 'generator.dart';
+
+void main(List<String> args) {
+ final logger = MemoryLogger(LEVEL_DETAIL);
+ final generator = Generator(logger);
+ I18nIo.internal('data/i18n', logger);
+ if (args.isEmpty) {
+ usage(generator);
+ generator.out('+++ missing arguments.');
+ } else {
+ ModuleMetaData? metaData;
+ switch (args[0]) {
+ case 'all-modules':
+ generator.out(moduleNames().join('\n'));
+ break;
+ case 'show-modules-list':
+ generator.out(generator.createModules());
+ break;
+ case 'show-page':
+ if (args.length < 3) {
+ generator.out('+++ missing PAGE');
+ } else if ((metaData = generator.checkModule(args, 1)) != null) {
+ final pageName = args[2];
+ final page = metaData!.pageByName(pageName);
+ if (page == null) {
+ generator.out('+++ unknown page: $pageName');
+ } else {
+ generator.out((generator.createPage(page)));
+ }
+ }
+ break;
+ case 'show-custom-page':
+ if (args.length < 3) {
+ generator.out('+++ missing PAGE');
+ } else if ((metaData = generator.checkModule(args, 1)) != null) {
+ final pageName = args[2];
+ final page = metaData!.pageByName(pageName);
+ if (page == null) {
+ generator.out('+++ unknown page: $pageName');
+ } else {
+ generator.out((generator.createCustomized(page)));
+ }
+ }
+ break;
+ case 'show-table':
+ if ((metaData = generator.checkModule(args, 1)) != null) {
+ generator.out(generator.createDbTable(metaData!));
+ }
+ break;
+ case 'show-sql':
+ if ((metaData = generator.checkModule(args, 1)) != null) {
+ generator.out(generator.createSqlStatements(metaData!));
+ }
+ break;
+ case 'show-data':
+ if ((metaData = generator.checkModule(args, 1)) != null) {
+ generator.out(generator.createModuleData(metaData!));
+ }
+ break;
+ case 'update-modules-files':
+ generator.updateModules(generator);
+ generator.updatePages(generator);
+ break;
+ case 'update-modules-names':
+ generator.updateModulesList(generator);
+ break;
+ case 'update-sql':
+ generator.updateSql(generator);
+ break;
+ default:
+ usage(generator);
+ generator.out('+++ unknown MODE: ${args[0]}.');
+ break;
+ }
+ }
+}
+
+void usage(Generator generator) {
+ generator.out('''Usage: meta_tool MODE MODULE
+ Generate code specified by MODE for the module MODULE.
+MODE:
+ all-modules
+ Shows the module names.
+ show-modules-list
+ Shows the file lib/meta/modules.dart.
+ That works without of meta-data: It can be called before
+ creating a the meta_tool.
+ show-page MODULE PAGE
+ Shows the page PAGE from the module MODULE.
+ show-table MODULE
+ Shows the SQL table definition of MODULE.
+ show-data MODULE
+ Creates the data storage class of MODULE
+ show-sql MODULE
+ Creates the SQL statements (for the rest_server) of MODULE
+ update-modules-names
+ Generates the modules.dart file in the correct directory.
+ That works without of meta-data: It can be called before
+ creating a the meta_tool.
+ update-modules-files
+ Generates all module files depending on the meta data.
+ update-sql
+ Generates all Sql statement files depending on the meta data.
+MODULE:
+ The name of the module, e.g. "Users"
+Example:
+meta_tool print-table Users
+''');
+}
--- /dev/null
+import 'dart:io';
+
+import 'package:dart_bones/dart_bones.dart';
+
+import '../lib/meta/module_meta_data.dart';
+import '../lib/meta/modules.dart';
+import 'generator.dart';
+import 'generator_base.dart';
+import 'sql_generator.dart';
+
+/// Handles the page generation. Is a base class of [Generator].
+class PageGenerator extends SqlGenerator {
+ static final templatePage = '''
+// DO NOT CHANGE. This file is created by the meta_tool!
+import 'package:flutter/material.dart';
+
+import '../../meta/users_meta.dart';
+import '../../setting/global_data.dart';
+import '../../widget/attended_page.dart';
+import 'edit_user_custom.dart';
+
+class EditUserPage extends StatefulWidget {
+ final GlobalData globalData;
+ final PageStates pageStates = PageStates();
+ EditUserPage(this.globalData) : super();
+ @override
+ _EditUserPageState createState() {
+ final rc = _EditUserPageState();
+ rc.attendedPage =
+ AttendedPage(this, rc, globalData, UserMeta.instance, pageStates);
+ pageStates.attendedPage = rc.attendedPage;
+ return rc;
+ }
+}
+
+class _EditUserPageState extends EditUserCustom {
+ @override
+ void didChangeDependencies() {
+ final size = MediaQuery.of(context).size;
+ attendedPage!.pageStates.screenWidth = size.width;
+ attendedPage!.pageStates.screenHeight = size.height;
+ super.didChangeDependencies();
+ }
+}
+''';
+
+ static final templateCustom = '''// This file is created by the meta_tool. But it can be customized.
+// It will never overridden by the meta_tool.
+import 'package:flutter/material.dart';
+
+import '../../base/i18n.dart';
+import '../../setting/global_data.dart';
+import '../../widget/attended_page.dart';
+import '../../widget/widget_form.dart';
+import 'edit_user_page.dart';
+
+final i18n = I18N();
+
+class EditUserCustom extends State<EditUserPage> {
+ final globalData = GlobalData();
+ AttendedPage? attendedPage;
+
+
+ @override
+ Widget build(BuildContext context) {
+ final padding = 16.0;
+ final rc = Scaffold(
+ appBar: globalData.appBarBuilder(i18n.tr('Change User data')),
+ drawer: globalData.drawerBuilder(context),
+ body: SafeArea(
+ child: WidgetForm.flexibleGrid(attendedPage!.attendedWidgets(),
+ screenWidth: attendedPage!.pageStates.screenWidth,
+ padding: padding)));
+ return rc;
+ }
+ @override
+ void initState() {
+ super.initState();
+ }
+}
+''';
+ static final templateCollection = '''// DO NOT CHANGE. This file is created by the meta_tool!
+import 'package:flutter/material.dart';
+import '../setting/global_data.dart';
+import 'page_collection_custom.dart';
+
+#IMPORTS#
+
+/// Manages all meta data driven pages of the program.
+class PageCollection {
+ static PageCollection? _instance;
+ PageCollection.internal();
+ factory PageCollection() {
+ _instance ??= PageCollection.internal();
+ return _instance!;
+ }
+ final map = <String, StatefulWidget>{};
+
+ /// Creates a page defined by a [route].
+ StatefulWidget? newPageByRoute(String route) {
+ StatefulWidget? rc;
+ final globalData = GlobalData();
+ switch (route) {
+#CASES# default:
+ rc = customPageByRoute(route, globalData);
+ break;
+ }
+ if (rc != null) {
+ map[route] = rc;
+ }
+ return rc;
+ }
+
+ /// Returns the last generated page of a given [route].
+ StatefulWidget? existingPageByRoute(String route) {
+ return map[route];
+ }
+}
+''';
+ static final templateCollectionCustom = '''// This file is created by the meta_tool. But it can be customized.
+// It will never overridden by the meta_tool.
+import 'package:flutter/material.dart';
+import '../setting/global_data.dart';
+import 'info_page.dart';
+import 'log_page.dart';
+
+/// Creates a page defined by a [route].
+///
+/// Note: only pages without meta data definitions should be handled here.
+StatefulWidget? customPageByRoute(String route, GlobalData globalData) {
+ StatefulWidget? rc;
+ switch (route) {
+ case '/info':
+ rc = InfoPage(globalData);
+ break;
+ case '/log':
+ rc = LogPage(globalData);
+ break;
+ default:
+ break;
+ }
+ return rc;
+}
+''';
+ static final templateCase = ''' case '/users/edit':
+ rc = EditUserPage(globalData);
+ break;
+''';
+ PageGenerator(BaseLogger logger) : super(logger);
+
+ String replaceVariables(String template, PageMetaData page) {
+ final camelModules = toCamelCase(page.module.moduleName);
+ final camelModule = toCamelCase(page.module.moduleNameSingular);
+ final camelPageName = toCamelCase(page.name);
+ final rc = template
+ .replaceAll('Users', camelModules)
+ .replaceAll('User', camelModule)
+ .replaceAll('Edit', camelPageName)
+ .replaceAll('edit', page.name)
+ .replaceAll('users', page.module.moduleName.toLowerCase())
+ .replaceAll('user', page.module.moduleNameSingular.toLowerCase());
+ return rc;
+ }
+ /// Returns a Dart class definition of the [page] that should not be modified.
+ String createCustomized(PageMetaData page) {
+ var rc = replaceVariables(templateCustom, page);
+ return rc;
+ }
+
+ /// Returns a Dart class definition of the [page] that should not be modified.
+ String createPage(PageMetaData page) {
+ var rc = replaceVariables(templatePage, page);
+ return rc;
+ }
+
+ /// Creates the file page_collection.dart and (if it does not exist) the file
+ /// page_collection_custom.dart.
+ void updateCollection(){
+ final modules = moduleNames();
+ final imports = StringBuffer();
+ final cases = StringBuffer();
+ for (var name in modules){
+ ModuleMetaData? module = moduleByName(name);
+ for (var page in module!.pageList) {
+ imports.writeln("import '${name.toLowerCase()}/${page.name}_"
+ "${module.moduleNameSingular.toLowerCase()}_page.dart';");
+ cases.write(replaceVariables(templateCase, page));
+ }
+ }
+ final content = templateCollection.replaceFirst('#IMPORTS#', imports.toString())
+ .replaceFirst('#CASES#', cases.toString());
+ writeFile('../lib/page/page_collection.dart', content);
+ final full = '../lib/page/page_collection_custom.dart';
+ if (File(full).existsSync()){
+ logger.log('$full already exists. We do not override');
+ } else {
+ writeFile(full, templateCollectionCustom);
+ }
+ }
+ /// Generates all modules defined in the meta data.
+ void updatePages(Generator generator) {
+ if (!Directory('bin').existsSync() ||
+ !Directory('../lib/page').existsSync()) {
+ logger.error('missing ../lib/page: The program must be started '
+ 'in the directory "metatool". cwd: ${Directory.current.path}');
+ } else {
+ final modules = moduleNames();
+ for (var name in modules) {
+ ModuleMetaData? module = moduleByName(name);
+ if (module == null) {
+ logger.error('+++ unknown module: $name');
+ } else {
+ for (var page in module.pageList) {
+ String filename = page.name +
+ '_' +
+ module.moduleNameSingular.toLowerCase() +
+ '_page.dart';
+ var full = '../lib/page/${name.toLowerCase()}/$filename';
+ writeFile(full, generator.createPage(page));
+ filename = page.name +
+ '_' +
+ module.moduleNameSingular.toLowerCase() +
+ '_custom.dart';
+ full = '../lib/page/${name.toLowerCase()}/$filename';
+ if (File(full).existsSync()) {
+ out('$filename already exists. We do not override.');
+ } else {
+ writeFile(full, generator.createCustomized(page));
+ }
+ }
+ }
+ }
+ updateCollection();
+ }
+ }
+}
--- /dev/null
+import 'dart:io';
+
+import 'package:dart_bones/dart_bones.dart';
+import '../lib/meta/module_meta_data.dart';
+import '../lib/meta/modules.dart';
+
+import 'generator.dart';
+import 'generator_base.dart';
+
+/// Handles the SQL generation part of the generator.
+class SqlGenerator extends GeneratorBase {
+ SqlGenerator(BaseLogger logger) : super(logger);
+
+ /// Returns a DDL statement creating the database table of the [module].
+ String createDbTable(ModuleMetaData module) {
+ final tableName = module.tableName;
+ final list = module.propertyList;
+ final buffer = StringBuffer();
+ buffer.write('-- DO NOT CHANGE. This file is created by the meta_tool\n');
+ buffer.write('CREATE TABLE $tableName (\n');
+ String? primary;
+ for (var item in list) {
+ if (item.options.contains('primary')) {
+ primary = item.columnName;
+ }
+ buffer.write(' ${item.columnName}');
+ buffer.write(' ' + module.mySqlType(item.dataType, item.size));
+ buffer.write(module.dbOptions(item) + ',\n');
+ }
+ buffer.write(' PRIMARY KEY ($primary)\n');
+ buffer.write(');\n');
+ return buffer.toString();
+ }
+
+ /// Returns the SQL statements for insert, update, delete...
+ /// for a given [module].
+ /// This yaml file is a configuration for the rest_server.
+ String createSqlStatements(ModuleMetaData module) {
+ final moduleName = module.moduleName;
+ final tableName = module.tableName;
+ final list = module.propertyList;
+ final buffer = StringBuffer();
+ buffer.write('---\n');
+ buffer.write('# DO NOT CHANGE. This file is created by the meta_tool\n');
+ buffer.write('''# SQL statements of the module "$moduleName":\n
+module: $moduleName
+list:
+ type: list
+ parameters: []
+ sql: "select * from $tableName;"
+byId:
+ type: record
+ parameters: [ "${list[0].name}" ]
+ sql: "select * from $tableName where ${list[0].columnName}=:${list[0].name};"
+delete:
+ type: delete
+ parameters: [ "${list[0].name}" ]
+ sql: "delete * from $tableName where ${list[0].columnName}=:${list[0].name};"
+update:
+ type: update
+''');
+ var items = module.standardColumns('changedBy');
+ var parameters = addToBuffer(' parameters: [', maxLength: 80);
+ var assignments = addToBuffer(' ', maxLength: 80);
+ var first = true;
+ for (var item in items) {
+ addToBuffer('":${item.name}"',
+ maxLength: 80,
+ separator: first ? null : ',',
+ indent: 4,
+ buffer: parameters);
+ addToBuffer('${item.columnName}=:${item.name}',
+ maxLength: 80,
+ separator: first ? null : ',',
+ indent: 4,
+ buffer: assignments);
+ first = false;
+ }
+ parameters.write(']');
+ var item = module.properties['changed']!;
+ addToBuffer('${item.columnName}=NOW()',
+ maxLength: 80, separator: ',', indent: 4, buffer: assignments);
+ // parameters: [":id", ":name", ":displayname", ":email", ":changedby"]
+ buffer.writeln(parameters);
+ buffer.write(' sql: "UPDATE $tableName SET\n');
+ buffer.writeln(assignments);
+ //user_name=:name, user_displayname=:displayname, user_email=:email, user_changed=NOW(), user_changedby=:changedby
+ buffer.write(' WHERE ${list[0].columnName}=:${list[0].name};"\n');
+
+ items = module.standardColumns('createdBy');
+ parameters = addToBuffer(' parameters: [', maxLength: 80);
+ final sql1 = addToBuffer(' sql: "INSERT INTO $tableName(', maxLength: 80);
+ final sql2 = addToBuffer(' VALUES(', maxLength: 80);
+ first = true;
+ for (var item in items) {
+ addToBuffer('":${item.name}"',
+ maxLength: 80,
+ separator: first ? null : ',',
+ indent: 4,
+ buffer: parameters);
+ addToBuffer('${item.columnName}',
+ maxLength: 80,
+ separator: first ? null : ',',
+ indent: 6,
+ buffer: sql1);
+ addToBuffer(':${item.name}',
+ maxLength: 80,
+ separator: first ? null : ',',
+ indent: 6,
+ buffer: sql2);
+ first = false;
+ }
+ parameters.write(']');
+ item = module.properties['created']!;
+ addToBuffer('${item.columnName})',
+ maxLength: 80, separator: ',', indent: 4, buffer: sql1);
+ addToBuffer('NOW());"',
+ maxLength: 80, separator: ',', indent: 4, buffer: sql2);
+ // parameters: [":id", ":name", ":displayname", ":email", ":changedby"]
+ buffer.write('''insert:
+ type: insert
+''');
+ buffer.writeln(parameters);
+ buffer.writeln(sql1);
+ buffer.writeln(sql2);
+ return buffer.toString();
+ }
+
+ /// Generates the Sql statement file for each module defined in the meta data.
+ void updateSql(Generator generator) {
+ final modules = moduleNames();
+ for (var name in modules) {
+ ModuleMetaData? module = moduleByName(name);
+ if (module == null) {
+ logger.error('+++ unknown module: $name');
+ } else {
+ logger.log('current directory: ${Directory.current.path}');
+ String filename = 'rest_server/data/sql/${name.toLowerCase()}.sql.yaml';
+ writeFile(filename, generator.createSqlStatements(module));
+ }
+ }
+ }
+}
--- /dev/null
+name: meta-tool
+description: A generator of programming code for building Flutter apps.
+version: 1.0.0
+homepage: https://github.com/hamatoma/exhibition
+
+environment:
+ sdk: ">=2.12.0 <3.0.0"
+
+dependencies:
+ meta: ^1.7.0
+ sprintf: ^6.0.0
+ yaml: ^3.1.0
+ path: ^1.8.0
+
+dev_dependencies:
+ pedantic: ^1.11.1
+ test: ^1.17.10
+
flutter:
sdk: flutter
path: ^1.8.0
- dart_bones: "^1.1.1"
- url_launcher: ^6.0.3
+ dart_bones: ^1.2.2
+ url_launcher: ^6.0.9
flutter_markdown: ^0.6.1
flutter_bloc: ^7.1.0
equatable: ^2.0.3
- permission_handler: ^7.0.0
+ permission_handler: ^8.1.6
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2