From 145554bc8e47e834cfb592608a91f307ae95535c Mon Sep 17 00:00:00 2001 From: Hamatoma Date: Mon, 4 Oct 2021 00:20:21 +0200 Subject: [PATCH] Generator, helper, module_meta_data PageMetaData. * Generator: ** new: page_generator, sql_generator generator_base * helper: new toCamelCase() * module_meta_data: Fix: no flutter dependencies. * PageMetaData: minimalistic constructor --- Meta | 3 +- ReCreateMetaTool | 5 +- bin/generator.dart | 388 ------------------------- lib/base/defines.dart | 1 + lib/base/helper.dart | 7 + lib/meta/module_meta_data.dart | 47 ++- lib/meta/roles_meta.dart | 8 +- lib/meta/users_meta.dart | 8 +- lib/page/page_collection.dart | 33 ++- lib/page/page_collection_custom.dart | 24 ++ lib/page/roles/create_role_custom.dart | 34 +++ lib/page/roles/create_role_page.dart | 31 ++ lib/page/roles/edit_role_custom.dart | 34 +++ lib/page/roles/edit_role_page.dart | 31 ++ lib/page/roles/list_role_custom.dart | 34 +++ lib/page/roles/list_role_page.dart | 31 ++ lib/page/users/create_user_custom.dart | 34 +++ lib/page/users/create_user_page.dart | 31 ++ lib/page/users/delete_user_custom.dart | 34 +++ lib/page/users/delete_user_page.dart | 31 ++ lib/page/users/edit_user_custom.dart | 34 +++ lib/page/users/edit_user_page.dart | 31 ++ lib/page/users/list_user_custom.dart | 34 +++ lib/page/users/list_user_page.dart | 31 ++ lib/page/users/users_edit_page.dart | 55 ---- lib/widget/attended_widget.dart | 3 + metatool/Compile | 5 + metatool/bin/generator.dart | 197 +++++++++++++ metatool/bin/generator_base.dart | 81 ++++++ {bin => metatool/bin}/meta_tool.dart | 65 +++-- metatool/bin/page_generator.dart | 236 +++++++++++++++ metatool/bin/sql_generator.dart | 143 +++++++++ metatool/pubspec.yaml | 18 ++ pubspec.yaml | 6 +- 34 files changed, 1302 insertions(+), 486 deletions(-) delete mode 100644 bin/generator.dart create mode 100644 lib/page/page_collection_custom.dart create mode 100644 lib/page/roles/create_role_custom.dart create mode 100644 lib/page/roles/create_role_page.dart create mode 100644 lib/page/roles/edit_role_custom.dart create mode 100644 lib/page/roles/edit_role_page.dart create mode 100644 lib/page/roles/list_role_custom.dart create mode 100644 lib/page/roles/list_role_page.dart create mode 100644 lib/page/users/create_user_custom.dart create mode 100644 lib/page/users/create_user_page.dart create mode 100644 lib/page/users/delete_user_custom.dart create mode 100644 lib/page/users/delete_user_page.dart create mode 100644 lib/page/users/edit_user_custom.dart create mode 100644 lib/page/users/edit_user_page.dart create mode 100644 lib/page/users/list_user_custom.dart create mode 100644 lib/page/users/list_user_page.dart delete mode 100644 lib/page/users/users_edit_page.dart create mode 100755 metatool/Compile create mode 100644 metatool/bin/generator.dart create mode 100644 metatool/bin/generator_base.dart rename {bin => metatool/bin}/meta_tool.dart (56%) create mode 100644 metatool/bin/page_generator.dart create mode 100644 metatool/bin/sql_generator.dart create mode 100644 metatool/pubspec.yaml diff --git a/Meta b/Meta index 6668928..4dcab93 100755 --- a/Meta +++ b/Meta @@ -1,5 +1,6 @@ #! /bin/sh EXE=tools/meta_tool test ! -x $EXE && ./ReCreateMetaTool -$EXE $* +cd metatool +../$EXE $* diff --git a/ReCreateMetaTool b/ReCreateMetaTool index bbfbb6b..ece3381 100755 --- a/ReCreateMetaTool +++ b/ReCreateMetaTool @@ -1,3 +1,4 @@ #! /bin/bash -APP=meta_tool -dart compile exe bin/$APP.dart -o tools/$APP +cd metatool +./Compile + diff --git a/bin/generator.dart b/bin/generator.dart deleted file mode 100644 index 89b8b7f..0000000 --- a/bin/generator.dart +++ /dev/null @@ -1,388 +0,0 @@ -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 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 = []; - final files = []; - final fileOfModule = {}; - - 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 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); - } -} diff --git a/lib/base/defines.dart b/lib/base/defines.dart index 88bbea2..1496a80 100644 --- a/lib/base/defines.dart +++ b/lib/base/defines.dart @@ -8,6 +8,7 @@ typedef JsonData = Object; enum EventSource { undef, localData, remoteData } enum EventCardinality { undef, record, list } enum DataType { + undefined, bool, currency, date, diff --git a/lib/base/helper.dart b/lib/base/helper.dart index 1ee83b3..23a13af 100644 --- a/lib/base/helper.dart +++ b/lib/base/helper.dart @@ -1,5 +1,10 @@ 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; @@ -23,6 +28,8 @@ dynamic valueOf(DataType dataType, String data) { case DataType.string: rc = data; break; + case DataType.undefined: + throw FormatException('valueOf(): data type is undefined'); } return rc; } diff --git a/lib/meta/module_meta_data.dart b/lib/meta/module_meta_data.dart index 71943ce..6c4f4f8 100644 --- a/lib/meta/module_meta_data.dart +++ b/lib/meta/module_meta_data.dart @@ -1,8 +1,8 @@ -import 'package:flutter/material.dart'; +import 'package:dart_bones/dart_bones.dart'; import '../base/defines.dart'; -typedef MenuItemBuilder = List> Function( +typedef MetaMenuItemBuilder = List Function( PropertyMetaData propertyMetaData); /// Describes a button of a page. @@ -131,6 +131,8 @@ class ModuleMetaData { case DataType.string: rc = 'String'; break; + case DataType.undefined: + throw FormatException('dartType(): data type is undefined'); } return rc; } @@ -195,10 +197,26 @@ class ModuleMetaData { 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( @@ -207,6 +225,13 @@ class ModuleMetaData { 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. @@ -218,15 +243,17 @@ class ModuleMetaData { } class PageMetaData { - final String name; + String name = ''; + final String label; final PageType pageType; ModuleMetaData module = DummyModule(); - List? fields; - PageMetaData( - this.name, - this.pageType, - this.fields, - ); + final List? field = []; + PageMetaData(this.label, this.pageType, {String name = ''}) { + if (name.isEmpty) { + name = enumToString(pageType); + } + this.name = name; + } } enum PageType { create, custom, delete, edit, list } @@ -245,7 +272,7 @@ class PropertyMetaData { final String options; final DisplayType displayType; final DataType dataType; - MenuItemBuilder? menuItemBuilder; + MetaMenuItemBuilder? menuItemBuilder; /// The size if dataType is DataType.string. final int size; diff --git a/lib/meta/roles_meta.dart b/lib/meta/roles_meta.dart index e45c347..7e1225a 100644 --- a/lib/meta/roles_meta.dart +++ b/lib/meta/roles_meta.dart @@ -28,8 +28,10 @@ class RoleMeta extends ModuleMetaData { '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() {} } diff --git a/lib/meta/users_meta.dart b/lib/meta/users_meta.dart index c627751..af9ba00 100644 --- a/lib/meta/users_meta.dart +++ b/lib/meta/users_meta.dart @@ -37,9 +37,9 @@ class UserMeta extends ModuleMetaData { '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), ]); } diff --git a/lib/page/page_collection.dart b/lib/page/page_collection.dart index 2187508..675ce07 100644 --- a/lib/page/page_collection.dart +++ b/lib/page/page_collection.dart @@ -1,7 +1,15 @@ +// 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 { @@ -18,8 +26,29 @@ 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) { diff --git a/lib/page/page_collection_custom.dart b/lib/page/page_collection_custom.dart new file mode 100644 index 0000000..c61ba97 --- /dev/null +++ b/lib/page/page_collection_custom.dart @@ -0,0 +1,24 @@ +// 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; +} diff --git a/lib/page/roles/create_role_custom.dart b/lib/page/roles/create_role_custom.dart new file mode 100644 index 0000000..7d338e8 --- /dev/null +++ b/lib/page/roles/create_role_custom.dart @@ -0,0 +1,34 @@ +// 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 { + 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(); + } +} diff --git a/lib/page/roles/create_role_page.dart b/lib/page/roles/create_role_page.dart new file mode 100644 index 0000000..6018fb8 --- /dev/null +++ b/lib/page/roles/create_role_page.dart @@ -0,0 +1,31 @@ +// 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(); + } +} diff --git a/lib/page/roles/edit_role_custom.dart b/lib/page/roles/edit_role_custom.dart new file mode 100644 index 0000000..b66d65b --- /dev/null +++ b/lib/page/roles/edit_role_custom.dart @@ -0,0 +1,34 @@ +// 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 { + 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(); + } +} diff --git a/lib/page/roles/edit_role_page.dart b/lib/page/roles/edit_role_page.dart new file mode 100644 index 0000000..309976c --- /dev/null +++ b/lib/page/roles/edit_role_page.dart @@ -0,0 +1,31 @@ +// 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(); + } +} diff --git a/lib/page/roles/list_role_custom.dart b/lib/page/roles/list_role_custom.dart new file mode 100644 index 0000000..f367ee2 --- /dev/null +++ b/lib/page/roles/list_role_custom.dart @@ -0,0 +1,34 @@ +// 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 { + 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(); + } +} diff --git a/lib/page/roles/list_role_page.dart b/lib/page/roles/list_role_page.dart new file mode 100644 index 0000000..b2f01d4 --- /dev/null +++ b/lib/page/roles/list_role_page.dart @@ -0,0 +1,31 @@ +// 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(); + } +} diff --git a/lib/page/users/create_user_custom.dart b/lib/page/users/create_user_custom.dart new file mode 100644 index 0000000..309f2e0 --- /dev/null +++ b/lib/page/users/create_user_custom.dart @@ -0,0 +1,34 @@ +// 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 { + 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(); + } +} diff --git a/lib/page/users/create_user_page.dart b/lib/page/users/create_user_page.dart new file mode 100644 index 0000000..dec4186 --- /dev/null +++ b/lib/page/users/create_user_page.dart @@ -0,0 +1,31 @@ +// 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(); + } +} diff --git a/lib/page/users/delete_user_custom.dart b/lib/page/users/delete_user_custom.dart new file mode 100644 index 0000000..6a3dfd1 --- /dev/null +++ b/lib/page/users/delete_user_custom.dart @@ -0,0 +1,34 @@ +// 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 { + 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(); + } +} diff --git a/lib/page/users/delete_user_page.dart b/lib/page/users/delete_user_page.dart new file mode 100644 index 0000000..e5ac100 --- /dev/null +++ b/lib/page/users/delete_user_page.dart @@ -0,0 +1,31 @@ +// 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(); + } +} diff --git a/lib/page/users/edit_user_custom.dart b/lib/page/users/edit_user_custom.dart new file mode 100644 index 0000000..3c20745 --- /dev/null +++ b/lib/page/users/edit_user_custom.dart @@ -0,0 +1,34 @@ +// 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 { + 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(); + } +} diff --git a/lib/page/users/edit_user_page.dart b/lib/page/users/edit_user_page.dart new file mode 100644 index 0000000..4c6bcf3 --- /dev/null +++ b/lib/page/users/edit_user_page.dart @@ -0,0 +1,31 @@ +// 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(); + } +} diff --git a/lib/page/users/list_user_custom.dart b/lib/page/users/list_user_custom.dart new file mode 100644 index 0000000..e2b1db5 --- /dev/null +++ b/lib/page/users/list_user_custom.dart @@ -0,0 +1,34 @@ +// 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 { + 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(); + } +} diff --git a/lib/page/users/list_user_page.dart b/lib/page/users/list_user_page.dart new file mode 100644 index 0000000..b05bbf3 --- /dev/null +++ b/lib/page/users/list_user_page.dart @@ -0,0 +1,31 @@ +// 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(); + } +} diff --git a/lib/page/users/users_edit_page.dart b/lib/page/users/users_edit_page.dart deleted file mode 100644 index 4f66ce6..0000000 --- a/lib/page/users/users_edit_page.dart +++ /dev/null @@ -1,55 +0,0 @@ -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 { - 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(); - } -} diff --git a/lib/widget/attended_widget.dart b/lib/widget/attended_widget.dart index e88c5b8..cf9cebb 100644 --- a/lib/widget/attended_widget.dart +++ b/lib/widget/attended_widget.dart @@ -3,6 +3,9 @@ import 'package:flutter/material.dart'; import '../meta/module_meta_data.dart'; import '../widget/attended_page.dart'; +typedef MenuItemBuilder = List> Function( + PropertyMetaData propertyMetaData); + /// Manages a widget controlled by the meta data. abstract class AttendedWidget { final AttendedPage attendedPage; diff --git a/metatool/Compile b/metatool/Compile new file mode 100755 index 0000000..4fb1079 --- /dev/null +++ b/metatool/Compile @@ -0,0 +1,5 @@ +#! /bin/bash +APP=meta_tool +/usr/bin/dart compile exe bin/$APP.dart -o ../tools/$APP + + diff --git a/metatool/bin/generator.dart b/metatool/bin/generator.dart new file mode 100644 index 0000000..fa0bb51 --- /dev/null +++ b/metatool/bin/generator.dart @@ -0,0 +1,197 @@ +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 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 = []; + final files = []; + final fileOfModule = {}; + + 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 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()); + } +} diff --git a/metatool/bin/generator_base.dart b/metatool/bin/generator_base.dart new file mode 100644 index 0000000..fc5fb42 --- /dev/null +++ b/metatool/bin/generator_base.dart @@ -0,0 +1,81 @@ +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; + } +} diff --git a/bin/meta_tool.dart b/metatool/bin/meta_tool.dart similarity index 56% rename from bin/meta_tool.dart rename to metatool/bin/meta_tool.dart index 0c30a98..603757a 100644 --- a/bin/meta_tool.dart +++ b/metatool/bin/meta_tool.dart @@ -1,7 +1,7 @@ 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 '../lib/base/i18n_io.dart'; +import '../lib/meta/module_meta_data.dart'; +import '../lib/meta/modules.dart'; import 'generator.dart'; @@ -18,28 +18,55 @@ void main(List args) { case 'all-modules': generator.out(moduleNames().join('\n')); break; - case 'print-modules-list': + case 'show-modules-list': generator.out(generator.createModules()); break; - case 'print-table': + 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 'print-sql': + case 'show-sql': if ((metaData = generator.checkModule(args, 1)) != null) { generator.out(generator.createSqlStatements(metaData!)); } break; - case 'print-data': + case 'show-data': if ((metaData = generator.checkModule(args, 1)) != null) { generator.out(generator.createModuleData(metaData!)); } break; - case 'update-modules': + case 'update-modules-files': generator.updateModules(generator); + generator.updatePages(generator); break; - case 'update-modules-list': + case 'update-modules-names': generator.updateModulesList(generator); break; case 'update-sql': @@ -58,22 +85,24 @@ void usage(Generator generator) { 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. + 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. - print-table MODULE - Prints the SQL table definition of MODULE. - print-data MODULE + 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 - print-sql MODULE + show-sql MODULE Creates the SQL statements (for the rest_server) of MODULE - update-modules-list + 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 + update-modules-files Generates all module files depending on the meta data. update-sql Generates all Sql statement files depending on the meta data. diff --git a/metatool/bin/page_generator.dart b/metatool/bin/page_generator.dart new file mode 100644 index 0000000..6c13ce6 --- /dev/null +++ b/metatool/bin/page_generator.dart @@ -0,0 +1,236 @@ +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 { + 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 = {}; + + /// 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(); + } + } +} diff --git a/metatool/bin/sql_generator.dart b/metatool/bin/sql_generator.dart new file mode 100644 index 0000000..8efbb1d --- /dev/null +++ b/metatool/bin/sql_generator.dart @@ -0,0 +1,143 @@ +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)); + } + } + } +} diff --git a/metatool/pubspec.yaml b/metatool/pubspec.yaml new file mode 100644 index 0000000..224883d --- /dev/null +++ b/metatool/pubspec.yaml @@ -0,0 +1,18 @@ +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 + diff --git a/pubspec.yaml b/pubspec.yaml index 1de4d83..ed0fb86 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -30,12 +30,12 @@ dependencies: 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 -- 2.39.5