diff --git a/tools/brick_generator/.gitignore b/tools/brick_generator/.gitignore new file mode 100644 index 0000000..3c8a157 --- /dev/null +++ b/tools/brick_generator/.gitignore @@ -0,0 +1,6 @@ +# Files and directories created by pub. +.dart_tool/ +.packages + +# Conventional directory for build output. +build/ diff --git a/tools/brick_generator/CHANGELOG.md b/tools/brick_generator/CHANGELOG.md new file mode 100644 index 0000000..effe43c --- /dev/null +++ b/tools/brick_generator/CHANGELOG.md @@ -0,0 +1,3 @@ +## 1.0.0 + +- Initial version. diff --git a/tools/brick_generator/README.md b/tools/brick_generator/README.md new file mode 100644 index 0000000..cc5567f --- /dev/null +++ b/tools/brick_generator/README.md @@ -0,0 +1,6 @@ +A sample command-line application with an entrypoint in `bin/`, library code +in `lib/`. + +```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 diff --git a/tools/brick_generator/analysis_options.yaml b/tools/brick_generator/analysis_options.yaml new file mode 100644 index 0000000..dee8927 --- /dev/null +++ b/tools/brick_generator/analysis_options.yaml @@ -0,0 +1,30 @@ +# 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 diff --git a/tools/brick_generator/assets/MainActivity.kt b/tools/brick_generator/assets/MainActivity.kt new file mode 100644 index 0000000..eb6d0bb --- /dev/null +++ b/tools/brick_generator/assets/MainActivity.kt @@ -0,0 +1,6 @@ +package {{#dotCase}}{{org_name}}{{/dotCase}}.{{#snakeCase}}{{project_name}}{{/snakeCase}} + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} \ No newline at end of file diff --git a/tools/brick_generator/bin/brick_generator.dart b/tools/brick_generator/bin/brick_generator.dart new file mode 100644 index 0000000..058d5fc --- /dev/null +++ b/tools/brick_generator/bin/brick_generator.dart @@ -0,0 +1,102 @@ +import 'dart:io'; + +import 'package:brick_generator/shell.dart'; +import 'package:path/path.dart' as path; + +// Constants +final _apps = 'apps'; +final _bricks = 'bricks'; +final _staticDir = path.join('tools', 'brick_generator', 'assets'); + +Future main(List arguments) async { + final _appName = arguments[0]; + + final _projectSnakeName = arguments[1]; + final _projectParamName = arguments[2]; + final _projectTitleName = arguments[3]; + final _orgName = arguments[4]; + + final _sourcePath = path.join(_apps, _appName); + final _targetPath = path.join(_bricks, _appName, '__brick__'); + + final _androidPath = path.join(_targetPath, 'android'); + final _androidKotlinPath = + path.join(_androidPath, 'app', 'src', 'main', 'kotlin'); + final _orgPath = path.join(_androidKotlinPath, 'com'); + + // Remove Previously Generated Files + final targetDir = Directory(_targetPath); + if (targetDir.existsSync()) { + await targetDir.delete(recursive: true); + } + + // 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/shell.dart b/tools/brick_generator/lib/shell.dart new file mode 100644 index 0000000..62b5881 --- /dev/null +++ b/tools/brick_generator/lib/shell.dart @@ -0,0 +1,52 @@ +import 'dart:io'; + +class Shell { + static Future cp(String source, String destination) { + return _Cmd.run('cp', ['-Rf', source, destination]); + } + + static Future mkdir(String destination) { + return _Cmd.run('mkdir', ['-p', destination]); + } +} + +class _Cmd { + static Future run( + String cmd, + List args, { + bool throwOnError = true, + String? processWorkingDir, + }) async { + final result = await Process.run(cmd, args, + workingDirectory: processWorkingDir, runInShell: true); + + if (throwOnError) { + _throwIfProcessFailed(result, cmd, args); + } + return result; + } + + static void _throwIfProcessFailed( + ProcessResult pr, + String process, + List args, + ) { + if (pr.exitCode != 0) { + final values = { + 'Standard out': pr.stdout.toString().trim(), + 'Standard error': pr.stderr.toString().trim() + }..removeWhere((k, v) => v.isEmpty); + + String message; + if (values.isEmpty) { + message = 'Unknown error'; + } else if (values.length == 1) { + message = values.values.single; + } else { + message = values.entries.map((e) => '${e.key}\n${e.value}').join('\n'); + } + + throw ProcessException(process, args, message, pr.exitCode); + } + } +} \ No newline at end of file diff --git a/tools/brick_generator/pubspec.yaml b/tools/brick_generator/pubspec.yaml new file mode 100644 index 0000000..65468d7 --- /dev/null +++ b/tools/brick_generator/pubspec.yaml @@ -0,0 +1,11 @@ +name: brick_generator +description: A template generator from real app. +version: 1.0.0 + +publish_to: none + +environment: + sdk: '>=2.17.0 <3.0.0' + +dependencies: + path: ^1.8.2