Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 49 additions & 10 deletions rocket_cli/README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,49 @@
```dart
import 'package:rocket_cli/rocket_cli.dart';

void main(List<String> arguments) {
Generator gen = Generator();
ModelsController models = gen.generate(
'{"name":"John Doe","age":30,"cars":[{"hello":"World"}]}', "Person");
print(models.models);
}
```
# Rocket CLI

A powerful command-line tool to generate `RocketModel` classes from JSON data.

## Features

- Generate models from raw JSON strings.
- Generate models from JSON files.
- Automatically handles nested objects and lists.
- Supports custom class names and output directories.

## Installation

You can run it directly using `dart run` within the package:

```bash
dart run rocket_cli [arguments]
```

Or activate it globally:

```bash
dart pub global activate --source path .
rocket_cli [arguments]
```

## Usage

### Generate from JSON file

```bash
rocket_cli -f data.json -n UserProfile -o lib/models
```

### Generate from raw JSON string

```bash
rocket_cli -j '{"id":1, "name":"John"}' -n User
```

### Arguments

| Argument | Abbr | Description | Default |
| --- | --- | --- | --- |
| `--json` | `-j` | Raw JSON string | |
| `--file` | `-f` | Path to JSON file | |
| `--name` | `-n` | Root class name | `MyModel` |
| `--output` | `-o` | Output directory | `lib/models` |
| `--help` | `-h` | Show help | |
85 changes: 78 additions & 7 deletions rocket_cli/bin/rocket_cli.dart
Original file line number Diff line number Diff line change
@@ -1,12 +1,83 @@
import 'dart:io';
import 'package:args/args.dart';
import 'package:rocket_cli/rocket_cli.dart';

void main(List<String> arguments) {
Generator gen = Generator();
ModelsController controller = ModelsController();
gen.generate('{"name":"John Doe","age":30,"cars":[{"hello":"World"}]}',
"Person", controller);
void main(List<String> arguments) async {
final parser = ArgParser()
..addOption('json', abbr: 'j', help: 'Raw JSON string')
..addOption('file', abbr: 'f', help: 'Path to JSON file')
..addOption('name', abbr: 'n', help: 'Class name', defaultsTo: 'MyModel')
..addOption('output',
abbr: 'o', help: 'Output directory', defaultsTo: 'lib/models')
..addFlag('help', abbr: 'h', negatable: false, help: 'Show help');

ArgResults argResults;
try {
argResults = parser.parse(arguments);
} catch (e) {
print(e);
print(parser.usage);
return;
}

if (argResults['help']) {
print('Rocket CLI - Model Generator');
print(parser.usage);
return;
}

String? jsonContent;

if (argResults['json'] != null) {
jsonContent = argResults['json'];
} else if (argResults['file'] != null) {
final file = File(argResults['file']);
if (!await file.exists()) {
print('Error: File not found at ${argResults['file']}');
return;
}
jsonContent = await file.readAsString();
} else {
print('Error: You must provide either --json or --file');
print(parser.usage);
return;
}

final String className = argResults['name'];
final String outputDir = argResults['output'];

final Generator gen = Generator();
final ModelsController controller = ModelsController();

try {
await gen.generate(jsonContent!, className, controller);
} catch (e) {
print('Error during generation: $e');
return;
}

if (controller.models.isEmpty) {
print('No models generated.');
return;
}

final directory = Directory(outputDir);
if (!await directory.exists()) {
await directory.create(recursive: true);
}

for (var model in controller.models) {
print(model.name);
print(model.result);
// Generate filename from class name (camelCase to snake_case)
String fileName = model.name
.replaceAllMapped(
RegExp(r'([a-z0-9])([A-Z])'), (Match m) => '${m[1]}_${m[2]}')
.toLowerCase();

final file = File('${directory.path}/$fileName.dart');
await file.writeAsString(model.result);
print('✅ Generated: ${file.path}');
}

print(
'\nSuccess! ${controller.models.length} model(s) created in $outputDir');
}
130 changes: 97 additions & 33 deletions rocket_cli/lib/src/model_generator/models/generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,94 +9,140 @@ import 'utils/template.dart';

class Generator {
late String copyTemplate;
final Set<String> _generatedClasses = {};

Future<void> generate(
String inputUser, String className, ModelsController controller,
{bool multi = false}) async {
copyTemplate = template;
className = className.isEmpty ? "MyModel" : className.firstUpper;

if (_generatedClasses.contains(className)) return;

inputUser =
inputUser.isEmpty ? '{"MVCRocket Package":"MvcRocket"}' : inputUser;
var jsonInputUser = json.decode(inputUser.trim());

dynamic jsonInputUser;
try {
jsonInputUser = json.decode(inputUser.trim());
} catch (e) {
print("Error decoding JSON: $e");
return;
}

if (jsonInputUser is List) {
if (jsonInputUser.isEmpty) {
print("Empty list, cannot infer type for $className");
return;
}
return generate(json.encode(jsonInputUser.first), className, controller,
multi: true);
} else if (jsonInputUser is Map<String, dynamic>) {
_generatedClasses.add(className);
generateFields(jsonInputUser, className, controller, multi: multi);
} else {
print("Unsupported type");
print("Unsupported JSON type for $className");
}
}

bool _isPrimitive(item) {
return item is String || item is int || item is double || item is bool;
}

bool _isDateField(String value) {
return DateTime.tryParse(value) != null &&
RegExp(r'^\d{4}-\d{2}-\d{2}').hasMatch(value);
}

generateFields(Map<String, dynamic> fields, String className,
ModelsController controller,
{bool multi = false}) {
ModelItems modelItems = ModelItems();
fields.forEach((key, value) {
String fieldType = _solveDouble(value);
String fieldType = _solveType(value);
late String line;
late String fromJson, toJson;
String initFields = "";
late String fieldsKey, updateFieldsParams, updateFieldsBody;

final String fieldKeyMap =
"${className.toLowerCase()}${key.camel.firstUpper}Field";
final String fieldLine =
'const String ${className.toLowerCase()}${key.camel.firstUpper}Field = "$key";';
final String updateFieldParamLine = "$fieldType? ${key.camel}Field,";
final String updateFieldBodyLine =
"${key.camel} = ${key.camel}Field ?? ${key.camel};";

bool isPrimitive = _isPrimitive(value);
if (isPrimitive) {
line = "$fieldType? ${key.camel};";

if (value == null) {
line = "dynamic? ${key.camel};";
fromJson = "${key.camel} = json[$fieldKeyMap];";
toJson = "data[$fieldKeyMap] = ${key.camel};";
fieldsKey = fieldLine;
updateFieldsParams = "dynamic? ${key.camel}Field,";
updateFieldsBody = updateFieldBodyLine;
} else if (isPrimitive) {
line = "$fieldType? ${key.camel};";
if (fieldType == "DateTime") {
fromJson =
"${key.camel} = json[$fieldKeyMap] != null ? DateTime.tryParse(json[$fieldKeyMap].toString()) : null;";
toJson = "data[$fieldKeyMap] = ${key.camel}?.toIso8601String();";
} else {
fromJson = "${key.camel} = json[$fieldKeyMap];";
toJson = "data[$fieldKeyMap] = ${key.camel};";
}
fieldsKey = fieldLine;
updateFieldsParams = updateFieldParamLine;
updateFieldsBody = updateFieldBodyLine;
} else if (value is List) {
bool isNotEmpty = value.isNotEmpty;
bool isPrimitive = false;
if (isNotEmpty) isPrimitive = _isPrimitive(value.first);
if (!isNotEmpty || isPrimitive) {
bool isListOrPrimitive = false;

dynamic firstItem = isNotEmpty ? value.first : null;
if (isNotEmpty) {
isListOrPrimitive = _isPrimitive(firstItem) || firstItem is List;
}

if (!isNotEmpty || isListOrPrimitive) {
final String fieldSubType =
isNotEmpty ? _solveDouble(value.first) : "dynamic";
isNotEmpty ? _solveType(firstItem) : "dynamic";
line = "List<$fieldSubType>? ${key.camel};";
fromJson = "${key.camel} = json[$fieldKeyMap];";
fromJson =
"${key.camel} = json[$fieldKeyMap]?.cast<$fieldSubType>();";
toJson = "data[$fieldKeyMap] = ${key.camel};";
fieldsKey = fieldLine;
updateFieldsParams = "List<$fieldSubType>? ${key.camel}Field,";
updateFieldsBody = updateFieldBodyLine;
} else {
line = "${key.firstUpper}? $key;";
fromJson = "${key.camel}!.setMulti(json['$key']);";
String subClassName = key.camel.firstUpper;
line = "$subClassName? ${key.camel};";
fromJson =
"if (json[$fieldKeyMap] != null) ${key.camel}!.setMulti(json[$fieldKeyMap]);";
toJson =
"data[$fieldKeyMap] = ${key.camel}!.all.map((e)=> e.toJson()).toList();";
"data[$fieldKeyMap] = ${key.camel}!.all?.map((e)=> e.toJson()).toList();";
fieldsKey = fieldLine;
updateFieldsParams = "${key.firstUpper}? ${key.camel}Field,";
updateFieldsParams = "$subClassName? ${key.camel}Field,";
updateFieldsBody = updateFieldBodyLine;
initFields = "$key??=${key.firstUpper}();";
initFields = "${key.camel}??=$subClassName();";

Generator reGenerate = Generator();
reGenerate.generate(json.encode(value), key.firstUpper, controller,
multi: true);
generate(json.encode(value), subClassName, controller, multi: true);
}
} else if (value is Map) {
line = "${key.firstUpper}? $key;";
} else if (value is Map<String, dynamic>) {
String subClassName = key.camel.firstUpper;
line = "$subClassName? ${key.camel};";
fromJson = "${key.camel}!.fromJson(json[$fieldKeyMap]);";
toJson = "data[$fieldKeyMap] = ${key.camel}!.toJson();";
fieldsKey = fieldLine;
updateFieldsParams = "${key.firstUpper}? ${key.camel}Field,";
updateFieldsParams = "$subClassName? ${key.camel}Field,";
updateFieldsBody = updateFieldBodyLine;
initFields = "$key??=${key.firstUpper}();";
initFields = "${key.camel}??=$subClassName();";

Generator reGenerate = Generator();
reGenerate.generate(json.encode(value), key.firstUpper, controller);
generate(json.encode(value), subClassName, controller);
} else {
print("Unsupported type");
print("Unsupported type for key: $key");
}

modelItems.constFields += "this.${key.camel},";
modelItems.fieldsLines += line;
modelItems.fromJsonFields += fromJson;
Expand All @@ -105,21 +151,39 @@ class Generator {
modelItems.updateFieldsParams += updateFieldsParams;
modelItems.updateFieldsBody += updateFieldsBody;
modelItems.initFields += initFields;
if (multi) {
modelItems.instance = "@override get instance => $className();";
}
});

if (multi) {
modelItems.instance =
"@override $className get instance => $className();";
}

modelItems.className = className;
String result = DartFormatter().format(modelItems.result);
controller.addModel(result, className);
// return controller;
try {
String result = DartFormatter().format(modelItems.result);
controller.addModel(result, className);
} catch (e) {
print("Error formatting $className: $e");
controller.addModel(modelItems.result, className);
}
}

String _solveDouble(dynamic field) {
if (field is int) {
String _solveType(dynamic field) {
if (field == null) return "dynamic";
if (field is String) {
if (_isDateField(field)) return "DateTime";
return "String";
}
if (field is bool) return "bool";
if (field is num) {
if (field.isDouble) {
return "double";
}
return "int";
}
if (field is List) {
String subType = field.isNotEmpty ? _solveType(field.first) : "dynamic";
return "List<$subType>";
}
return field.runtimeType.toString();
}
Expand Down
Loading