diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..463eba8 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,126 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "wyatt_clean_code", + "cwd": "apps/wyatt_clean_code", + "request": "launch", + "type": "dart" + }, + { + "name": "wyatt_clean_code (profile mode)", + "cwd": "apps/wyatt_clean_code", + "request": "launch", + "type": "dart", + "flutterMode": "profile" + }, + { + "name": "wyatt_clean_code (release mode)", + "cwd": "apps/wyatt_clean_code", + "request": "launch", + "type": "dart", + "flutterMode": "release" + }, + { + "name": "brick_generator", + "cwd": "tools/brick_generator", + "request": "launch", + "type": "dart" + }, + { + "name": "__brick__", + "cwd": "bricks/core_app_brick/__brick__", + "request": "launch", + "type": "dart" + }, + { + "name": "__brick__ (profile mode)", + "cwd": "bricks/core_app_brick/__brick__", + "request": "launch", + "type": "dart", + "flutterMode": "profile" + }, + { + "name": "__brick__ (release mode)", + "cwd": "bricks/core_app_brick/__brick__", + "request": "launch", + "type": "dart", + "flutterMode": "release" + }, + { + "name": "__brick__", + "cwd": "bricks/wyatt_clean_code/__brick__", + "request": "launch", + "type": "dart" + }, + { + "name": "__brick__ (profile mode)", + "cwd": "bricks/wyatt_clean_code/__brick__", + "request": "launch", + "type": "dart", + "flutterMode": "profile" + }, + { + "name": "__brick__ (release mode)", + "cwd": "bricks/wyatt_clean_code/__brick__", + "request": "launch", + "type": "dart", + "flutterMode": "release" + }, + { + "name": "hooks", + "cwd": "bricks/wyatt_clean_code/hooks", + "request": "launch", + "type": "dart" + }, + { + "name": "__brick__", + "cwd": "bricks/wyatt_package/__brick__", + "request": "launch", + "type": "dart" + }, + { + "name": "__brick__ (profile mode)", + "cwd": "bricks/wyatt_package/__brick__", + "request": "launch", + "type": "dart", + "flutterMode": "profile" + }, + { + "name": "__brick__ (release mode)", + "cwd": "bricks/wyatt_package/__brick__", + "request": "launch", + "type": "dart", + "flutterMode": "release" + }, + { + "name": "hooks", + "cwd": "bricks/wyatt_package/hooks", + "request": "launch", + "type": "dart" + }, + { + "name": "example", + "cwd": "bricks/wyatt_package/__brick__/example", + "request": "launch", + "type": "dart" + }, + { + "name": "example (profile mode)", + "cwd": "bricks/wyatt_package/__brick__/example", + "request": "launch", + "type": "dart", + "flutterMode": "profile" + }, + { + "name": "example (release mode)", + "cwd": "bricks/wyatt_package/__brick__/example", + "request": "launch", + "type": "dart", + "flutterMode": "release" + } + ] +} \ No newline at end of file diff --git a/tools/brick_generator/README.md b/tools/brick_generator/README.md index cc5567f..885baa8 100644 --- a/tools/brick_generator/README.md +++ b/tools/brick_generator/README.md @@ -1,6 +1,57 @@ -A sample command-line application with an entrypoint in `bin/`, library code +A sample command-line application to generate which allows to generate the template of a brick from a project which compiles. with an entrypoint in `bin/`, library code in `lib/`. +# How to use + +- Add your app in `apps`. +- Add `brick_config.yaml` in you app folder and add this fields : + +```yaml +brick_name: wyatt_feature_brick + +path_to_brickify: lib/feature_brick_folder + +variables: + feature_brick: + variable_name: feature_brick + type: string + syntax: + camel_case: featureBrick + constant_case: FEATURE_BRICK + dot_case: feature.brick + header_case: Feature-Brick + lower_case: feature brick + pascal_case: FeatureBrick + param_case: feature-brick + sentence_case: Feature brick + snake_case: feature_brick + title_case: Feature Brick + upper_case: FEATURE BRICK + description: + variable_name: description + type: string + syntax: + camel_case: description + constant_case: DESCRIPTION + dot_case: description + header_case: Description + lower_case: description + pascal_case: Description + param_case: description + sentence_case: Description + snake_case: description + title_case: Description + upper_case: DESCRIPTION + isBloc: + variable_name: bloc + type: bool +``` + +then run command with project path + ```sh -dart /bin/brick_generator.dart wyatt_clean_code wyatt_clean_code wyatt-clean-code "Wyatt Demo" app.wyatt.io -``` \ No newline at end of file +dart tools/brick_generator/bin/brick_generator.dart ./apps/wyatt_feature_brick +``` + +# TODO +- Work on bool variables \ No newline at end of file diff --git a/tools/brick_generator/analysis_options.yaml b/tools/brick_generator/analysis_options.yaml index dee8927..e1caf22 100644 --- a/tools/brick_generator/analysis_options.yaml +++ b/tools/brick_generator/analysis_options.yaml @@ -1,30 +1 @@ -# This file configures the static analysis results for your project (errors, -# warnings, and lints). -# -# This enables the 'recommended' set of lints from `package:lints`. -# This set helps identify many issues that may lead to problems when running -# or consuming Dart code, and enforces writing Dart using a single, idiomatic -# style and format. -# -# If you want a smaller set of lints you can change this to specify -# 'package:lints/core.yaml'. These are just the most critical lints -# (the recommended set includes the core lints). -# The core lints are also what is used by pub.dev for scoring packages. - -include: package:lints/recommended.yaml - -# Uncomment the following section to specify additional rules. - -# linter: -# rules: -# - camel_case_types - -# analyzer: -# exclude: -# - path/to/excluded/files/** - -# For more information about the core and recommended set of lints, see -# https://dart.dev/go/core-lints - -# For additional information about configuring this file, see -# https://dart.dev/guides/language/analysis-options +include: package:wyatt_analysis/analysis_options.yaml diff --git a/tools/brick_generator/bin/brick_generator.dart b/tools/brick_generator/bin/brick_generator.dart index 058d5fc..08a5530 100644 --- a/tools/brick_generator/bin/brick_generator.dart +++ b/tools/brick_generator/bin/brick_generator.dart @@ -1,102 +1,133 @@ import 'dart:io'; +import 'package:brick_generator/models/brick_config.dart'; +import 'package:brick_generator/models/variable_string_syntax.dart'; +import 'package:brick_generator/models/variable_type.dart'; import 'package:brick_generator/shell.dart'; +import 'package:brick_generator/yaml_reader.dart'; import 'package:path/path.dart' as path; // Constants -final _apps = 'apps'; -final _bricks = 'bricks'; -final _staticDir = path.join('tools', 'brick_generator', 'assets'); +const _bricks = 'bricks'; +const _brickFolderLabel = '__brick__'; +const _yamlFileName = 'brick_config.yaml'; Future main(List arguments) async { - final _appName = arguments[0]; + try { + if (arguments.length != 1) { + throw ArgumentError('Please entry exemple project path'); + } - final _projectSnakeName = arguments[1]; - final _projectParamName = arguments[2]; - final _projectTitleName = arguments[3]; - final _orgName = arguments[4]; + final projectPath = arguments[0]; - final _sourcePath = path.join(_apps, _appName); - final _targetPath = path.join(_bricks, _appName, '__brick__'); + // Store options from yaml file + final configs = + YamlReader.readYamlFile(path.join(projectPath, _yamlFileName)); + final brickConfig = BrickConfig.from(configs); + stdout.writeln('🍺 config retrieved'); - final _androidPath = path.join(_targetPath, 'android'); - final _androidKotlinPath = - path.join(_androidPath, 'app', 'src', 'main', 'kotlin'); - final _orgPath = path.join(_androidKotlinPath, 'com'); + brickConfig?.checkFormat(); - // Remove Previously Generated Files - final targetDir = Directory(_targetPath); - if (targetDir.existsSync()) { - await targetDir.delete(recursive: true); + // Define paths + final sourcePath = path.join( + projectPath, + brickConfig!.pathToBrickify, + ); + final targetPath = path.join( + _bricks, + brickConfig.brickName, + _brickFolderLabel, + ); + stdout.writeln('🍺 path defined'); + + // Remove previously generated files + final targetDir = Directory(targetPath); + if (targetDir.existsSync()) { + await targetDir.delete(recursive: true); + } + + // create target folder + await Shell.mkdir(targetPath); + + // Copy project files + await Shell.cp(sourcePath, targetPath); + stdout.writeln('🍺 files copied'); + + // Convert values to variables + await Future.wait( + Directory(targetPath) + .listSync(recursive: true) + .whereType() + .map((_) async { + var file = _; + + var contents = await file.readAsString(); + + // Transform all values in variables + if (brickConfig.variables != null) { + for (final variable in brickConfig.variables!) { + if (variable?.type == VariabelType.string) { + for (final syntax in VariableStringSyntax.values) { + final toReplace = variable?.syntax?[syntax.mapKey] as String?; + if (toReplace != null) { + contents = contents.replaceAll( + toReplace, + '{{#${syntax.id}}}{{${variable?.name}}}{{/${syntax.id}}}', + ); + } + } + } + } + } + + // Replace content + file = await file.writeAsString(contents); + stdout.writeln('🍺 variables added'); + + // Rename file if needed + final filePath = file.path; + + if (brickConfig.variables != null) { + for (final variable in brickConfig.variables!) { + if (variable?.type == VariabelType.string && + variable?.syntax?[VariableStringSyntax.snakeCase.mapKey] != + null) { + if (filePath.contains( + variable!.syntax![VariableStringSyntax.snakeCase.mapKey] + as String, + )) { + var pathList = + filePath.split(Platform.pathSeparator).sublist(3); + pathList = pathList + .map( + (segment) => segment.replaceAll( + variable.syntax![VariableStringSyntax.snakeCase.mapKey] + as String, + '{{${variable.name}.snakeCase()}}', + ), + ) + .toList(); + + final newPath = path.join( + filePath + .split(Platform.pathSeparator) + .sublist(0, 3) + .join(Platform.pathSeparator), + pathList.join(Platform.pathSeparator), + ); + + File(newPath).createSync(recursive: true); + file.renameSync(newPath); + Directory(file.parent.path).deleteSync(recursive: true); + } + } + } + } + stdout.writeln('🍺 folders and files renamed'); + }), + ); + stdout.writeln('🍺 done'); + } catch (_) { + stdout.writeln('❌ ${_.toString()}'); } - - // Copy Project Files - await Shell.cp(_sourcePath, _targetPath); - - // Delete Android's Organization Folder Hierarchy - Directory(_orgPath).deleteSync(recursive: true); - - // Convert Values to Variables - await Future.wait( - Directory(_targetPath) - .listSync(recursive: true) - .whereType() - .map((_) async { - var file = _; - - try { - - if (path.extension(file.path) == '.dart') { - final contents = await file.readAsString(); - file = await file.writeAsString(contents); - } - - final contents = await file.readAsString(); - file = await file.writeAsString( - contents - .replaceAll( - _projectSnakeName, - '{{#snakeCase}}{{project_name}}{{/snakeCase}}', - ) - .replaceAll( - _projectParamName, - '{{#paramCase}}{{project_name}}{{/paramCase}}', - ) - .replaceAll('A new Flutter project.', '{{{description}}}') - .replaceAll( - _projectTitleName, - '{{#titleCase}}{{project_name}}{{/titleCase}}', - ) - .replaceAll( - _orgName, - path.isWithin(_androidPath, file.path) - ? '{{#dotCase}}{{org_name}}{{/dotCase}}.{{#snakeCase}}{{project_name}}{{/snakeCase}}' - : '{{#dotCase}}{{org_name}}{{/dotCase}}.{{#paramCase}}{{project_name}}{{/paramCase}}', - ), - ); - final fileSegments = file.path.split('/').sublist(2); - if (fileSegments.contains(_projectSnakeName)) { - final newPathSegment = fileSegments.join('/').replaceAll( - _projectSnakeName, - '{{#snakeCase}}{{project_name}}{{/snakeCase}}', - ); - final newPath = path.join(_targetPath, newPathSegment); - File(newPath).createSync(recursive: true); - file.renameSync(newPath); - Directory(file.parent.path).deleteSync(recursive: true); - } - } catch (_) {} - }), - ); - - final mainActivityKt = File( - path.join( - _androidKotlinPath, - '{{#pathCase}}{{org_name}}{{/pathCase}}', - 'MainActivity.kt', - ), - ); - - await Shell.mkdir(mainActivityKt.parent.path); - await Shell.cp(path.join(_staticDir, 'MainActivity.kt'), mainActivityKt.path); } diff --git a/tools/brick_generator/lib/models/brick_config.dart b/tools/brick_generator/lib/models/brick_config.dart new file mode 100644 index 0000000..e1ce248 --- /dev/null +++ b/tools/brick_generator/lib/models/brick_config.dart @@ -0,0 +1,51 @@ +import 'package:brick_generator/models/brick_variable.dart'; +import 'package:yaml/yaml.dart'; + +const _brickNameKey = 'brick_name'; +const _pathToBrickifyKey = 'path_to_brickify'; +const _variablesKey = 'variables'; + +class BrickConfig { + String? brickName; + String? pathToBrickify; + List? variables; + + BrickConfig({ + required this.brickName, + required this.pathToBrickify, + required this.variables, + }); + + BrickConfig._(); + factory BrickConfig.parser() => BrickConfig._(); + + static BrickConfig? from(YamlMap? data) => data != null + ? BrickConfig( + brickName: data[_brickNameKey] as String?, + pathToBrickify: data[_pathToBrickifyKey] as String?, + variables: (data[_variablesKey] as YamlMap?) + ?.map( + (dynamic key, dynamic value) => + MapEntry( + key, + BrickVariable.from(value as YamlMap?), + ), + ) + .values + .toList(), + ) + : null; + + void checkFormat() { + if (brickName == null || pathToBrickify == null) { + throw ArgumentError( + 'Yaml file is not conform', + ); + } + if (variables != null) { + for (final variable in variables!) { + variable?.checkFormat(); + } + } + } +} diff --git a/tools/brick_generator/lib/models/brick_variable.dart b/tools/brick_generator/lib/models/brick_variable.dart new file mode 100644 index 0000000..a34e704 --- /dev/null +++ b/tools/brick_generator/lib/models/brick_variable.dart @@ -0,0 +1,38 @@ +import 'package:brick_generator/models/variable_type.dart'; +import 'package:yaml/yaml.dart'; + +const _variableNameKey = 'variable_name'; +const _typeKey = 'type'; +const _syntaxKey = 'syntax'; + +class BrickVariable { + String? name; + VariabelType? type; + YamlMap? syntax; + + BrickVariable({ + required this.name, + required this.type, + required this.syntax, + }); + + BrickVariable._(); + + factory BrickVariable.parser() => BrickVariable._(); + + static BrickVariable? from(YamlMap? data) => data != null + ? BrickVariable( + name: data[_variableNameKey] as String?, + type: VariabelType.stringToEnum(data[_typeKey] as String?), + syntax: data[_syntaxKey] as YamlMap?, + ) + : null; + + void checkFormat() { + if (name == null || type == null) { + throw ArgumentError( + 'Yaml file is not conform', + ); + } + } +} diff --git a/tools/brick_generator/lib/models/variable_string_syntax.dart b/tools/brick_generator/lib/models/variable_string_syntax.dart new file mode 100644 index 0000000..6b99cfb --- /dev/null +++ b/tools/brick_generator/lib/models/variable_string_syntax.dart @@ -0,0 +1,18 @@ +enum VariableStringSyntax { + camelCase('camel_case', 'camelCase'), + constantCase('constant_case', 'constantCase'), + dotCase('dot_case', 'dotCase'), + headerCase('header_case', 'headerCase'), + lowerCase('lower_case', 'lowerCase'), + pascalCase('pascal_case', 'pascalCase'), + paramCase('param_case', 'paramCase'), + sentenceCase('sentence_case', 'sentenceCase'), + snakeCase('snake_case', 'snakeCase'), + titleCase('title_case', 'titleCase'), + upperCase('upper_case', 'upperCase'); + + final String mapKey; + final String id; + + const VariableStringSyntax(this.mapKey, this.id); +} diff --git a/tools/brick_generator/lib/models/variable_type.dart b/tools/brick_generator/lib/models/variable_type.dart new file mode 100644 index 0000000..db6aef6 --- /dev/null +++ b/tools/brick_generator/lib/models/variable_type.dart @@ -0,0 +1,16 @@ +enum VariabelType { + none, + string, + bool; + + static VariabelType stringToEnum(String? type) { + switch (type) { + case 'string': + return string; + case 'bool': + return bool; + default: + return none; + } + } +} diff --git a/tools/brick_generator/lib/shell.dart b/tools/brick_generator/lib/shell.dart index 62b5881..e16b30f 100644 --- a/tools/brick_generator/lib/shell.dart +++ b/tools/brick_generator/lib/shell.dart @@ -17,8 +17,12 @@ class _Cmd { bool throwOnError = true, String? processWorkingDir, }) async { - final result = await Process.run(cmd, args, - workingDirectory: processWorkingDir, runInShell: true); + final result = await Process.run( + cmd, + args, + workingDirectory: processWorkingDir, + runInShell: true, + ); if (throwOnError) { _throwIfProcessFailed(result, cmd, args); @@ -49,4 +53,4 @@ class _Cmd { throw ProcessException(process, args, message, pr.exitCode); } } -} \ No newline at end of file +} diff --git a/tools/brick_generator/lib/yaml_reader.dart b/tools/brick_generator/lib/yaml_reader.dart new file mode 100644 index 0000000..a7bdf15 --- /dev/null +++ b/tools/brick_generator/lib/yaml_reader.dart @@ -0,0 +1,7 @@ +import 'dart:io'; +import 'package:yaml/yaml.dart'; + +class YamlReader { + static YamlMap readYamlFile(String path) => + loadYaml(File(path).readAsStringSync()) as YamlMap; +} diff --git a/tools/brick_generator/pubspec.yaml b/tools/brick_generator/pubspec.yaml index 65468d7..3ecdbc3 100644 --- a/tools/brick_generator/pubspec.yaml +++ b/tools/brick_generator/pubspec.yaml @@ -5,7 +5,15 @@ version: 1.0.0 publish_to: none environment: - sdk: '>=2.17.0 <3.0.0' + sdk: ">=2.17.0 <3.0.0" dependencies: path: ^1.8.2 + yaml: ^3.1.1 + +dev_dependencies: + wyatt_analysis: + git: + url: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages + ref: wyatt_analysis-v2.1.0 + path: packages/wyatt_analysis