* new: Generator contains all meta data creation parts.
* new: i18n_text_parser parses Dart files for I18N texts.
* new: i18n allows multi language support.
* new: yaml_merger merges SQL statement definitions,
normally generated files and hand made files.
* rest_server: scripts for simple installing on the backend.
web/
linux/
pubspec.lock
+tools/meta_tool
+tools/yaml_merger
--- /dev/null
+#! /bin/sh
+EXE=tools/meta_tool
+test ! -x $EXE && ./ReCreateMetaTool
+$EXE $*
+
--- /dev/null
+import 'dart:io';
+
+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 {
+ /// 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) {
+ out('+++ too few arguments. Missing MODULE.');
+ } else if ((rc = moduleByName(args[index])) == null) {
+ out('+++ 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.list;
+ 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();
+ buffer.write('// DO NOT CHANGE. This file is created by the meta_tool\n');
+ buffer.write("import '../../base/defines.dart';\n");
+ buffer.write("import '../../base/helper.dart';\n");
+ buffer.write('class ${module.moduleNameSingular}Data{\n');
+ for (var item in module.list) {
+ buffer.write(' ' + module.dartType(item.dataType) + '? ${item.name};\n');
+ }
+ buffer.write(' ${module.moduleNameSingular}Data({');
+ String separator = '';
+ for (var item in module.list) {
+ buffer.write('$separator this.${item.name}');
+ separator = ',';
+ }
+ buffer.write('});\n');
+ buffer
+ .write(' ${module.moduleNameSingular}Data.fromYaml(YamlMap data) {\n');
+ for (var item in module.list) {
+ final name = item.columnName;
+ final type = item.dataType.toString();
+ //id = data.containsKey('id') ? int.tryParse(data['id']) : null;
+ buffer.write(
+ " ${item.name} = data.containsKey('$name') ? valueOf($type, data['$name']) : null;\n");
+ }
+ buffer.write(' }\n');
+ buffer.write(' static DataType? dataTypeOf(String name) {\n');
+ buffer.write(' DataType? rc;\n');
+ buffer.write(' switch(name){\n');
+ for (var item in module.list) {
+ buffer.write(" case '${item.name}':\n");
+ buffer.write(' rc = ${item.dataType};\n');
+ buffer.write(' break;\n');
+ }
+ buffer.write(''' default:
+ break;
+ }
+ return rc;
+ }
+''');
+ buffer.write('}\n');
+
+ 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 'dart:io';\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.list;
+ 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 {
+ print('+++ missing class in $node');
+ rc = filenameToClass(node.replaceFirst('.dart', ''));
+ }
+ return rc;
+ }
+
+ /// Replaces print().
+ void out(String line) {
+ stdout.write(line + '\n');
+ }
+ /// 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) {
+ print('+++ 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 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) {
+ print('+++ unknown module: $name');
+ } else {
+ String filename = 'rest_server/data/sql/${name.toLowerCase()}.sql.yaml';
+ writeFile(filename, generator.createSqlStatements(module));
+ }
+ }
+ }
+
+ void writeFile(String filename, String contents) {
+ out('creating $filename ...');
+ final file = File(filename);
+ file.writeAsStringSync(contents);
+ }
+}
--- /dev/null
+import "dart:io";
+
+import 'package:dart_bones/dart_bones.dart';
+import "package:path/path.dart";
+
+void main(List<String> args) {}
+
+class I18nTextParser {
+ final BaseLogger logger;
+ final modules = <String, Map<String, String>>{};
+ final foundFiles = <String>{};
+ var regExpFiles = RegExp(r'.dart$');
+ String currentModule = '';
+ final moduleVariables = <String, String>{};
+ String currentFile = '';
+ List<String> lines = [];
+ int currentLineNo = 0;
+ // ...........................1.............1.........2
+ final regExpModule =
+ RegExp(r'''(\w+) = (I18N\(\)|i18N)\.module\(["'](.*?)['"]\);''');
+ final regExpDelimiter = RegExp(r'''["']''');
+ // ..........1.............1..2..............................2
+ final regExpText =
+ RegExp(r'(i18N|I18N\(\))\.(tr|trPlural|trMulti|trWithArgs)\(');
+ final regExpStringConstant = RegExp(r'''^\s*(r?)(["'])(.*?)\2''');
+ final regExpVariable = RegExp(r'^\w+');
+ final regExpEmptyString = RegExp(r'^\s*$');
+
+ I18nTextParser(this.logger);
+
+ /// Gets the text (multiple lines) from the [arguments].
+ /// [arguments] is the string behind the 'trMulti('.
+ void handleMultiText(String arguments) {
+ logger.error('not implemented: trMulti()');
+ }
+
+ /// Gets the text (multiple lines) from the [arguments] .
+ /// [arguments] is the string behind the 'trPlural('.
+ void handlePluralText(String arguments) {
+ logger.error('not implemented: trPlural()');
+ }
+
+ /// Gets the text (single line) from the [arguments] and store it.
+ /// [arguments] is the string behind the 'tr('.
+ void handleSimpleText(String arguments) {
+ RegExpMatch? match;
+ String module = '!global';
+ int lineNo = currentLineNo;
+ if (regExpEmptyString.firstMatch(arguments) != null) {
+ arguments = lineNo >= lines.length ? '' : lines[lineNo++].trimLeft();
+ }
+ String? key;
+ if ((match = regExpStringConstant.firstMatch(arguments)) != null) {
+ key = match?.group(3);
+ arguments = arguments.substring(match!.end).trimLeft();
+ if (arguments.startsWith(',')) {
+ arguments = arguments.substring(1).trimLeft();
+ if (arguments.isEmpty) {
+ arguments = lineNo >= lines.length ? '' : lines[lineNo++].trimLeft();
+ }
+ if ((match = regExpStringConstant.firstMatch(arguments)) != null) {
+ module = match!.group(3)!;
+ } else if ((match = regExpVariable.firstMatch(arguments)) != null) {
+ final variable = match!.group(0);
+ if (moduleVariables.containsKey(variable)) {
+ module = moduleVariables[variable]!;
+ } else {
+ logger.error(
+ '$currentFile-$currentLineNo: unknown module variable: $variable');
+ }
+ } else {
+ module = currentModule;
+ }
+ }
+ }
+ if (key == null) {
+ logger.error(
+ '$currentFile-$currentLineNo: cannot analyse parameters of tr()');
+ } else {
+ putText(key, currentFile, currentLineNo, module);
+ }
+ }
+
+ /// Gets the text (with placeholders) from the [arguments] and store it.
+ /// [arguments] is the string behind the 'trArgs('.
+ void handleTextWithArgs(String? arguments) {
+ logger.error('not implemented: trArgs()');
+ }
+
+ /// Stores the [key] in the [module] map with location info [filename] and
+ /// [lineNo].
+ void putText(String key, String filename, int lineNo, [String module = '']) {
+ if (module.isEmpty) {
+ module = currentModule;
+ }
+ if (!modules.containsKey(module)) {
+ modules[module] = <String, String>{};
+ }
+ if (modules[module]!.containsKey(key)) {
+ modules[module]![key] = modules[module]![key]! + '\n#: $filename:$lineNo';
+ } else {
+ modules[module]![key] = '#: $filename:$lineNo';
+ }
+ }
+
+ /// Scans recursively all dart files of the [directory].
+ void scanDirectory(
+ String directory,
+ ) {
+ final base = Directory(directory);
+ final subDirs = [];
+ try {
+ for (var file in base.listSync()) {
+ if (file is Directory) {
+ subDirs.add(file.path);
+ continue;
+ }
+ if (regExpFiles.firstMatch(file.path) == null) {
+ continue;
+ }
+ if (file is File) {
+ scanFile(file, directory);
+ } else {
+ logger.error('ignored: $directory/${file.path}');
+ }
+ }
+ } on FileSystemException catch (exc) {
+ logger.error('ignored: $directory: $exc');
+ }
+ for (var node in subDirs) {
+ scanDirectory(join(directory, node));
+ }
+ }
+
+ /// Scans the [file] for I18N texts.
+ /// [directory]: used for logging.
+ void scanFile(File file, String directory) {
+ try {
+ currentFile = join(directory, file.path);
+ lines = file.readAsLinesSync();
+ moduleVariables.clear();
+ currentLineNo = 0;
+ for (var line in lines) {
+ ++currentLineNo;
+ RegExpMatch? match;
+ if ((match = regExpModule.firstMatch(line)) != null) {
+ currentModule = match!.group(3)!;
+ moduleVariables[match.group(1)!] = currentModule;
+ } else {
+ for (match in regExpText.allMatches(line)) {
+ final restLine = line.substring(match.end);
+ switch (match.group(2)!) {
+ case 'tr':
+ handleSimpleText(restLine);
+ break;
+ case 'trMulti':
+ handleMultiText(restLine);
+ break;
+ case 'trPlural':
+ handlePluralText(restLine);
+ break;
+ case 'trArgs':
+ handleTextWithArgs(restLine);
+ break;
+ }
+ }
+ }
+ }
+ } on FileSystemException catch (exc) {
+ logger.error('ignored: $directory: $exc');
+ }
+ }
+
+ /// Writes the *.pot files to the [directory].
+ void writePot(String directory) {
+ FileSync().ensureDirectory(directory);
+ for (var module in modules.keys) {
+ final map = modules[module]!;
+ final keys = map.keys.toList();
+ keys.sort();
+ final buffer = StringBuffer('# Texts of module $module, parsed by i18n_text_parser\n');
+ for (var key in keys) {
+ buffer.writeln();
+ buffer.writeln(map[key]);
+ final key2 = key.replaceAll('"', r'\"');
+ buffer.writeln('msgid "$key2"');
+ buffer.writeln('msgstr ""');
+ }
+ final fn = join(directory, '$module.pot');
+ logger.log('writing $fn', LEVEL_DETAIL);
+ File(fn).writeAsStringSync(buffer.toString());
+ }
+ }
+}
-import 'dart:io';
-
+import 'package:exhibition/base/i18n.dart';
import 'package:exhibition/meta/module_meta_data.dart';
import 'package:exhibition/meta/modules.dart';
-import 'package:path/path.dart';
-void usage(){
- out('''Usage: meta_tool MODE MODULE
- Generate code specified by MODE for the module MODULE.
-MODE:
- all-modules
- Prints the module names.
- print-modules
- Prints the file lib/meta/modules.dart.
- print-table MODULE
- Prints the SQL table definition of MODULE.
- print-data MODULE
- Creates the data storage class of MODULE
- update-modules
- Generates all module files depending on the meta data.
-MODULE:
- The name of the module, e.g. "Users"
-Example:
-meta_tool print-table Users
-''');
-}
+import 'generator.dart';
+
void main(List<String> args) {
+ final generator = Generator();
+ I18N.internal('data/i18n');
if (args.isEmpty) {
- usage();
- out('+++ missing arguments.');
+ usage(generator);
+ generator.out('+++ missing arguments.');
} else {
ModuleMetaData? metaData;
switch (args[0]) {
case 'all-modules':
- out(moduleNames().join('\n'));
+ generator.out(moduleNames().join('\n'));
break;
case 'print-modules':
- out(createModules());
+ generator.out(generator.createModules());
break;
case 'print-table':
- if ((metaData = checkModule(args, 1)) != null) {
- out(metaData!.createDbTable());
+ 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 = checkModule(args, 1)) != null) {
- out(metaData!.createModuleData());
+ if ((metaData = generator.checkModule(args, 1)) != null) {
+ generator.out(generator.createModuleData(metaData!));
}
break;
case 'update-modules':
- updateModules();
+ generator.updateModules(generator);
+ break;
+ case 'update-sql':
+ generator.updateSql(generator);
break;
default:
- usage();
- out('+++ unknown MODE: ${args[0]}.');
+ usage(generator);
+ generator.out('+++ unknown MODE: ${args[0]}.');
break;
}
}
}
-ModuleMetaData? checkModule(List<String> args, int index) {
- ModuleMetaData? rc;
- if (index >= args.length) {
- out('+++ too few arguments. Missing MODULE.');
- } else {
- rc = moduleByName(args[index]);
- }
- return rc;
-}
-
-String createModules() {
- final buffer = StringBuffer();
- buffer.write('// DO NOT CHANGE. This file is created by the meta_tool\n');
- buffer.write("import 'dart:io';\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].
-ModuleMetaData moduleByName(String name) {
- ModuleMetaData rc;
- switch (name) {
-''');
- String? firstClass;
- for (var module in modules) {
- buffer.write(" case 'Users':\n");
- final className = findClass(fileOfModule[module]!);
- firstClass ??= className;
- buffer.write(" rc = $className();\n");
- buffer.write(" break;\n");
- }
- firstClass ??= 'MissingFirstClass';
- buffer.write(''' default:
- stdout.write('+++ unknown module: \$name. Using "$firstClass".');
- rc = $firstClass();
- 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(''' ];
-}
+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
+ Prints the file lib/meta/modules.dart.
+ 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
+ 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
''');
- 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 {
- out('+++ missing class in $node');
- rc = filenameToClass(node.replaceFirst('.dart', ''));
- }
- return rc;
-}
-
-/// Replaces print().
-void out(String line) {
- stdout.write(line + '\n');
-}
-
-void writeFile(String filename, String contents){
- out('creating $filename ...');
- final file = File(filename);
- file.writeAsStringSync(contents);
-}
-void updateModules(){
- writeFile('lib/meta/modules.dart', createModules());
- final modules = moduleNames();
- for (var name in modules){
- ModuleMetaData module = moduleByName(name);
- String filename = classToFilename(module.moduleNameSingular) + '_data.dart';
- final directory = name.toLowerCase();
- writeFile('lib/page/$directory/$filename', module.createModuleData());
- }
-}
\ No newline at end of file
--- /dev/null
+import 'dart:io';
+
+import 'package:path/path.dart';
+import 'package:dart_bones/dart_bones.dart';
+
+/// Implements a merge tool for yaml files.
+/// @see merge() for description of merge.
+class Merger {
+ RegExp regExpKey = RegExp(r'^([a-zA-Z]\w+):\s*$');
+ RegExp regExpKeyValue = RegExp(r'^ ');
+ final BaseLogger logger;
+ final precedenceKeys = <String, String>{};
+ Merger(this.logger);
+
+ /// Parses the keys of a given [file] into [this.precedenceKeys].
+ void parseKeys(File file){
+ precedenceKeys.clear();
+ final lines = file.readAsLinesSync();
+ var state = 'undef';
+ var lastKey = '';
+ final keyValue = [];
+ for (var line in lines){
+ final match = regExpKey.firstMatch(line);
+ if (match != null){
+ if (keyValue.isNotEmpty){
+ precedenceKeys[lastKey] = keyValue.join('\n');
+ keyValue.clear();
+ }
+ state = 'inKey';
+ lastKey = match.group(1)!;
+ } else {
+ if (regExpKeyValue.firstMatch(line) != null){
+ if (state == 'inKey'){
+ keyValue.add(line);
+ }
+ }
+ }
+ }
+ if (keyValue.isNotEmpty){
+ precedenceKeys[lastKey] = keyValue.join('\n');
+ }
+ }
+ /// Merges the [fileSource] and [filePreference] into the [target] file.
+ void mergeOneFile(File fileSource, File filePreference, String target) {
+ parseKeys(filePreference);
+ final output = <String>[];
+ final foundKeys = <String>{};
+ final lines = fileSource.readAsLinesSync();
+ var state = 'undef';
+ var lastKey = '';
+ final keyValue = [];
+ for (var line in lines){
+ final match = regExpKey.firstMatch(line);
+ if (match != null) {
+ if (keyValue.isNotEmpty) {
+ output.add(lastKey + ':');
+ output.add(precedenceKeys.containsKey(lastKey)
+ ? precedenceKeys[lastKey]!
+ : keyValue.join('\n'));
+ }
+ keyValue.clear();
+ state = 'inKey';
+ lastKey = match.group(1)!;
+ if (foundKeys.contains(lastKey)){
+ logger.error('key $lastKey found multiple times');
+ }
+ foundKeys.add(lastKey);
+ } else {
+ if (regExpKeyValue.firstMatch(line) != null){
+ if (state == 'inKey'){
+ keyValue.add(line);
+ }
+ } else {
+ output.add(line);
+ }
+ }
+ }
+ if (keyValue.isNotEmpty){
+ output.add(lastKey + ':');
+ if (precedenceKeys.containsKey(lastKey)) {
+ output.add(precedenceKeys[lastKey]!);
+ } else {
+ output.add(keyValue.join('\n'));
+ }
+ }
+ for (var key in precedenceKeys.keys){
+ if (! foundKeys.contains(key)){
+ output.add(key + ':');
+ output.add(precedenceKeys[key]!);
+ }
+ }
+ File(target).writeAsStringSync(output.join('\n') + '\n');
+ }
+ /// Merges or copies files from two source directories into a target directory.
+ /// [pathSource] contains files with lower priorities.
+ /// [pathPreferences] contains files with higher priorities.
+ /// [pathTarget] is the target directory.
+ /// Only files with the same node will be merged.
+ /// Merging: the two source files contains keys. If a key is part of both
+ /// files the key content of of the higher priority file is taken.
+ void merge(String pathSource, String pathPreference, String pathTarget) {
+ final dirSource = Directory(pathSource);
+ final dirPreference = Directory(pathPreference);
+ final dirTarget = Directory(pathTarget);
+ if (!dirSource.existsSync()) {
+ logger.error('source not a directory: $pathSource');
+ } else if (!dirPreference.existsSync()) {
+ logger.error('source not a directory: $pathPreference');
+ } else if (!dirTarget.existsSync()) {
+ logger.error('source not a directory: $pathTarget');
+ } else {
+ /// Copy/merge the files from dirSource:
+ for (var file in dirSource.listSync()) {
+ final node = basename(file.path);
+ if (! node.endsWith('.yaml')){
+ continue;
+ }
+ final fnPreference = join(pathPreference, node);
+ final fnTarget = join(pathTarget, node);
+ final filePreference = File(fnPreference);
+ if (!(file is File)) {
+ logger.error('not a file: ${file.path} ignoring...');
+ } else if (filePreference.existsSync()) {
+ mergeOneFile(file, filePreference, fnTarget);
+ } else {
+ logger.log('copying ${file.path}', LEVEL_DETAIL);
+ file.copy(fnTarget);
+ }
+ }
+
+ /// Copy the "single" files from preferences:
+ for (var file in dirPreference.listSync()) {
+ final node = basename(file.path);
+ if (! node.endsWith('.yaml')){
+ continue;
+ }
+ final fnSource = join(pathSource, node);
+ final fnTarget = join(pathTarget, node);
+ File fileSource = File(fnSource);
+ if (file is File && !fileSource.existsSync()) {
+ logger.log('copying ${file.path}', LEVEL_DETAIL);
+ file.copy(fnTarget);
+ }
+ }
+ }
+ }
+}
+void main(List<String> args){
+ final logger = MemoryLogger(LEVEL_DETAIL);
+ if (args.length != 3){
+ print('''Usage: yaml_merger DIR_SOURCE DIR_PREFERENCE DIR_OUTPUT
+ Merges the yaml files from DIR_SOURCE (with lower priority) and DIR_PREFERENCE
+ (with higher priority) into files in DIR_OUTPUT.
+Version: 0.1.0
+Example:
+yaml_merger data/sql data/sql/precedence /tmp/sql
+''');
+ print('+++ wrong count of arguments: ${args.length} instead of 3');
+ } else {
+ final merger = Merger(logger);
+ merger.merge(args[0], args[1], args[2]);
+ }
+}
\ No newline at end of file
--- /dev/null
+class I18N {
+ static I18N? instance;
+ Map<String, Map<String, String>> modules = {};
+ String locale = 'de';
+ factory I18N() {
+ return instance!;
+ }
+ I18N.internal(String directory) {
+ instance = this;
+ }
+ String module(String name) {
+ return name;
+ }
+
+ /// Translates the [key] into the local language using the namespace [module].
+ /// Returns the translation or (if not found) the key.
+ String tr(String key, [String module = '!global']) {
+ String? rc;
+ final mapModule = modules[module];
+ if (mapModule != null) {
+ rc = mapModule[key];
+ if (rc == null) {
+ if (module == '!global') {
+ rc = key;
+ } else {
+ rc = tr(key, '!global');
+ }
+ }
+ }
+ return rc!;
+ }
+
+ /// Translates the [key] into the local language using the namespace [module].
+ /// [key] contains placeholders "{0}", "{1}"... which will be replaced by
+ /// the matching entry in the array [args].
+ /// Returns the translation or (if not found) the key.
+ String trArgs(String key, List<Object> args, [String module = '!global']) {
+ String rc = tr(key, module);
+ for (var ix = 0; ix < args.length; ix++) {
+ rc = rc.replaceAll('{$ix}', args[ix].toString());
+ }
+ return rc;
+ }
+
+ /// Translates the [keySingular] or [keyPlural] into the local language
+ /// using the namespace [module].
+ /// If [count] is 1 the translation of [keySingular] is returned.
+ /// Otherwise the translation of [keyPlural] is returned.
+ /// Returns the translation or (if not found) the key.
+ String trPlural(String keySingular, String keyPlural, int count,
+ [String module = '!global']) {
+ String rc = tr(count == 1 ? keySingular : keyPlural, module);
+ return rc;
+ }
+}
-import 'package:path/path.dart';
import '../base/defines.dart';
+/// Describes a button of a page.
+class ButtonMetaData extends WidgetMetaData {
+ ButtonMetaData(String name) : super(name, WidgetType.button) {
+ //@ToDo
+ }
+}
+/// Describes a field related to a database column.
+class DbFieldMetaData extends WidgetMetaData {
+ PropertyMetaData? reference;
+ DbFieldMetaData(String name, String reference)
+ : super(name, WidgetType.dbField) {
+ //@ToDo
+ }
+}
+
+enum DisplayType {
+ line,
+ multiLine,
+ combobox,
+}
+
+/// Describes a field of the page.
+class FieldMetaData extends WidgetMetaData {
+ DataType dataType;
+ DisplayType displayType;
+ final String options;
+ FieldMetaData(String name, this.options,
+ {this.dataType = DataType.string, this.displayType = DisplayType.line})
+ : super(name, WidgetType.field);
+}
+
class MetaException extends FormatException {}
/// Stores the meta data of a module.
class ModuleMetaData {
+ static final metaColumns = [
+ 'created',
+ 'createdBy',
+ 'changed',
+ 'changedBy',
+ 'deleted',
+ 'deletedBy'
+ ];
+
/// The module name, e.g. users
final String moduleName;
item.module = this;
properties[item.name] = item;
if (item.columnName.isEmpty) {
- final prefix = shortModifiedLabel &&
- ['created', 'createdBy', 'changed', 'changedBy']
- .contains(item.name)
+ final prefix = shortModifiedLabel && metaColumns.contains(item.name)
? ''
: columnPrefix + '_';
item.columnName = prefix + item.name.toLowerCase();
}
}
- /// Returns a DDL statement creating the database table.
- String createDbTable() {
- 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(' ' + mySqlType(item.dataType, item.size));
- buffer.write(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() {
- final buffer = StringBuffer();
- buffer.write('// DO NOT CHANGE. This file is created by the meta_tool\n');
- buffer.write("import '../../base/defines.dart';\n");
- buffer.write("import '../../base/helper.dart';\n");
- buffer.write('class ${moduleNameSingular}Data{\n');
- for (var item in list) {
- buffer.write(' ' + dartType(item.dataType) + '? ${item.name};\n');
- }
- buffer.write(' ${moduleNameSingular}Data({');
- String separator = '';
- for (var item in list) {
- buffer.write('$separator this.${item.name}');
- separator = ',';
- }
- buffer.write('});\n');
- buffer.write(' ${moduleNameSingular}Data.fromYaml(YamlMap data) {\n');
- for (var item in list) {
- final name = item.columnName;
- final type = item.dataType.toString();
- //id = data.containsKey('id') ? int.tryParse(data['id']) : null;
- buffer.write(
- " ${item.name} = data.containsKey('$name') ? valueOf($type, data['$name']) : null;\n");
- }
- buffer.write(' }\n');
- buffer.write(' static DataType? dataTypeOf(String name) {\n');
- buffer.write(' DataType? rc;\n');
- buffer.write(' switch(name){\n');
- for (var item in list) {
- buffer.write(" case '${item.name}':\n");
- buffer.write(' rc = ${item.dataType};\n');
- buffer.write(' break;\n');
- }
- buffer.write(''' default:
- break;
- }
- return rc;
- }
-''');
- buffer.write('}\n');
-
- return buffer.toString();
- }
-
+ /// Returns a given [dataType] as string.
String dartType(DataType dataType) {
String rc;
switch (dataType) {
}
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.
+ Iterable<PropertyMetaData> standardColumns([String included = '']) {
+ final rc = list.where(
+ (item) => item.name == included || !metaColumns.contains(item.name));
+ return rc;
+ }
}
-/// Stores the meta data of a module property.
+class PageMetaData {
+ final String name;
+ final PageType pageType;
+ List<FieldMetaData>? fields;
+ PageMetaData(
+ this.name,
+ this.pageType,
+ this.fields,
+ );
+}
+
+enum PageType { create, custom, delete, edit, list }
+
+/// Stores the meta data of a module property stored as column in a database.
class PropertyMetaData {
/// Name of the property (Dart name).
final String name;
ModuleMetaData module = ModuleMetaData('', []);
+ final String label;
/// A colon delimited list of options, e.g. ':notnull:unique:'.
final String options;
/// The foreign key if dataType is DataType.reference, e.g. 'users.user_id'
String? reference;
- PropertyMetaData(this.name, this.dataType, this.options, this.widgetType,
+ PropertyMetaData(
+ this.name, this.dataType, this.options, this.widgetType, this.label,
{this.columnName = '', this.size = 0, this.reference});
}
-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;
+/// Describes a widget used in the page.
+/// Base class of all other widgets.
+class WidgetMetaData {
+ final WidgetType widgetType;
+ final String name;
+ WidgetMetaData(this.name, this.widgetType);
}
-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();
-}
+enum WidgetType { button, field, dbField }
import 'module_meta_data.dart';
import 'users_meta.dart';
/// Returns the meta data of the module given by [name].
-ModuleMetaData moduleByName(String name) {
- ModuleMetaData rc;
+/// Returns null if not found.
+ModuleMetaData? moduleByName(String name) {
+ ModuleMetaData? rc;
switch (name) {
case 'Users':
rc = UserMeta();
break;
default:
- stdout.write('+++ unknown module: $name. Using "UserMeta".');
- rc = UserMeta();
break;
}
return rc;
'Users',
];
}
+
--- /dev/null
+import '../base/defines.dart';
+import 'module_meta_data.dart';
+import '../base/i18n.dart';
+
+final i18N = I18N();
+final M = i18N.module("Roles");
+
+class RoleMeta extends ModuleMetaData {
+ static RoleMeta instance = RoleMeta.internal();
+ RoleMeta.internal()
+ : super(
+ 'Roles',
+ [
+ PropertyMetaData('id', DataType.reference, ':primary:', 'combo',
+ i18N.tr('Id', M)),
+ PropertyMetaData(
+ 'name', DataType.string, ':notnull:', '', i18N.tr('Name', M),
+ size: 64),
+ PropertyMetaData('created', DataType.datetime, ':hidden:', '',
+ i18N.tr('Created', M)),
+ PropertyMetaData('createdBy', DataType.string, ':hidden:', '',
+ i18N.tr('Created by', M),
+ size: 32),
+ PropertyMetaData('changed', DataType.datetime, ':hidden:', '',
+ i18N.tr('Changed', M)),
+ PropertyMetaData('changedBy', DataType.string, ':hidden:', '',
+ i18N.tr('Changed by', M),
+ size: 32),
+ ],
+ );
+ factory RoleMeta() {
+ return instance;
+ }
+}
import '../base/defines.dart';
import 'module_meta_data.dart';
+import '../base/i18n.dart';
+final i18N = I18N();
+final M = i18N.module("Users");
class UserMeta extends ModuleMetaData {
static UserMeta instance = UserMeta.internal();
: super(
'Users',
[
- PropertyMetaData('id', DataType.reference, ':primary:', 'combo'),
+ PropertyMetaData('id', DataType.reference, ':primary:', 'combo',
+ i18N.tr('Id', M)),
PropertyMetaData('name', DataType.string, ':notnull:', '',
+ i18N.tr('Name', M),
size: 64),
- PropertyMetaData(
- 'displayName', DataType.string, ':unique:notnull:', '',
+ PropertyMetaData('displayName', DataType.string, ':unique:notnull:',
+ '', i18N.tr('Display name', M),
size: 32),
PropertyMetaData('email', DataType.string, ':unique:notnull:', '',
+ i18N.tr('EMail', M),
size: 255),
- PropertyMetaData('role', DataType.reference, ':notnull:', ''),
- PropertyMetaData('created', DataType.datetime, '', ''),
- PropertyMetaData('createdBy', DataType.string, '', '', size: 32),
- PropertyMetaData('changed', DataType.datetime, '', ''),
- PropertyMetaData('changedBy', DataType.string, '', '', size: 32),
+ PropertyMetaData('role', DataType.reference, ':notnull:', '',
+ i18N.tr('Role', M), reference: 'roles.role_id'),
+ PropertyMetaData('created', DataType.datetime, ':hidden:', '',
+ i18N.tr('Created', M)),
+ PropertyMetaData('createdBy', DataType.string, ':hidden:', '',
+ i18N.tr('Created by', M),
+ size: 32),
+ PropertyMetaData('changed', DataType.datetime, ':hidden:', '',
+ i18N.tr('Changed', M)),
+ PropertyMetaData('changedBy', DataType.string, ':hidden:', '',
+ i18N.tr('Changed by', M),
+ size: 32),
],
- tableName: 'loginusers',
- shortModifiedLabel: true,
);
factory UserMeta() {
return instance;
+++ /dev/null
-// DO NOT CHANGE. This file is created by the meta_tool
-class User{
- int? id;
- String? name;
- String? displayName;
- String? email;
- int? role;
- DateTime? created;
- String? createdBy;
- DateTime? changed;
- String? changedBy;
- User({ this.id, this.name, this.displayName, this.email, this.role, this.created, this.createdBy, this.changed, this.changedBy});
-}
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../base/defines.dart';
import 'user_state.dart';
-import 'user_data.dart';
import 'package:equatable/equatable.dart';
class User extends StatefulWidget {
@override
void initState() {
- context.read<UserBloc>().add(UserEvent(EventSource.undef, EventCardinality.undef));
+ //context.read<UserBloc>().add(UserEvent(EventSource.undef, EventCardinality.undef));
super.initState();
}
builder: (context, UserState state) {
return Column(
children: [
- ResultDisplay(
- text: _getDisplayText(state.calculationModel),
- ),
+ //ResultDisplay(
+ // text: _getDisplayText(state.calculationModel),
+ //),
Row(
children: [
_getButton(text: '7', onTap: () => numberPressed(7)),
],
),
Spacer(),
- UserHistoryContainer(
- calculations: state.history.reversed.toList())
+ //UserHistoryContainer(
+ // calculations: state.history.reversed.toList())
],
);
},
required void Function() onTap,
Color backgroundColor = Colors.white,
Color textColor = Colors.black}) {
+ /*
return CalculatorButton(
label: text,
onTap: onTap,
backgroundColor: backgroundColor,
labelColor: textColor,
);
+ */
+ return SizedBox(width: 10);
}
numberPressed(int number) {
- context.read<UserBloc>().add(NumberPressed(number: number));
+ //context.read<UserBloc>().add(NumberPressed(number: number));
}
operatorPressed(String operator) {
- context.read<UserBloc>().add(OperatorPressed(operator: operator));
+ //context.read<UserBloc>().add(OperatorPressed(operator: operator));
}
calculateResult() {
- context.read<UserBloc>().add(CalculateResult());
+ //context.read<UserBloc>().add(CalculateResult());
}
clear() {
- context.read<UserBloc>().add(ClearUser());
+ //context.read<UserBloc>().add(ClearUser());
}
-
+ /*
String _getDisplayText(UserModel model) {
if (model.result != null) {
return '${model.result}';
return "${model.result ?? 0}";
}
+ */
}
--- /dev/null
+#! /bin/bash
+APP=rest_server
+TRG=tools/$APP
+pub get
+dart compile exe bin/$APP.dart -o $TRG
+ls -ld $TRG
type: record
parameters: [ ":id" ]
sql: "select * from loginusers where user_id=:id;"
+delete:
+ type: delete
+ parameters: [ ":id" ]
+ sql: "delete * from loginusers where user_id=:id;"
update:
type: update
parameters: [":id", ":name", ":displayname", ":email", ":changedby"]
service:
address: 0.0.0.0
port: 58021
- dataDirectory: /var/cache/rest_server/data
- sqlDirectory: /etc/rest_server/sql.d
+ dataDirectory: /var/cache/exhibition/data
+ sqlDirectory: /usr/share/exhibition/rest_server/data/sql
threads: 2
watchDogPause: 60
# logFile: /var/log/local/exhibition.log
starter: executable,
user: appName,
group: appName,
- description: 'A REST server serving the POLLECTOR project',
+ description: 'A REST server serving the Exhibition project',
);
}
}
static String version() => _version;
}
-/// Datenklasse für eine Isolate-Instanz.
+/// Implements an isolate instance.
class ServiceWorker {
/// for unittests:
static int maxRequests = 0;
logger.log('thread $threadId stopped');
}
- /// Prüft, ob ein Isolate auf eine Watchdog-Anfrage antwortet.
- /// Hintergrund: Wenn eine DB-Verbindung abbricht, tritt beim nächsten
- /// DB-Zugriff eine SocketException auf, die nicht abgefangen werden kann.
- /// Der Thread (Isolate) ist dann tot.
- /// Daher wird vom Observer-Thread regelmäßig eine Anfrage verschickt.
- /// Kommt keine Antwort, ist kein Empfangsthread mehr erreichbar, das Programm
- /// wird mittels exit() abgebrochen, damit es von SystemD erneut gestartet
- /// wird.
+ /// Checks whether an isolate instance is reponding to a request.
+ /// Background: If one DB connection breaks, a SocketException occurs
+ /// the next time the DB is accessed, which cannot be intercepted.
+ // The thread (Isolate) is then dead.
+ // Therefore, the observer thread regularly sends a request.
+ // f there is no response, the receiving thread can no longer be reached,
+ // the program is aborted with exit () so that it can be restarted by SystemD.
Future observe() async {
final duration = Duration(
seconds:
}
}
- /// Ermittelt die Anfragenklasse ("what"), die Teil der URI ist.
+ /// Determines the request class ("what") that is part of the URI.
void prepareResource({bool withId = true}) {
if (!(currentRequest?.requestedUri.path.contains('watchdog') ?? false)) {
logger.log(currentRequest!.requestedUri.path, LEVEL_FINE);
return buffer;
}
- /// Fordert per POST eine Aktion an.
- /// [what] legt die Aktion fest.
- /// [body] ist der Text, der mit der Anfrage mitgeliefert wird.
- /// [headers] sind HTTP-Headerkomponenten.
- /// Liefert die Antwort der Anfrage.
+ /// Requests an action via POST.
+ /// [what] defines the action.
+ /// [body] is the text that is supplied with the request.
+ /// [headers] are HTTP header components.
+ /// Returns the answer to the request.
Future<String> runRequest(String what,
{String? body, Map<String, String>? headers}) async {
final port = configuration.asInt('port', section: 'service') ?? 58011;
return rc;
}
- /// Speichert [content] im Verzeichnis [directory] unter dem Namen [node].
+ /// Stores [content] in the directory [directory] under the name [node].
Future storeFile(String directory, String node, String content) async {
if (directory.isEmpty) {
logger.error('storeFile: missing data directory');
--- /dev/null
+PROJECT=exhibition
--- /dev/null
+import 'package:dart_bones/dart_bones.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:path/path.dart';
+
+import "../bin/i18n_text_parser.dart";
+
+void main() {
+ final logger = MemoryLogger(LEVEL_FINE);
+ FileSync.initialize(logger);
+ final fileSync = FileSync();
+ final baseDir = init(logger);
+ final targetDir = join(baseDir, nodeTarget);
+ test('parse', () {
+ final parser = I18nTextParser(logger);
+ parser.scanDirectory(baseDir);
+ parser.writePot(targetDir);
+ expect(parser.modules, isNotEmpty);
+ expect(parser.modules.containsKey('Example'), isTrue);
+ expect(parser.modules.containsKey('!global'), isTrue);
+ expect(parser.modules.containsKey('Sample'), isTrue);
+ expect(parser.modules['Example']!.containsKey('introduction'), isTrue);
+ expect(parser.modules['Sample']!.containsKey('info'), isTrue);
+ var fn = join(targetDir, 'Example.pot');
+ var content = fileSync.fileAsString(fn);
+ expect(content, '''# Texts of module Example, parsed by i18n_text_parser
+
+#: /tmp/unittest/i18n/lib/base/simple.dart:5
+msgid "description"
+msgstr ""
+
+#: /tmp/unittest/i18n/lib/base/simple.dart:5
+msgid "introduction"
+msgstr ""
+''');
+ fn = join(targetDir, 'Sample.pot');
+ content = fileSync.fileAsString(fn);
+ expect(content, '''# Texts of module Sample, parsed by i18n_text_parser
+
+#: /tmp/unittest/i18n/lib/base/args.dart:5
+msgid "info"
+msgstr ""
+''');
+ fn = join(targetDir, '!global.pot');
+ content = fileSync.fileAsString(fn);
+ expect(content, '''# Texts of module !global, parsed by i18n_text_parser
+
+#: /tmp/unittest/i18n/lib/base/simple.dart:10
+#: /tmp/unittest/i18n/lib/base/args.dart:9
+msgid "Status line"
+msgstr ""
+''');
+ });
+}
+
+const nodeTarget = 'output';
+
+String init(MemoryLogger logger) {
+ final fileSync = FileSync();
+ final baseDir = fileSync.tempDirectory('i18n', subDirs: 'unittest');
+ final subDir = fileSync.tempDirectory('base', subDirs: 'unittest/i18n/lib');
+ fileSync.toFile(join(subDir, 'simple.dart'), r'''import 'dart:io';
+final i18N = I18N();
+final M = i18N.module('Example');
+String header(){
+ return i18N.tr('introduction', M) + " " + i18N.tr(
+ "description",
+ M);
+}
+String footer(){
+ return I18N().tr("Status line");
+}
+''');
+ fileSync.toFile(join(subDir, 'args.dart'), r'''import 'dart:io';
+final i18N = I18N();
+final M = i18N.module("Sample");
+String header(String user, String role){
+ return i18N.trArgs('Name: {0} Role: {1}', [user, role], M) + " " + i18N.tr(
+ r'info', M);
+}
+String footer(){
+ return I18N().tr('Status line');
+}
+''');
+ return baseDir;
+}
--- /dev/null
+import 'package:dart_bones/dart_bones.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:path/path.dart';
+
+import "../bin/yaml_merger.dart";
+
+const nodePrecedence = 'precedence';
+const nodeTarget = 'output';
+void main() {
+ final logger = MemoryLogger(LEVEL_FINE);
+ FileSync.initialize(logger);
+ final fileSync = FileSync();
+ final baseDir = init(logger);
+ final targetDir = join(baseDir, nodeTarget);
+ test('merge', () {
+ final merger = Merger(logger);
+ merger.merge(
+ baseDir, join(baseDir, nodePrecedence), join(baseDir, nodeTarget));
+ var content = fileSync.fileAsString(join(targetDir, 'users.sql.yaml'));
+ expect(content, r'''---
+# SQL statements of the module "Users":
+module: Users
+list:
+ type: list
+ parameters: [":name", ":role"]
+ sql: "select * from loginusers where user_name like :filterName;
+ and (:role is null or user_role=:role);"
+byId:
+ type: record
+ parameters: [ ":id" ]
+ sql: "select * from loginusers where user_id=:id;"
+insert:
+ type: insert
+ parameters: [":name", ":displayname", ":email", ":createdby"]
+ sql: "INSERT INTO loginusers(user_name, user_displayname, user_email, user_changedby)
+ VALUES(:name, :displayname, :email, NOW(), :createdby);"
+byName:
+ type: record
+ parameters: [":name"]
+ sql: "select * from loginusers where user_name=:name;"
+''');
+ content = fileSync.fileAsString(join(targetDir, 'roles.sql.yaml'));
+ expect(content, r'''---
+# SQL statements of the module "Roles":
+module: Roles
+list:
+ type: list
+ parameters: [":name", ":role"]
+ sql: "select * from loginusers where user_name like :filterName;
+ and (:role is null or user_role=:role);"
+''');
+ content = fileSync.fileAsString(join(targetDir, 'misc.sql.yaml'));
+ expect(content, r'''---
+# SQL statements of the module "Misc":
+module: Misc
+tables:
+ type: list
+ parameters: []
+ sql: "show tables;"
+''');
+ });
+}
+
+String init(MemoryLogger logger) {
+ final fileSync = FileSync();
+ final baseDir = fileSync.tempDirectory('yaml_merger', subDirs: 'unittest');
+ final precedenceDir = join(baseDir, nodePrecedence);
+ fileSync.ensureDirectory(precedenceDir);
+ fileSync.ensureDirectory(join(baseDir, nodeTarget));
+ fileSync.toFile(join(baseDir, 'users.sql.yaml'), r'''---
+# SQL statements of the module "Users":
+module: Users
+list:
+ type: list
+ parameters: []
+ sql: "select * from loginusers;"
+byId:
+ type: record
+ parameters: [ ":id" ]
+ sql: "select * from loginusers where user_id=:id;"
+insert:
+ type: insert
+ parameters: [":name", ":displayname", ":email", ":createdby"]
+ sql: "INSERT INTO loginusers(user_name, user_displayname, user_email, user_changedby)
+ VALUES(:name, :displayname, :email, NOW(), :createdby);"
+''');
+ fileSync.toFile(join(baseDir, 'roles.sql.yaml'), r'''---
+# SQL statements of the module "Roles":
+module: Roles
+list:
+ type: list
+ parameters: []
+ sql: select * from loginusers;
+''');
+ fileSync.toFile(join(precedenceDir, 'users.sql.yaml'), r'''---
+# Overriding SQL statements of the module "Users":
+module: Users
+list:
+ type: list
+ parameters: [":name", ":role"]
+ sql: "select * from loginusers where user_name like :filterName;
+ and (:role is null or user_role=:role);"
+byName:
+ type: record
+ parameters: [":name"]
+ sql: "select * from loginusers where user_name=:name;"
+''');
+ fileSync.toFile(join(precedenceDir, 'misc.sql.yaml'), r'''---
+# SQL statements of the module "Misc":
+module: Misc
+tables:
+ type: list
+ parameters: []
+ sql: "show tables;"
+''');
+ return baseDir;
+}
--- /dev/null
+#! /bin/bash
+APP=yaml_merger
+TRG=tools/$APP
+dart compile exe bin/$APP.dart -o $TRG
+ls -ld $TRG
--- /dev/null
+#! /bin/bash
+APP=$1
+APP2=$(echo ${APP:0:1} | tr a-z A-Z)${APP:1}
+function Usage(){
+ echo "Usage: InitProject PROJECT"
+ echo " Prepares the project PROJECT for using the framework exhibition"
+ echo "PROJECT: the name of the already existing project (created with 'flutter create')"
+ echo "Example:"
+ echo "InitProject myapp"
+ echo "+++ $*"
+}
+function MkDirs(){
+ for dir in tools rest_server/data; do
+ mkdir -p $dir
+ done
+}
+function EnsureDir(){
+ local dir=$1
+ if [ ! -d $dir ]; then
+ echo "creating $dir"
+ mkdir -p $dir
+ fi
+}
+function CopyFiles(){
+ local projDir=$(pwd)
+ cd ../exhibition
+ local srcDir=$(pwd)
+ for file in lib/base/*.dart lib/meta/module_meta_data.dart lib/meta/modules.dart \
+ rest_server/pubspec.yaml \
+ tools/CompileMerge tools/PackRestServer \
+ ; do
+ cd $projDir
+ local dir=$(dirname $file)
+ EnsureDir $dir
+ echo "creating $file"
+ cp -a ../exhibition/$file $file
+ cd $srcDir
+ done
+ cd $projDir
+}
+function CopyAndReplace(){
+ local projDir=$(pwd)
+ cd ../exhibition
+ local srcDir=$(pwd)
+ for file in lib/setting/*.dart \
+ pubspec.yaml \
+ lib/persistence/*.dart \
+ lib/page/*.dart \
+ rest_server/lib/*.dart rest_server/bin/*.dart rest_server/tools/project.inc \
+ rest_server/CR \
+ lib/main.dart \
+ lib/meta/*_meta.dart \
+ bin/*.dart \
+ ; do
+ cd $projDir
+ local dir=$(dirname $file)
+ EnsureDir $dir
+ local file2=${file/exhibition/$APP}
+ echo "copy and replace $file -> $(basename $file2)"
+ sed <../exhibition/$file >$file2 -e "s/exhibition/$APP/g" -e "s/Exhibition/$APP2/g"
+ chmod --reference=../exhibition/$file $file2
+ cd $srcDir
+ done
+ cd $projDir
+}
+function SymbolicLinks(){
+ for links in ReCreateMetaTool:. Meta:. ; do
+ local src=../exhibition/${links%:*}
+ local trg=${links#*:}
+ local trg2=$trg
+ test $trg = . && trg2=$(basename $src)
+ echo "trg: $trg2"
+ if [ ! -L $trg2 ]; then
+ echo "linking $src -> $trg2"
+ ln -s $src $trg2
+ fi
+ done
+}
+function PrepareTools(){
+ test -e tools/yaml_merger || tools/CompileMerger
+}
+if [ -z "$APP" ]; then
+ Usage "missing PROJECT"
+elif [ ! -d ../$APP ]; then
+ Usage "wrong current directory: Please go into the base folder of the project."
+else
+ PrepareTools
+ MkDirs
+ CopyFiles
+ CopyAndReplace
+ SymbolicLinks
+fi
--- /dev/null
+#! /bin/bash
+BASE_DIR=/tmp/rest_server
+SCRIPT_INSTALL=/tmp/InstallRestServer
+TAR_NODE=rest_server.tgz
+TAR=/tmp/$TAR_NODE
+source rest_server/tools/project.inc
+
+function DoIt(){
+ test -d $BASE_DIR && rm -Rf $BASE_DIR
+ mkdir -p $BASE_DIR/data/sql
+ cd rest_server
+ ./CR
+ test -d data/sql/precedence || mkdir -p data/sql/precedence
+ cd ..
+ cp -av rest_server/tools/rest_server $BASE_DIR
+ tools/CompileMerge
+ tools/yaml_merge data/sql data/sql/precedence $BASE_DIR/data/sql
+ cat <<EOS >$BASE_DIR/INSTALL.TXT
+# ------------
+# Installation:
+./InstallRestServer
+# or manually:
+DIR=\$(pwd)
+mkdir -p /usr/share/$PROJECT && cd /usr/share/$PROJECT
+tar xzf \DIR/$TAR_NODE
+# ./rest_server install <executable> <service-name>
+./rest_server install /usr/share/$PROJECT/rest_server $PROJECT
+# see /usr/share/$PROJECT and /etc/$PROJECT
+EOS
+ tar -C $BASE_DIR -czf $TAR .
+ cat <<EOS >$SCRIPT_INSTALL
+#! /bin/bash
+DIR=\$(pwd)
+mkdir -p /usr/share/$PROJECT && cd /usr/share/$PROJECT
+tar xzf \$DIR/rest_server.tgz
+./rest_server install /usr/share/$PROJECT/rest_server $PROJECT
+EOS
+ chmod uog+x $SCRIPT_INSTALL
+ echo "================"
+ echo "= put $TAR and $SCRIPT_INSTALL to the backend server and..."
+ cat $BASE_DIR/INSTALL.TXT
+}
+DoIt