feat: rewrite generator

This commit is contained in:
Hugo Pointcheval 2023-01-26 16:29:16 +01:00
parent 1646dbd919
commit 18aeef9e35
Signed by: hugo
GPG Key ID: 3AAC487E131E00BC
25 changed files with 1376 additions and 145 deletions

View File

@ -0,0 +1,30 @@
name: <brick_name>
description: <brick_description>
version: 0.1.0
vars:
display_name:
type: string
description: The display name
default: Display Name
prompt: What is the display name?
project_name:
type: string
description: The project name
default: wyatt_app
prompt: What is the project name?
bundle_id:
type: string
description: The bundle id used in Android and iOS
default: io.wyattapp.new
prompt: What is the bundle id?
flutter:
type: boolean
description: If this app is a Flutter or Dart project.
default: false
prompt: Is it Flutter app ?

View File

@ -1,9 +1,9 @@
import 'dart:io';
import 'package:brick_generator/core/logger.dart';
import 'package:brick_generator/core/shell.dart';
import 'package:brick_generator/file_system_utils.dart';
import 'package:brick_generator/logger.dart';
import 'package:brick_generator/models/brick_config.dart';
import 'package:brick_generator/shell.dart';
import 'package:brick_generator/yaml_reader.dart';
import 'package:path/path.dart' as path;

View File

@ -0,0 +1,182 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
// Copyright (C) 2023 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'dart:io';
import 'package:args/args.dart';
import 'package:brick_generator/core/file_system.dart';
import 'package:brick_generator/core/logger.dart';
import 'package:brick_generator/models/brick_config.dart';
import 'package:path/path.dart';
const _configurationFileName = 'brickgen.yaml';
const _masonConfigurationFileName = 'brick.yaml';
const _brickFolderName = '__brick__';
const _helpOption = 'help';
const _verboseOption = 'verbose';
const _removeEmptyOption = 'remove-empty';
class Brickgen {
final String brickPath;
final String outputPath;
final bool deleteEmptyFolders;
late String configPath;
String? toBrickifyPath;
String? masonConfigPath;
String? targetPath;
Brickgen({
required this.brickPath,
required this.outputPath,
required this.deleteEmptyFolders,
}) {
configPath = join(brickPath, _configurationFileName);
}
Future<void> run() async {
// Load config
final config = BrickConfig.fromFile(configPath);
Logger.debug('Read config: \n${config.toMason()}');
Logger.info('Config retrieved.');
// Define paths
toBrickifyPath = FileSystem.joinExists(
brickPath,
config.brickgenConfig.pathToBrickify,
);
Logger.debug('Define `toBrickifyPath`: $toBrickifyPath');
masonConfigPath = FileSystem.joinTouch(
outputPath,
config.name,
_masonConfigurationFileName,
);
Logger.debug('Define `masonConfigPath`: $masonConfigPath');
targetPath = FileSystem.joinRecreate(
outputPath,
config.name,
_brickFolderName,
);
Logger.debug('Define `targetPath`: $targetPath');
// Check paths
if (toBrickifyPath == null || targetPath == null) {
throw Exception('An error occures during path definition.');
}
Logger.info('Paths defined.');
// Copy (and exclude from copy) project files
await FileSystem.copyFolder(
toBrickifyPath!,
targetPath!,
ignoreList: config.brickgenConfig.ignore,
);
Logger.info('Project copied.');
// Convert compilable values -> variables (in files)
FileSystem.convertCompilableVariablesInFolder(config, targetPath!);
Logger.info('Files content converted with variables.');
// Create `.gitkeep` in empty folders (to keep them in brick generation)
// Or remove them (depending of --remove option)
if (deleteEmptyFolders) {
FileSystem.removeEmptyFolders(targetPath!);
Logger.info('Empty folders removed.');
} else {
FileSystem.createGitKeepInEmptyFolders(targetPath!);
Logger.info('Empty folders marked as to keep.');
}
// Convert compilable values -> variables (files/folder names)
FileSystem.renameCompilableFilesInFolder(config, targetPath!);
Logger.info('Files renamed with variables.');
// Take care of boolean mapping and boolean fs
// Create Mason config
FileSystem.writeFile(masonConfigPath!, config.toMason());
Logger.info('Mason `brick.yaml` generated.');
// Create gitkeep hook
// Copy custom hooks
// Success!
}
}
Future<void> main(List<String> args) async {
int exitCode = 0;
final parser = ArgParser()
..addFlag(
_helpOption,
negatable: false,
abbr: 'h',
help: 'Show this help dialog',
)
..addFlag(
_verboseOption,
negatable: false,
abbr: 'v',
help: 'Show additional diagnostic info',
)
..addFlag(
_removeEmptyOption,
negatable: false,
abbr: 'r',
help: 'Remove empty folders (if not it creates '
'.gitkeep in each empty folders)',
);
final argResults = parser.parse(args);
if (argResults[_helpOption] as bool? ?? false) {
Logger.info('Brickgen\n${parser.usage}');
exit(0);
}
if (argResults[_verboseOption] as bool? ?? false) {
Logger.setVerbose();
}
final paths = argResults.rest;
if (paths.length < 2) {
Logger.error('Please provide input and output paths.');
if (!(argResults[_helpOption] as bool? ?? false)) {
Logger.info('Brickgen\n${parser.usage}');
}
exitCode = 1;
exit(exitCode);
}
final brickPath = paths[0];
final outputPath = paths[1];
await Brickgen(
brickPath: brickPath,
outputPath: outputPath,
deleteEmptyFolders: argResults[_removeEmptyOption] as bool? ?? false,
).run();
exit(exitCode);
}

View File

@ -0,0 +1,357 @@
// Copyright (C) 2023 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'dart:io';
import 'package:brick_generator/core/logger.dart';
import 'package:brick_generator/core/shell.dart';
import 'package:brick_generator/models/brick_config.dart';
import 'package:brick_generator/models/brick_variable_string.dart';
import 'package:brick_generator/models/ignore_list.dart';
import 'package:brick_generator/models/variable_string_syntax.dart';
import 'package:path/path.dart';
abstract class FileSystem {
static void writeFile(String path, Object? content, {bool binary = false}) {
final file = File(join(path))..createSync(recursive: true);
if (content == null) {
// Just create an empty file
return;
}
try {
if (binary) {
file.writeAsBytesSync(content as List<int>);
} else {
file.writeAsStringSync(content as String);
}
} catch (e) {
Logger.error(e);
}
}
static Future<void> copyFolder(
String from,
String to, {
IgnoreList? ignoreList,
}) async {
if (!FileSystemEntity.isDirectorySync(from)) {
throw ArgumentError('Path must be a directory', 'from');
}
final List<String> copiedFolders = [];
// Copy folders
await Future.wait<void>(
Directory(from)
.listSync(recursive: true)
.whereType<Directory>()
.map((directory) async {
final absolutPath = directory.path;
final name = absolutPath.split('/').last;
if (ignoreList != null && ignoreList.contains(from, absolutPath)) {
Logger.debug('Ignoring $absolutPath');
} else {
Logger.debug('cp -Rf $absolutPath $to/$name');
copiedFolders.add(absolutPath);
await Shell.cp(absolutPath, '$to/$name');
}
}),
);
// Copy files
await Future.wait<void>(
Directory(from)
.listSync(recursive: true)
.whereType<File>()
.map((file) async {
final absolutPath = file.path;
final name = absolutPath.split('/').last;
if (ignoreList != null && ignoreList.contains(from, absolutPath)) {
Logger.debug('Ignoring $absolutPath');
} else {
if (copiedFolders.any(absolutPath.startsWith)) {
Logger.debug('$absolutPath already copied!');
} else {
Logger.debug('cp -Rf $absolutPath $to/$name');
await Shell.cp(absolutPath, '$to/$name');
}
}
}),
);
}
static void makeDirectory(String path) {
final dir = Directory(path);
if (dir.existsSync()) {
dir.deleteSync(recursive: true);
}
dir.createSync(recursive: true);
}
/// Join and verify if the joined path exists.
static String joinExists(
String part1, [
String? part2,
String? part3,
String? part4,
String? part5,
String? part6,
String? part7,
String? part8,
]) {
final path = join(part1, part2, part3, part4, part5, part6, part7, part8);
if (FileSystemEntity.isDirectorySync(path)) {
Logger.debug('$path is a directory, and it exists.');
return path;
}
if (FileSystemEntity.isFileSync(path)) {
Logger.debug('$path is a file, and it exists.');
return path;
}
throw ArgumentError('Joined path is not existing.');
}
/// Join and verify if the joined path exists. If not, create it.
static String joinCreate(
String part1, [
String? part2,
String? part3,
String? part4,
String? part5,
String? part6,
String? part7,
String? part8,
]) {
final path = join(part1, part2, part3, part4, part5, part6, part7, part8);
if (FileSystemEntity.isDirectorySync(path)) {
Logger.debug('$path is a directory, and it exists.');
return path;
}
if (FileSystemEntity.isFileSync(path)) {
Logger.debug('$path is a file, and it exists.');
return path;
}
makeDirectory(path);
Logger.debug('$path directory was created.');
return path;
}
/// Join and verify if the joined path exists. If yes, recreate it.
static String joinRecreate(
String part1, [
String? part2,
String? part3,
String? part4,
String? part5,
String? part6,
String? part7,
String? part8,
]) {
final path = join(part1, part2, part3, part4, part5, part6, part7, part8);
if (FileSystemEntity.isDirectorySync(path)) {
makeDirectory(path);
Logger.debug('$path directory was recreated.');
return path;
}
if (FileSystemEntity.isFileSync(path)) {
Logger.debug('$path is a file ! No recreate it!');
return path;
}
makeDirectory(path);
Logger.debug('$path directory was created.');
return path;
}
/// Join and verify if the joined path exists. If yes, touch it. If not
/// create an empty file.
static String joinTouch(
String part1, [
String? part2,
String? part3,
String? part4,
String? part5,
String? part6,
String? part7,
String? part8,
]) {
final path = join(part1, part2, part3, part4, part5, part6, part7, part8);
if (FileSystemEntity.isDirectorySync(path)) {
Logger.debug('$path is a directory ! No recreate it!');
return path;
}
if (FileSystemEntity.isFileSync(path)) {
writeFile(path, null);
Logger.debug('$path files was touched!');
return path;
}
writeFile(path, null);
Logger.debug('$path file was created.');
return path;
}
/// Transforms compilable variables:
/// For each String `vars` in config, search for compilable in code, then
/// convert it to his mustache format.
///
/// Example:
/// ```yaml
/// bundle_id:
/// compilable: io.wyattapp.new
/// type: string
/// description: The bundle id used in Android and iOS
/// default: io.wyattapp.new
/// prompt: "What is the bundle id?"
/// ```
///
/// Search for `io.wyattapp.new` then converts
/// it to `{{#dotCase}}{{bundle_id}}{{/dotCase}}`
static void convertCompilableVariablesInFolder(
BrickConfig config,
String targetPath,
) {
if (!FileSystemEntity.isDirectorySync(targetPath)) {
throw ArgumentError('Target must be a directory', 'targetPath');
}
Directory(targetPath)
.listSync(recursive: true)
.whereType<File>()
.forEach((file) {
String? contents;
try {
contents = file.readAsStringSync();
} catch (e) {
Logger.error(e);
}
config.variables.whereType<BrickVariableString>().forEach((variable) {
// Replace all string variables
for (final syntax in VariableStringSyntax.values) {
final toReplace = variable.syntax?[syntax.mapKey];
if (toReplace != null) {
contents = contents!.replaceAll(
toReplace,
'{{#${syntax.id}}}{{${variable.name}}}{{/${syntax.id}}}',
);
}
}
Logger.debug('Variable `${variable.name}` replaced in ${file.path}');
});
file.writeAsStringSync(contents!);
});
}
static void createGitKeepInEmptyFolders(String targetPath) {
if (!FileSystemEntity.isDirectorySync(targetPath)) {
throw ArgumentError('Target must be a directory', 'targetPath');
}
Directory(targetPath)
.listSync(recursive: true)
.whereType<Directory>()
.forEach((directory) {
if (directory.existsSync() &&
directory.listSync(recursive: true).isEmpty) {
joinTouch(directory.path, '.gitkeep');
Logger.debug('${directory.path} (empty) marked as to keep.');
}
});
}
static void removeEmptyFolders(String targetPath) {
if (!FileSystemEntity.isDirectorySync(targetPath)) {
throw ArgumentError('Target must be a directory', 'targetPath');
}
Directory(targetPath)
.listSync(recursive: true)
.whereType<Directory>()
.forEach((directory) {
if (directory.existsSync() &&
directory.listSync(recursive: true).isEmpty) {
directory.deleteSync(recursive: true);
Logger.debug('${directory.path} (empty) deleted.');
}
});
}
/// Transforms compilable file/folder names:
/// For each String `vars` in config, search for compilable in code, then
/// convert it to his mustache format.
///
/// Example:
/// ```yaml
/// project_name:
/// compilable: wyatt_app_template
/// type: string
/// description: The project name
/// default: wyatt_app
/// prompt: "What is the project name?"
/// ```
///
/// Search for `wyatt_app_template` files and folders then converts
/// it to `{{project_name.snakeCase()}}`
static void renameCompilableFilesInFolder(
BrickConfig config,
String targetPath,
) {
if (!FileSystemEntity.isDirectorySync(targetPath)) {
throw ArgumentError('Target must be a directory', 'targetPath');
}
Directory(targetPath)
.listSync(recursive: true)
.whereType<File>()
.forEach((file) {
config.variables.whereType<BrickVariableString>().forEach((variable) {
// Replace all string variables
for (final syntax in VariableStringSyntax.values) {
final toReplace = variable.syntax?[syntax.mapKey];
if (toReplace != null && file.path.contains(toReplace)) {
final newPath = file.path.replaceAll(
toReplace,
'{{${variable.name}.${syntax.id}}}',
);
File(newPath).createSync(recursive: true);
file.renameSync(newPath);
Logger.debug(
'${file.path} renamed with variable `${variable.name}`',
);
}
}
});
});
}
}

View File

@ -19,12 +19,18 @@ import 'dart:io';
import 'package:brick_generator/models/log_level.dart';
class Logger {
static bool verbose = false;
static void setVerbose() => verbose = true;
static void log(Object? message, {LogLevel level = LogLevel.info}) {
stdout.writeln('${level.prefix} ${message.toString()}');
}
static void debug(Object? message) {
log(message, level: LogLevel.debug);
if (verbose) {
log(message, level: LogLevel.debug);
}
}
static void info(Object? message) {

View File

@ -16,23 +16,20 @@
import 'dart:io';
import 'package:brick_generator/logger.dart';
import 'package:brick_generator/core/logger.dart';
class Shell {
static Future<void> cp(String source, String destination) {
return _Cmd.run('cp', ['-Rf', source, destination]);
}
abstract class Shell {
static Future<void> cp(String source, String destination) =>
_Cmd.run('cp', ['-Rf', source, destination]);
static Future<void> mkdir(String destination) {
return _Cmd.run('mkdir', ['-p', destination]);
}
static Future<void> mkdir(String destination) =>
_Cmd.run('mkdir', ['-p', destination]);
static Future<void> rm(String target, {bool recursive = false}) {
return _Cmd.run('rm', ['-f${recursive ? "r" : ""}', target]);
}
static Future<void> rm(String target, {bool recursive = false}) =>
_Cmd.run('rm', ['-f${recursive ? "r" : ""}', target]);
}
class _Cmd {
abstract class _Cmd {
static Future<ProcessResult> run(
String cmd,
List<String> args, {

View File

@ -291,27 +291,21 @@ extension StringExtension on String {
/// print('Hello World'.sentence()) // Hello world
/// print('helloWorld'.sentence()) // Hello world
/// ```
String sentence() {
return lower().capitalize();
}
String sentence() => lower().capitalize();
/// ```dart
/// print('Hello World'.title()) // Hello World
/// print('helloWorld'.title()) // Hello World
/// ```
String title() {
return header().splitMapJoin(
_defaultMatcher,
onMatch: (m) => ' ',
onNonMatch: (n) => n,
);
}
String title() => header().splitMapJoin(
_defaultMatcher,
onMatch: (m) => ' ',
onNonMatch: (n) => n,
);
/// ```dart
/// print('Hello World'.mustache()) // {{ Hello World }}
/// print('helloWorld'.mustache()) // {{ Hello World }}
/// ```
String mustache() {
return '{{ ${title()} }}';
}
String mustache() => '{{ ${title()} }}';
}

View File

@ -16,11 +16,11 @@
import 'dart:io';
import 'package:brick_generator/logger.dart';
import 'package:brick_generator/core/logger.dart';
import 'package:brick_generator/core/shell.dart';
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:path/path.dart';
class FileSystemUtils {
@ -88,7 +88,7 @@ class FileSystemUtils {
final snake =
variable!.syntax![VariableStringSyntax.snakeCase.mapKey];
if (snake == null) {
final err = 'Invalid snake_case syntax';
const err = 'Invalid snake_case syntax';
Logger.throwError(ArgumentError(err), err);
}
final newPath = file.path.replaceAll(
@ -123,7 +123,7 @@ class FileSystemUtils {
BrickConfig brickConfig,
String brickPath,
) async {
for (final String ignore in brickConfig.brickIgnore ?? []) {
for (final String ignore in brickConfig.brickgenConfig.ignore.ignored) {
final String toDelete = join(brickPath, ignore);
if (FileSystemEntity.isDirectorySync(toDelete)) {
await Shell.rm(toDelete, recursive: true);

View File

@ -0,0 +1,69 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
// Copyright (C) 2023 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:brick_generator/models/boolean_file_system_variable.dart';
import 'package:yaml/yaml.dart';
const _foldersKey = 'folders';
const _filesKey = 'files';
class BooleanFileSystem {
BooleanFileSystem({
required this.booleanName,
required this.folders,
required this.files,
});
factory BooleanFileSystem.fromYaml(String key, YamlMap? source) {
if (source == null) {
throw ArgumentError.notNull('source');
}
final variableName = key;
final folders = BooleanFileSystemVariable.fromYaml(
source.nodes[_foldersKey] as YamlMap?,
);
final files = BooleanFileSystemVariable.fromYaml(
source.nodes[_filesKey] as YamlMap?,
);
return BooleanFileSystem(
booleanName: variableName,
folders: folders,
files: files,
);
}
String booleanName;
BooleanFileSystemVariable folders;
BooleanFileSystemVariable files;
BooleanFileSystem copyWith({
String? booleanName,
BooleanFileSystemVariable? folders,
BooleanFileSystemVariable? files,
}) =>
BooleanFileSystem(
booleanName: booleanName ?? this.booleanName,
folders: folders ?? this.folders,
files: files ?? this.files,
);
@override
String toString() => 'BooleanFileSystem(booleanName: $booleanName, '
'folders: $folders, files: $files)';
}

View File

@ -0,0 +1,61 @@
// Copyright (C) 2023 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:yaml/yaml.dart';
const _onTrueKey = 'on_true';
const _onFalseKey = 'on_false';
class BooleanFileSystemVariable {
BooleanFileSystemVariable({
required this.onTrueNames,
required this.onFalseNames,
});
factory BooleanFileSystemVariable.fromYaml(YamlMap? source) {
if (source == null) {
throw ArgumentError.notNull('source');
}
final onTrueNames = (source[_onTrueKey] as YamlList?)
?.map((element) => element as String)
.toList();
final onFalseNames = (source[_onFalseKey] as YamlList?)
?.map((element) => element as String)
.toList();
return BooleanFileSystemVariable(
onTrueNames: onTrueNames ?? [],
onFalseNames: onFalseNames ?? [],
);
}
final List<String> onTrueNames;
final List<String> onFalseNames;
BooleanFileSystemVariable copyWith({
List<String>? onTrueNames,
List<String>? onFalseNames,
}) =>
BooleanFileSystemVariable(
onTrueNames: onTrueNames ?? this.onTrueNames,
onFalseNames: onFalseNames ?? this.onFalseNames,
);
@override
String toString() => 'BooleanFileSystemVariable(onTrueNames: $onTrueNames, '
'onFalseNames: $onFalseNames)';
}

View File

@ -0,0 +1,77 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
// Copyright (C) 2023 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:brick_generator/models/boolean_name_single_mapping.dart';
import 'package:yaml/yaml.dart';
const _foldersKey = 'folders';
const _filesKey = 'files';
class BooleanMapping {
BooleanMapping({
required this.booleanName,
required this.folders,
required this.files,
});
factory BooleanMapping.fromYaml(String key, YamlMap? source) {
if (source == null) {
throw ArgumentError.notNull('source');
}
final variableName = key;
final folders = (source.nodes[_foldersKey] as YamlList?)
?.map(
(element) =>
BooleanNameSingleMapping.fromYaml(element as YamlMap?),
)
.toList() ??
[];
final files = (source.nodes[_filesKey] as YamlList?)
?.map(
(element) =>
BooleanNameSingleMapping.fromYaml(element as YamlMap?),
)
.toList() ??
[];
return BooleanMapping(
booleanName: variableName,
folders: folders,
files: files,
);
}
String booleanName;
List<BooleanNameSingleMapping> folders;
List<BooleanNameSingleMapping> files;
BooleanMapping copyWith({
String? booleanName,
List<BooleanNameSingleMapping>? folders,
List<BooleanNameSingleMapping>? files,
}) =>
BooleanMapping(
booleanName: booleanName ?? this.booleanName,
folders: folders ?? this.folders,
files: files ?? this.files,
);
@override
String toString() => 'BooleanMapping(booleanName: $booleanName, '
'folders: $folders, files: $files)';
}

View File

@ -0,0 +1,78 @@
// Copyright (C) 2023 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:brick_generator/models/boolean_name_mapping.dart';
import 'package:yaml/yaml.dart';
const _onTrueKey = 'on_true';
const _onFalseKey = 'on_false';
class BooleanNameListMapping extends BooleanNameMapping {
BooleanNameListMapping({
required this.fileSystemEntityName,
required this.onTrueNames,
required this.onFalseNames,
});
factory BooleanNameListMapping.fromYaml(YamlMap? source) {
if (source == null) {
throw ArgumentError.notNull('source');
}
final fsEntityName = source.keys.first as String;
final onTrueNames =
((source.values.first as YamlMap?)?[_onTrueKey] as YamlList?)
?.map((element) => element as String)
.toList();
final onFalseNames =
((source.values.first as YamlMap?)?[_onFalseKey] as YamlList?)
?.map((element) => element as String)
.toList();
return BooleanNameListMapping(
fileSystemEntityName: fsEntityName,
onTrueNames: onTrueNames ?? [],
onFalseNames: onFalseNames ?? [],
);
}
@override
final String fileSystemEntityName;
final List<String> onTrueNames;
final List<String> onFalseNames;
BooleanNameListMapping copyWith({
String? fileSystemEntityName,
List<String>? onTrueNames,
List<String>? onFalseNames,
}) =>
BooleanNameListMapping(
fileSystemEntityName: fileSystemEntityName ?? this.fileSystemEntityName,
onTrueNames: onTrueNames ?? this.onTrueNames,
onFalseNames: onFalseNames ?? this.onFalseNames,
);
@override
String get onFalseName => onFalseNames.first;
@override
String get onTrueName => onTrueNames.first;
@override
String toString() =>
'BooleanNameListMapping(fileSystemEntityName: $fileSystemEntityName, '
'onTrueNames: $onTrueNames, onFalseNames: $onFalseNames)';
}

View File

@ -0,0 +1,21 @@
// Copyright (C) 2023 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
abstract class BooleanNameMapping {
String get fileSystemEntityName;
String get onTrueName;
String get onFalseName;
}

View File

@ -0,0 +1,70 @@
// Copyright (C) 2023 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:brick_generator/models/boolean_name_mapping.dart';
import 'package:yaml/yaml.dart';
const _onTrueKey = 'on_true';
const _onFalseKey = 'on_false';
class BooleanNameSingleMapping extends BooleanNameMapping {
BooleanNameSingleMapping({
required this.fileSystemEntityName,
required this.onTrueName,
required this.onFalseName,
});
factory BooleanNameSingleMapping.fromYaml(YamlMap? source) {
if (source == null) {
throw ArgumentError.notNull('source');
}
final fsEntityName = source.keys.first as String;
final onTrueName =
(source.values.first as YamlMap?)?[_onTrueKey] as String?;
final onFalseName =
(source.values.first as YamlMap?)?[_onFalseKey] as String?;
return BooleanNameSingleMapping(
fileSystemEntityName: fsEntityName,
onTrueName: onTrueName ?? fsEntityName,
onFalseName: onFalseName ?? fsEntityName,
);
}
@override
final String fileSystemEntityName;
@override
final String onTrueName;
@override
final String onFalseName;
BooleanNameSingleMapping copyWith({
String? fileSystemEntityName,
String? onTrueName,
String? onFalseName,
}) =>
BooleanNameSingleMapping(
fileSystemEntityName: fileSystemEntityName ?? this.fileSystemEntityName,
onTrueName: onTrueName ?? this.onTrueName,
onFalseName: onFalseName ?? this.onFalseName,
);
@override
String toString() =>
'BooleanNameSingleMapping(fileSystemEntityName: $fileSystemEntityName, '
'onTrueName: $onTrueName, onFalseName: $onFalseName)';
}

View File

@ -14,96 +14,86 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:brick_generator/logger.dart';
import 'package:brick_generator/models/brick_variable.dart';
import 'package:brick_generator/models/brickgen_config.dart';
import 'package:brick_generator/yaml_reader.dart';
import 'package:yaml/yaml.dart';
const _nameKey = 'name';
const _descriptionKey = 'description';
const _versionKey = 'version';
const _pathToBrickifyKey = 'path_to_brickify';
const _brickIgnoreKey = 'brick_ignore';
const _varsKey = 'vars';
const _brickgenKey = 'brickgen';
class BrickConfig {
String? name;
String? description;
String? version;
String? pathToBrickify;
List<String>? brickIgnore;
List<BrickVariable?>? variables;
BrickConfig({
required this.name,
required this.description,
required this.version,
required this.pathToBrickify,
required this.brickIgnore,
required this.variables,
required this.brickgenConfig,
});
BrickConfig._();
factory BrickConfig.parser() => BrickConfig._();
factory BrickConfig.parse(YamlMap? data) {
if (data == null) {
throw ArgumentError.notNull('data');
}
static BrickConfig? from(YamlMap? data) => data != null
? BrickConfig(
name: data[_nameKey] as String?,
description: data[_descriptionKey] as String?,
version: data[_versionKey] as String?,
pathToBrickify: data[_pathToBrickifyKey] as String?,
brickIgnore: (data[_brickIgnoreKey] as YamlList?)
?.map((dynamic element) => element as String)
.toList(),
variables: (data[_varsKey] as YamlMap?)
?.map<dynamic, BrickVariable?>(
(dynamic key, dynamic value) =>
MapEntry<dynamic, BrickVariable?>(
key,
BrickVariable.from(value as YamlMap?),
),
)
.values
.toList(),
final variables = (data[_varsKey] as YamlMap?)
?.entries
.map(
(e) => BrickVariable.fromYaml(e.key as String, e.value as YamlMap?),
)
: null;
.toList();
void checkFormat() {
if (name == null || description == null || pathToBrickify == null) {
final err = 'Yaml file is not conform';
Logger.throwError(ArgumentError(err), err);
}
if (variables != null) {
for (final variable in variables!) {
variable?.checkFormat();
}
}
final config = BrickConfig(
name: data[_nameKey] as String? ?? '',
description: data[_descriptionKey] as String? ?? '',
version: data[_versionKey] as String? ?? '',
variables: variables ?? [],
brickgenConfig: BrickgenConfig.parse(data[_brickgenKey] as YamlMap?),
);
return config;
}
String toBrickYaml() {
String content = '''
factory BrickConfig.fromFile(String? path) {
if (path == null) {
throw ArgumentError.notNull('path');
}
final config = YamlReader.readYamlFile(path);
return BrickConfig.parse(config);
}
String name;
String description;
String version;
List<BrickVariable<dynamic>> variables;
BrickgenConfig brickgenConfig;
String toMason() {
final content = StringBuffer('''
name: $name
description: $description
version: $version
''';
''');
if (variables?.isNotEmpty ?? false) {
content += 'vars:\n';
for (final BrickVariable? v in variables!) {
if (v != null) {
final vString = '''
${v.name}:
type: ${v.type.toString()}
description: ${v.description}
default: ${v.defaultValue}
prompt: ${v.prompt}
''';
content += vString;
}
if (variables.isNotEmpty) {
content.writeln('vars:');
for (final variable in variables) {
content.writeln(variable.toMason());
}
}
return content;
return content.toString();
}
@override
String toString() => 'BrickConfig(name: $name, '
'description: $description, version: $version, variables: $variables, '
'brickgenConfig: $brickgenConfig)';
}

View File

@ -14,53 +14,46 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:brick_generator/logger.dart';
import 'package:brick_generator/models/brick_variable_boolean.dart';
import 'package:brick_generator/models/brick_variable_string.dart';
import 'package:brick_generator/models/variable_type.dart';
import 'package:brick_generator/string_extension.dart';
import 'package:yaml/yaml.dart';
const _typeKey = 'type';
const _nameKey = 'name';
const _descriptionKey = 'description';
const _defaultKey = 'default';
const _promptKey = 'prompt';
class BrickVariable {
VariableType? type;
String? name;
String? description;
String? defaultValue;
String? prompt;
Map<String, String>? syntax;
abstract class BrickVariable<T> {
BrickVariable({
required this.type,
required this.name,
required this.description,
required this.defaultValue,
required this.prompt,
required this.syntax,
});
BrickVariable._();
String name;
VariableType type;
String description;
T defaultValue;
String prompt;
factory BrickVariable.parser() => BrickVariable._();
static BrickVariable? from(YamlMap? data) => data != null
? BrickVariable(
type: VariableType.fromString(data[_typeKey] as String?),
name: data[_nameKey] as String?,
description: data[_descriptionKey] as String?,
defaultValue: data[_defaultKey] as String?,
prompt: data[_promptKey] as String?,
syntax: (data[_nameKey] as String? ?? '').syntaxes,
)
: null;
void checkFormat() {
if (name == null || description == null || type == null) {
final err = 'Yaml file is not conform';
Logger.throwError(ArgumentError(err), err);
static BrickVariable<dynamic> fromYaml(String key, YamlMap? source) {
final type = VariableType.fromString(source?[_typeKey] as String?);
switch (type) {
case VariableType.string:
return BrickVariableString.fromYaml(key, source);
case VariableType.boolean:
return BrickVariableBoolean.fromYaml(key, source);
case VariableType.none:
throw ArgumentError('Invalid variable type', 'type');
}
}
// TODO(wyatt): use `yaml_writer` to dumps YAML properly.
String toMason() => '''
$name:
type: $type
description: $description
default: $defaultValue
prompt: $prompt
''';
}

View File

@ -0,0 +1,53 @@
// Copyright (C) 2023 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:brick_generator/models/brick_variable.dart';
import 'package:brick_generator/models/variable_type.dart';
import 'package:yaml/yaml.dart';
const _typeKey = 'type';
const _descriptionKey = 'description';
const _defaultKey = 'default';
const _promptKey = 'prompt';
class BrickVariableBoolean extends BrickVariable<bool> {
BrickVariableBoolean({
required super.type,
required super.name,
required super.description,
required super.defaultValue,
required super.prompt,
});
factory BrickVariableBoolean.fromYaml(String key, YamlMap? source) {
if (source == null) {
throw ArgumentError.notNull('source');
}
return BrickVariableBoolean(
type: VariableType.fromString(source[_typeKey] as String?),
name: key,
description: source[_descriptionKey] as String? ?? '',
defaultValue: source[_defaultKey] as bool? ?? false,
prompt: source[_promptKey] as String? ?? '',
);
}
@override
String toString() => 'BrickVariableBoolean(name: $name, '
'description: $description, defaultValue: $defaultValue, '
'prompt: $prompt,)';
}

View File

@ -0,0 +1,62 @@
// Copyright (C) 2023 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:brick_generator/core/string_extension.dart';
import 'package:brick_generator/models/brick_variable.dart';
import 'package:brick_generator/models/variable_type.dart';
import 'package:yaml/yaml.dart';
const _compilableKey = 'compilable';
const _typeKey = 'type';
const _descriptionKey = 'description';
const _defaultKey = 'default';
const _promptKey = 'prompt';
class BrickVariableString extends BrickVariable<String> {
BrickVariableString({
required this.compilable,
required this.syntax,
required super.type,
required super.name,
required super.description,
required super.defaultValue,
required super.prompt,
});
factory BrickVariableString.fromYaml(String key, YamlMap? source) {
if (source == null) {
throw ArgumentError.notNull('source');
}
return BrickVariableString(
compilable: source[_compilableKey] as String? ?? '',
type: VariableType.fromString(source[_typeKey] as String?),
name: key,
description: source[_descriptionKey] as String? ?? '',
defaultValue: source[_defaultKey] as String? ?? '',
prompt: source[_promptKey] as String? ?? '',
syntax: (source[_compilableKey] as String? ?? '').syntaxes,
);
}
String compilable;
Map<String, String>? syntax;
@override
String toString() => 'BrickVariableString(name: $name, '
'description: $description, compilable: $compilable, '
'defaultValue: $defaultValue, prompt: $prompt,)';
}

View File

@ -0,0 +1,84 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
// Copyright (C) 2023 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:brick_generator/models/boolean_file_system.dart';
import 'package:brick_generator/models/boolean_mapping.dart';
import 'package:brick_generator/models/ignore_list.dart';
import 'package:yaml/yaml.dart';
const _pathToBrickifyKey = 'path_to_brickify';
const _ignoreKey = 'ignore';
const _hooksKey = 'hooks';
const _booleanMappingKey = 'boolean_mapping';
const _booleanFileSystemKey = 'boolean_file_system';
class BrickgenConfig {
final String pathToBrickify;
final IgnoreList ignore;
final bool hooks;
final List<BooleanMapping> booleanMapping;
final List<BooleanFileSystem> booleanFileSystem;
BrickgenConfig({
required this.pathToBrickify,
required this.ignore,
required this.hooks,
required this.booleanMapping,
required this.booleanFileSystem,
});
factory BrickgenConfig.parse(YamlMap? data) {
if (data == null) {
throw ArgumentError.notNull('data');
}
final booleanMapping = (data[_booleanMappingKey] as YamlMap?)
?.entries
.map(
(e) =>
BooleanMapping.fromYaml(e.key as String, e.value as YamlMap?),
)
.toList() ??
[];
final booleanFileSystem = (data[_booleanFileSystemKey] as YamlMap?)
?.entries
.map(
(e) => BooleanFileSystem.fromYaml(
e.key as String,
e.value as YamlMap?,
),
)
.toList() ??
[];
final config = BrickgenConfig(
pathToBrickify: data[_pathToBrickifyKey] as String? ?? '',
ignore: IgnoreList.fromConfig(data[_ignoreKey] as YamlList?),
hooks: data[_hooksKey] as bool? ?? false,
booleanMapping: booleanMapping,
booleanFileSystem: booleanFileSystem,
);
return config;
}
@override
String toString() => 'BrickgenConfig(pathToBrickify: $pathToBrickify, '
'ignore: $ignore, hooks: $hooks, booleanMapping: $booleanMapping, '
'booleanFileSystem: $booleanFileSystem)';
}

View File

@ -0,0 +1,108 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
// Copyright (C) 2023 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'dart:io';
import 'package:path/path.dart';
import 'package:yaml/yaml.dart';
class IgnoreList {
final List<String> ignoredFiles;
final List<String> ignoredFolders;
IgnoreList({
required this.ignoredFiles,
required this.ignoredFolders,
});
/// Only parse files an folders (no glob)
/// - file <- file
/// - folder/ <- folder, ends with "/"
/// - infolder/file <- file
/// - sub/folder/ <- folder, ends with "/"
factory IgnoreList.fromConfig(YamlList? list) {
final List<String> ignoredFiles = [];
final List<String> ignoredFolders = [];
final ignored =
list?.map<String>((element) => element as String).toList() ?? [];
for (final element in ignored) {
if (element.endsWith('/')) {
// This is a folder
ignoredFolders.add(element.substring(0, element.length - 1));
} else {
// This is a file
ignoredFiles.add(element);
}
}
return IgnoreList(
ignoredFiles: ignoredFiles,
ignoredFolders: ignoredFolders,
);
}
factory IgnoreList.fromBrickIgnoreFile() {
throw UnimplementedError('.brickignore is not yet supported');
}
bool contains(String root, String path) {
final FileSystemEntityType type = FileSystemEntity.typeSync(path);
// Check if file/folder is an ignored folder (or any subtree of it)
for (final element in ignoredFolders) {
final ignoredPath = join(root, element);
if (path.startsWith(ignoredPath)) {
return true;
}
}
switch (type) {
case FileSystemEntityType.file:
for (final element in ignoredFiles) {
final ignoredPath = join(root, element);
if (ignoredPath == path) {
return true;
}
}
return false;
case FileSystemEntityType.directory:
for (final element in ignoredFolders) {
final ignoredPath = join(root, element);
if (ignoredPath == path) {
return true;
}
}
return false;
case FileSystemEntityType.link:
case FileSystemEntityType.notFound:
case FileSystemEntityType.pipe:
case FileSystemEntityType.unixDomainSock:
throw ArgumentError(
'This file system entity type is not supported',
'from',
);
}
return false;
}
@override
String toString() => 'IgnoreList(ignoredFiles: $ignoredFiles, '
'ignoredFolders: $ignoredFolders)';
}

View File

@ -20,7 +20,7 @@ enum LogLevel {
success(''),
error('');
final String prefix;
const LogLevel(this.prefix);
final String prefix;
}

View File

@ -29,8 +29,8 @@ enum VariableStringSyntax {
pathCase('path_case', 'pathCase'),
mustacheCase('mustache_case', 'mustacheCase');
const VariableStringSyntax(this.mapKey, this.id);
final String mapKey;
final String id;
const VariableStringSyntax(this.mapKey, this.id);
}

View File

@ -17,21 +17,19 @@
enum VariableType {
none,
string,
bool;
boolean;
static VariableType fromString(String? type) {
switch (type) {
case 'string':
return string;
case 'bool':
return bool;
case 'boolean':
return boolean;
default:
return none;
}
}
@override
String toString() {
return name;
}
String toString() => name;
}

View File

@ -8,13 +8,14 @@ environment:
sdk: ">=2.17.0 <3.0.0"
dependencies:
args: ^2.3.2
path: ^1.8.2
yaml: ^3.1.1
dev_dependencies:
test: ^1.22.2
wyatt_analysis:
git:
url: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages
ref: wyatt_analysis-v2.1.0
path: packages/wyatt_analysis
hosted:
url: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/
name: wyatt_analysis
version: 2.3.0

View File

@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:brick_generator/string_extension.dart';
import 'package:brick_generator/core/string_extension.dart';
import 'package:test/test.dart';
void main() {
@ -32,22 +32,22 @@ void main() {
'snake_case': 'feature_name',
};
test('transforms `feature_name`', () {
final name = 'feature_name';
const name = 'feature_name';
expect(name.syntaxes, equals(expected));
});
test('transforms `featureName`', () {
final name = 'feature_name';
const name = 'feature_name';
expect(name.syntaxes, equals(expected));
});
test('transforms `feature-Name`', () {
final name = 'feature_name';
const name = 'feature_name';
expect(name.syntaxes, equals(expected));
});
test('transforms `Feature Name`', () {
final name = 'feature_name';
const name = 'feature_name';
expect(name.syntaxes, equals(expected));
});
}