diff --git a/.github/workflows/php81.yaml b/.github/workflows/php81.yaml index fead114..77e68ff 100644 --- a/.github/workflows/php81.yaml +++ b/.github/workflows/php81.yaml @@ -8,14 +8,14 @@ on: jobs: test: name: Run Tests - uses: WebFiori/workflows/.github/workflows/test-php.yaml@main + uses: WebFiori/workflows/.github/workflows/test-php.yaml@v1.2.1 with: php-version: '8.1' code-coverage: name: Coverage needs: test - uses: WebFiori/workflows/.github/workflows/coverage-codecov.yaml@main + uses: WebFiori/workflows/.github/workflows/coverage-codecov.yaml@v1.2.1 with: php-version: '8.1' coverage-file: 'php-8.1-coverage.xml' diff --git a/.github/workflows/php82.yaml b/.github/workflows/php82.yaml index 978033a..9e431d3 100644 --- a/.github/workflows/php82.yaml +++ b/.github/workflows/php82.yaml @@ -8,7 +8,7 @@ on: jobs: test: name: Run Tests - uses: WebFiori/workflows/.github/workflows/test-php.yaml@main + uses: WebFiori/workflows/.github/workflows/test-php.yaml@v1.2.1 with: php-version: '8.2' phpunit-config: "tests/phpunit10.xml" @@ -16,7 +16,7 @@ jobs: code-coverage: name: Coverage needs: test - uses: WebFiori/workflows/.github/workflows/coverage-codecov.yaml@main + uses: WebFiori/workflows/.github/workflows/coverage-codecov.yaml@v1.2.1 with: php-version: '8.2' coverage-file: 'php-8.2-coverage.xml' diff --git a/.github/workflows/php83.yaml b/.github/workflows/php83.yaml index 56bbbd1..17c1972 100644 --- a/.github/workflows/php83.yaml +++ b/.github/workflows/php83.yaml @@ -12,7 +12,7 @@ jobs: test: name: Run Tests - uses: WebFiori/workflows/.github/workflows/test-php.yaml@main + uses: WebFiori/workflows/.github/workflows/test-php.yaml@v1.2.1 with: php-version: '8.3' phpunit-config: 'tests/phpunit10.xml' @@ -21,7 +21,7 @@ jobs: code-coverage: name: Coverage needs: test - uses: WebFiori/workflows/.github/workflows/coverage-codecov.yaml@main + uses: WebFiori/workflows/.github/workflows/coverage-codecov.yaml@v1.2.1 with: php-version: '8.3' coverage-file: 'php-8.3-coverage.xml' diff --git a/.github/workflows/php84.yaml b/.github/workflows/php84.yaml index 1ce3097..adcc94b 100644 --- a/.github/workflows/php84.yaml +++ b/.github/workflows/php84.yaml @@ -8,7 +8,7 @@ on: jobs: test: name: Run Tests - uses: WebFiori/workflows/.github/workflows/test-php.yaml@main + uses: WebFiori/workflows/.github/workflows/test-php.yaml@v1.2.1 with: php-version: '8.4' phpunit-config: "tests/phpunit10.xml" @@ -16,7 +16,7 @@ jobs: code-coverage: name: Coverage needs: test - uses: WebFiori/workflows/.github/workflows/coverage-codecov.yaml@main + uses: WebFiori/workflows/.github/workflows/coverage-codecov.yaml@v1.2.1 with: php-version: '8.4' coverage-file: 'php-8.4-coverage.xml' diff --git a/.github/workflows/php85.yaml b/.github/workflows/php85.yaml new file mode 100644 index 0000000..b6743fd --- /dev/null +++ b/.github/workflows/php85.yaml @@ -0,0 +1,27 @@ +name: Build PHP 8.5 + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] +jobs: + test: + name: Run Tests + uses: WebFiori/workflows/.github/workflows/test-php.yaml@v1.2.1 + with: + php-version: '8.5' + phpunit-config: "tests/phpunit10.xml" + + code-coverage: + name: Coverage + needs: test + uses: WebFiori/workflows/.github/workflows/coverage-codecov.yaml@v1.2.1 + with: + php-version: '8.5' + coverage-file: 'php-8.5-coverage.xml' + secrets: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + + + diff --git a/README.md b/README.md index 35ffc2d..7b879ab 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@ Class library that can help in writing command line based applications with minimum dependencies using PHP.
-
-
+
+
@@ -56,6 +56,7 @@ Class library that can help in writing command line based applications with mini
|
|
|
|
|
|
+|
|
## Features
* **Easy Command Creation**: Simple class-based approach to building CLI commands
diff --git a/WebFiori/Cli/Commands/MakeCommand.php b/WebFiori/Cli/Commands/MakeCommand.php
new file mode 100644
index 0000000..832f360
--- /dev/null
+++ b/WebFiori/Cli/Commands/MakeCommand.php
@@ -0,0 +1,221 @@
+templateManager = new TemplateManager();
+
+ parent::__construct('make:command', [
+ '--name' => [
+ ArgumentOption::DESCRIPTION => 'The name of the command (e.g., "user:create")',
+ ArgumentOption::OPTIONAL => false
+ ],
+ '--class' => [
+ ArgumentOption::DESCRIPTION => 'The class name (e.g., "CreateUserCommand")',
+ ArgumentOption::OPTIONAL => true
+ ],
+ '--path' => [
+ ArgumentOption::DESCRIPTION => 'Output directory path',
+ ArgumentOption::OPTIONAL => true,
+ ArgumentOption::DEFAULT => 'commands'
+ ],
+ '--namespace' => [
+ ArgumentOption::DESCRIPTION => 'PHP namespace for the command class',
+ ArgumentOption::OPTIONAL => true
+ ],
+ '--interactive' => [
+ ArgumentOption::DESCRIPTION => 'Generate command with interactive prompts',
+ ArgumentOption::OPTIONAL => true
+ ],
+ '--args' => [
+ ArgumentOption::DESCRIPTION => 'Add command arguments (comma-separated)',
+ ArgumentOption::OPTIONAL => true
+ ],
+ '--template' => [
+ ArgumentOption::DESCRIPTION => 'Template type to use',
+ ArgumentOption::OPTIONAL => true,
+ ArgumentOption::VALUES => $this->templateManager->getAvailableTemplates(),
+ ArgumentOption::DEFAULT => 'basic'
+ ]
+ ], 'Generate a new CLI command class with scaffolding');
+ }
+
+ public function exec(): int {
+ $this->println('🚀 WebFiori CLI Command Generator');
+ $this->println('=================================');
+ $this->println();
+
+ // Get command details
+ $commandName = $this->getArgValue('--name');
+ $className = $this->getArgValue('--class') ?? $this->generateClassName($commandName);
+ $outputPath = $this->getArgValue('--path') ?? 'commands';
+ $namespace = $this->getArgValue('--namespace');
+ $template = $this->getArgValue('--template') ?? 'basic';
+ $interactive = $this->isArgProvided('--interactive');
+ $args = $this->getArgValue('--args');
+
+ // Interactive mode for missing details
+ if (!$namespace) {
+ $namespace = $this->getInput('Enter namespace (optional): ') ?: null;
+ }
+
+ // Validate inputs
+ if (!$this->validateInputs($commandName, $className)) {
+ return 1;
+ }
+
+ // Generate command
+ try {
+ $filePath = $this->generateCommand([
+ 'name' => $commandName,
+ 'class' => $className,
+ 'path' => $outputPath,
+ 'namespace' => $namespace,
+ 'template' => $template,
+ 'interactive' => $interactive,
+ 'args' => $args ? explode(',', $args) : []
+ ]);
+
+ $this->success("✅ Command generated successfully!");
+ $this->info("📁 File: $filePath");
+ $this->info("🏷️ Class: $className");
+ $this->info("⚡ Command: $commandName");
+
+ $this->println();
+ $this->println("Next steps:");
+ $this->println("1. Register the command in your application");
+ $this->println("2. Implement the exec() method logic");
+ $this->println("3. Add any additional arguments or validation");
+
+ return 0;
+ } catch (\Exception $e) {
+ $this->error("❌ Failed to generate command: " . $e->getMessage());
+ return 1;
+ }
+ }
+
+ /**
+ * Generate class name from command name.
+ */
+ private function generateClassName(string $commandName): string {
+ // Convert command-name or namespace:command to ClassName
+ $parts = preg_split('/[:\-_]/', $commandName);
+ $className = '';
+
+ foreach ($parts as $part) {
+ $className .= ucfirst(strtolower($part));
+ }
+
+ return $className . 'Command';
+ }
+
+ /**
+ * Validate command inputs.
+ */
+ private function validateInputs(string $commandName, string $className): bool {
+ // Validate command name
+ if (!preg_match('/^[a-z][a-z0-9\-:_]*$/', $commandName)) {
+ $this->error('Command name must start with a letter and contain only lowercase letters, numbers, hyphens, colons, and underscores.');
+ return false;
+ }
+
+ // Validate class name
+ if (!preg_match('/^[A-Z][a-zA-Z0-9]*$/', $className)) {
+ $this->error('Class name must be a valid PHP class name (PascalCase).');
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Generate the command file.
+ */
+ private function generateCommand(array $config): string {
+ $content = $this->templateManager->processTemplate($config['template'], [
+ 'namespace' => $config['namespace'] ? "namespace {$config['namespace']};\n\n" : '',
+ 'use_statements' => $this->generateUseStatements($config),
+ 'class_name' => $config['class'],
+ 'command_name' => $config['name'],
+ 'command_description' => "Description for {$config['name']} command",
+ 'arguments' => $this->generateArguments($config['args'])
+ ]);
+
+ // Ensure output directory exists
+ $outputDir = $config['path'];
+ if (!is_dir($outputDir)) {
+ mkdir($outputDir, 0755, true);
+ }
+
+ // Generate file path
+ $fileName = $config['class'] . '.php';
+ $filePath = rtrim($outputDir, '/') . '/' . $fileName;
+
+ // Check if file exists
+ if (file_exists($filePath)) {
+ $overwrite = $this->confirm("File $filePath already exists. Overwrite?");
+ if (!$overwrite) {
+ throw new \Exception("File already exists and overwrite was declined.");
+ }
+ }
+
+ // Write file
+ file_put_contents($filePath, $content);
+
+ return $filePath;
+ }
+
+ /**
+ * Generate use statements.
+ */
+ private function generateUseStatements(array $config): string {
+ $uses = [
+ 'use WebFiori\Cli\Command;',
+ 'use WebFiori\Cli\ArgumentOption;'
+ ];
+
+ if ($config['interactive'] || $config['template'] === 'interactive') {
+ $uses[] = 'use WebFiori\Cli\InputValidator;';
+ }
+
+ return implode("\n", $uses);
+ }
+
+ /**
+ * Generate command arguments array.
+ */
+ private function generateArguments(array $args): string {
+ if (empty($args)) {
+ return '[]';
+ }
+
+ $argStrings = [];
+ foreach ($args as $arg) {
+ $arg = trim($arg);
+ $argName = '--' . strtolower(str_replace(' ', '-', $arg));
+ $argStrings[] = " '$argName' => [\n" .
+ " ArgumentOption::DESCRIPTION => 'Description for $arg',\n" .
+ " ArgumentOption::OPTIONAL => true\n" .
+ " ]";
+ }
+
+ return "[\n" . implode(",\n", $argStrings) . "\n ]";
+ }
+}
diff --git a/WebFiori/Cli/Table/TableData.php b/WebFiori/Cli/Table/TableData.php
index 631355f..c6d14b0 100644
--- a/WebFiori/Cli/Table/TableData.php
+++ b/WebFiori/Cli/Table/TableData.php
@@ -95,7 +95,7 @@ public static function fromCsv(string $csv, bool $hasHeaders = true, string $del
continue;
}
- $row = str_getcsv($line, $delimiter);
+ $row = str_getcsv($line, $delimiter, '"', '\\');
if ($hasHeaders && $headers === null) {
$headers = $row;
diff --git a/WebFiori/Cli/Templates/TemplateManager.php b/WebFiori/Cli/Templates/TemplateManager.php
new file mode 100644
index 0000000..aa757f3
--- /dev/null
+++ b/WebFiori/Cli/Templates/TemplateManager.php
@@ -0,0 +1,55 @@
+templatesPath = $templatesPath ?? __DIR__ . '/stubs';
+ }
+
+ /**
+ * Get template content by name.
+ */
+ public function getTemplate(string $name): string {
+ $templateFile = $this->templatesPath . '/' . $name . '.stub';
+
+ if (!file_exists($templateFile)) {
+ throw new \InvalidArgumentException("Template '$name' not found at: $templateFile");
+ }
+
+ return file_get_contents($templateFile);
+ }
+
+ /**
+ * Get available templates.
+ */
+ public function getAvailableTemplates(): array {
+ $templates = [];
+ $files = glob($this->templatesPath . '/*.stub');
+
+ foreach ($files as $file) {
+ $templates[] = basename($file, '.stub');
+ }
+
+ return $templates;
+ }
+
+ /**
+ * Process template with variables.
+ */
+ public function processTemplate(string $template, array $variables): string {
+ $content = $this->getTemplate($template);
+
+ foreach ($variables as $key => $value) {
+ $content = str_replace('{{' . $key . '}}', $value, $content);
+ }
+
+ return $content;
+ }
+}
diff --git a/WebFiori/Cli/Templates/stubs/basic.stub b/WebFiori/Cli/Templates/stubs/basic.stub
new file mode 100644
index 0000000..85e8b45
--- /dev/null
+++ b/WebFiori/Cli/Templates/stubs/basic.stub
@@ -0,0 +1,23 @@
+println('🚀 Executing {{command_name}} command...');
+
+ // TODO: Implement your command logic here
+
+ $this->success('✅ Command completed successfully!');
+ return 0;
+ }
+}
diff --git a/WebFiori/Cli/Templates/stubs/crud.stub b/WebFiori/Cli/Templates/stubs/crud.stub
new file mode 100644
index 0000000..52c8b94
--- /dev/null
+++ b/WebFiori/Cli/Templates/stubs/crud.stub
@@ -0,0 +1,73 @@
+ [
+ ArgumentOption::DESCRIPTION => 'CRUD action to perform',
+ ArgumentOption::OPTIONAL => true,
+ ArgumentOption::VALUES => ['create', 'read', 'show', 'update', 'delete', 'list'],
+ ArgumentOption::DEFAULT => 'list'
+ ]
+ ]), '{{command_description}}');
+ }
+
+ public function exec(): int {
+ $action = $this->getArgValue('--action') ?? 'list';
+
+ switch ($action) {
+ case 'create':
+ return $this->createRecord();
+ case 'read':
+ case 'show':
+ return $this->showRecord();
+ case 'update':
+ return $this->updateRecord();
+ case 'delete':
+ return $this->deleteRecord();
+ case 'list':
+ default:
+ return $this->listRecords();
+ }
+ }
+
+ private function createRecord(): int {
+ $this->info('Creating new record...');
+ // TODO: Implement create logic
+ $this->success('✅ Record created successfully!');
+ return 0;
+ }
+
+ private function showRecord(): int {
+ $this->info('Showing record...');
+ // TODO: Implement show logic
+ return 0;
+ }
+
+ private function updateRecord(): int {
+ $this->info('Updating record...');
+ // TODO: Implement update logic
+ $this->success('✅ Record updated successfully!');
+ return 0;
+ }
+
+ private function deleteRecord(): int {
+ $this->info('Deleting record...');
+ // TODO: Implement delete logic
+ $this->success('✅ Record deleted successfully!');
+ return 0;
+ }
+
+ private function listRecords(): int {
+ $this->info('Listing records...');
+ // TODO: Implement list logic
+ return 0;
+ }
+}
diff --git a/WebFiori/Cli/Templates/stubs/interactive.stub b/WebFiori/Cli/Templates/stubs/interactive.stub
new file mode 100644
index 0000000..22cb7b2
--- /dev/null
+++ b/WebFiori/Cli/Templates/stubs/interactive.stub
@@ -0,0 +1,43 @@
+println('🔧 Interactive Command Setup');
+ $this->println('==========================');
+
+ // Get user input
+ $name = $this->getInput('Enter name: ');
+ $email = $this->getInput('Enter email: ', null, new InputValidator(function($email) {
+ return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
+ }, 'Please enter a valid email address!'));
+
+ // Confirm action
+ if ($this->confirm('Proceed with the operation?')) {
+ $this->processData($name, $email);
+ $this->success('✅ Operation completed successfully!');
+ } else {
+ $this->info('Operation cancelled.');
+ }
+
+ return 0;
+ }
+
+ /**
+ * Process the collected data.
+ */
+ private function processData(string $name, string $email): void {
+ // TODO: Implement data processing logic
+ $this->info("Processing data for: $name ($email)");
+ }
+}
diff --git a/examples/01-basic-hello-world/README.md b/examples/01-basic-hello-world/README.md
index 1a03ffa..fed2549 100644
--- a/examples/01-basic-hello-world/README.md
+++ b/examples/01-basic-hello-world/README.md
@@ -128,3 +128,18 @@ class HelloCommand extends Command {
```
This example serves as the foundation for understanding WebFiori CLI basics before moving to more advanced features.
+
+## Related Examples
+
+### Next Steps
+- **[02-arguments-and-options](../02-arguments-and-options/)** - Learn advanced argument handling and validation
+- **[03-user-input](../03-user-input/)** - Add interactive user input to your commands
+- **[04-output-formatting](../04-output-formatting/)** - Enhance output with colors and formatting
+
+### Advanced Features
+- **[10-multi-command-app](../10-multi-command-app/)** - Build complete CLI applications
+- **[12-command-scaffolding](../12-command-scaffolding/)** - Generate commands automatically
+
+### Similar Concepts
+- **[05-interactive-commands](../05-interactive-commands/)** - Interactive command workflows
+- **[11-masked-input](../11-masked-input/)** - Secure input handling
diff --git a/examples/02-arguments-and-options/README.md b/examples/02-arguments-and-options/README.md
index 0c3b8a9..c7358a1 100644
--- a/examples/02-arguments-and-options/README.md
+++ b/examples/02-arguments-and-options/README.md
@@ -350,3 +350,22 @@ class UserProfileCommand extends Command {
```
This example demonstrates advanced CLI application development with proper validation, error handling, and user experience design.
+
+## Related Examples
+
+### Prerequisites
+- **[01-basic-hello-world](../01-basic-hello-world/)** - Start here for basic command concepts
+
+### Next Steps
+- **[03-user-input](../03-user-input/)** - Interactive input and validation
+- **[11-masked-input](../11-masked-input/)** - Secure input for sensitive data
+- **[04-output-formatting](../04-output-formatting/)** - Enhanced output styling
+
+### Advanced Applications
+- **[10-multi-command-app](../10-multi-command-app/)** - Complete CLI applications
+- **[09-database-ops](../09-database-ops/)** - Database operations with validation
+- **[12-command-scaffolding](../12-command-scaffolding/)** - Generate commands with arguments
+
+### Similar Concepts
+- **[05-interactive-commands](../05-interactive-commands/)** - Menu-driven interfaces
+- **[08-file-processing](../08-file-processing/)** - File operations with validation
diff --git a/examples/03-user-input/README.md b/examples/03-user-input/README.md
index 72a9cad..b30caad 100644
--- a/examples/03-user-input/README.md
+++ b/examples/03-user-input/README.md
@@ -373,3 +373,25 @@ private function collectBasicInfo() {
- **User Experience**: Rich formatting with emojis and clear section divisions
This example demonstrates advanced user input handling suitable for complex CLI applications requiring data collection and validation.
+
+## Related Examples
+
+### Prerequisites
+- **[01-basic-hello-world](../01-basic-hello-world/)** - Basic command structure
+- **[02-arguments-and-options](../02-arguments-and-options/)** - Argument handling and validation
+
+### Enhanced Input Methods
+- **[11-masked-input](../11-masked-input/)** - Secure input for passwords and sensitive data
+- **[05-interactive-commands](../05-interactive-commands/)** - Menu-driven interactive workflows
+
+### Output Enhancement
+- **[04-output-formatting](../04-output-formatting/)** - Colors, styles, and formatting
+- **[06-table-display](../06-table-display/)** - Structured data presentation
+- **[07-progress-bars](../07-progress-bars/)** - Visual progress indicators
+
+### Complete Applications
+- **[10-multi-command-app](../10-multi-command-app/)** - Full CLI applications with user management
+- **[09-database-ops](../09-database-ops/)** - Database operations with user input
+
+### Development Tools
+- **[12-command-scaffolding](../12-command-scaffolding/)** - Generate interactive commands automatically
diff --git a/examples/04-output-formatting/README.md b/examples/04-output-formatting/README.md
index b57f0c7..a9038e1 100644
--- a/examples/04-output-formatting/README.md
+++ b/examples/04-output-formatting/README.md
@@ -562,3 +562,25 @@ private function showSpinnerAnimation(): void {
```
This example demonstrates professional CLI output formatting suitable for creating visually appealing and user-friendly command-line applications.
+
+## Related Examples
+
+### Prerequisites
+- **[01-basic-hello-world](../01-basic-hello-world/)** - Basic command and output concepts
+
+### Enhanced Output Features
+- **[06-table-display](../06-table-display/)** - Structured data in formatted tables
+- **[07-progress-bars](../07-progress-bars/)** - Professional progress indicators
+- **[05-interactive-commands](../05-interactive-commands/)** - Interactive menus with formatting
+
+### Input with Formatting
+- **[03-user-input](../03-user-input/)** - User input with formatted prompts
+- **[11-masked-input](../11-masked-input/)** - Secure input with visual feedback
+
+### Complete Applications
+- **[10-multi-command-app](../10-multi-command-app/)** - Full applications with consistent formatting
+- **[09-database-ops](../09-database-ops/)** - Database operations with formatted output
+- **[08-file-processing](../08-file-processing/)** - File operations with status formatting
+
+### Development Tools
+- **[12-command-scaffolding](../12-command-scaffolding/)** - Generate commands with formatting templates
diff --git a/examples/05-interactive-commands/README.md b/examples/05-interactive-commands/README.md
index a41aef9..bce5db7 100644
--- a/examples/05-interactive-commands/README.md
+++ b/examples/05-interactive-commands/README.md
@@ -316,3 +316,26 @@ private function showContextHelp(): void {
- **[03-user-input](../03-user-input/)**: Input validation and handling
- **[04-output-formatting](../04-output-formatting/)**: ANSI colors and formatting
+## Related Examples
+
+### Prerequisites
+- **[01-basic-hello-world](../01-basic-hello-world/)** - Basic command structure
+- **[03-user-input](../03-user-input/)** - User input and validation fundamentals
+- **[04-output-formatting](../04-output-formatting/)** - ANSI colors and formatting
+
+### Enhanced Interactive Features
+- **[11-masked-input](../11-masked-input/)** - Secure input for sensitive operations
+- **[02-arguments-and-options](../02-arguments-and-options/)** - Command arguments and options
+
+### Visual Enhancements
+- **[06-table-display](../06-table-display/)** - Display data in formatted tables
+- **[07-progress-bars](../07-progress-bars/)** - Visual progress indicators
+
+### Complete Applications
+- **[10-multi-command-app](../10-multi-command-app/)** - Full CLI applications with menus
+- **[09-database-ops](../09-database-ops/)** - Database management with interactive menus
+- **[08-file-processing](../08-file-processing/)** - File operations with user interaction
+
+### Development Tools
+- **[12-command-scaffolding](../12-command-scaffolding/)** - Generate interactive commands automatically
+
diff --git a/examples/06-table-display/README.md b/examples/06-table-display/README.md
index 14c6b63..b7d2cce 100644
--- a/examples/06-table-display/README.md
+++ b/examples/06-table-display/README.md
@@ -298,3 +298,25 @@ php main.php table-demo --help
- **[05-interactive-commands](../05-interactive-commands/)** - Interactive menu systems
- **[08-file-processing](../08-file-processing/)** - File data processing
- **[10-multi-command-app](../10-multi-command-app/)** - Complete CLI applications
+
+## Related Examples
+
+### Prerequisites
+- **[01-basic-hello-world](../01-basic-hello-world/)** - Basic command structure
+- **[04-output-formatting](../04-output-formatting/)** - Colors and formatting basics
+
+### Enhanced Display Features
+- **[07-progress-bars](../07-progress-bars/)** - Visual progress indicators
+- **[05-interactive-commands](../05-interactive-commands/)** - Interactive menus with tables
+
+### Data Sources
+- **[08-file-processing](../08-file-processing/)** - Process files and display results in tables
+- **[09-database-ops](../09-database-ops/)** - Database queries with table output
+- **[03-user-input](../03-user-input/)** - Collect data and display in tables
+
+### Complete Applications
+- **[10-multi-command-app](../10-multi-command-app/)** - Full applications with data display
+- **[02-arguments-and-options](../02-arguments-and-options/)** - Commands with formatted output
+
+### Development Tools
+- **[12-command-scaffolding](../12-command-scaffolding/)** - Generate commands with table display
diff --git a/examples/07-progress-bars/README.md b/examples/07-progress-bars/README.md
index 72769e2..1b3b6a4 100644
--- a/examples/07-progress-bars/README.md
+++ b/examples/07-progress-bars/README.md
@@ -302,3 +302,24 @@ php main.php progress-demo --style=custom --items=20 --delay=50
- **[06-table-display](../06-table-display/)** - Data presentation techniques
- **[08-file-processing](../08-file-processing/)** - File operations with progress
- **[10-multi-command-app](../10-multi-command-app/)** - Complete CLI applications
+## Related Examples
+
+### Prerequisites
+- **[01-basic-hello-world](../01-basic-hello-world/)** - Basic command structure
+- **[04-output-formatting](../04-output-formatting/)** - Colors and formatting
+
+### Visual Enhancement
+- **[06-table-display](../06-table-display/)** - Structured data display
+- **[05-interactive-commands](../05-interactive-commands/)** - Interactive workflows with progress
+
+### Long-Running Operations
+- **[08-file-processing](../08-file-processing/)** - File operations with progress tracking
+- **[09-database-ops](../09-database-ops/)** - Database operations with progress indicators
+- **[03-user-input](../03-user-input/)** - Multi-step processes with progress
+
+### Complete Applications
+- **[10-multi-command-app](../10-multi-command-app/)** - Full applications with progress feedback
+- **[02-arguments-and-options](../02-arguments-and-options/)** - Commands with progress reporting
+
+### Development Tools
+- **[12-command-scaffolding](../12-command-scaffolding/)** - Generate commands with progress bars
diff --git a/examples/08-file-processing/README.md b/examples/08-file-processing/README.md
index c530980..d3b0f60 100644
--- a/examples/08-file-processing/README.md
+++ b/examples/08-file-processing/README.md
@@ -274,3 +274,25 @@ class FileProcessCommand extends Command {
- **[04-output-formatting](../04-output-formatting/)** - Text formatting and colors
- **[07-progress-bars](../07-progress-bars/)** - Progress tracking for file operations
- **[10-multi-command-app](../10-multi-command-app/)** - Complete CLI applications
+## Related Examples
+
+### Prerequisites
+- **[01-basic-hello-world](../01-basic-hello-world/)** - Basic command structure
+- **[02-arguments-and-options](../02-arguments-and-options/)** - File path arguments and validation
+
+### Enhanced Processing
+- **[07-progress-bars](../07-progress-bars/)** - Visual progress for file operations
+- **[06-table-display](../06-table-display/)** - Display file data in formatted tables
+- **[04-output-formatting](../04-output-formatting/)** - Formatted status messages
+
+### User Interaction
+- **[03-user-input](../03-user-input/)** - Interactive file selection
+- **[05-interactive-commands](../05-interactive-commands/)** - File operation menus
+- **[11-masked-input](../11-masked-input/)** - Secure file path input
+
+### Complete Applications
+- **[10-multi-command-app](../10-multi-command-app/)** - Full applications with file management
+- **[09-database-ops](../09-database-ops/)** - Database import/export from files
+
+### Development Tools
+- **[12-command-scaffolding](../12-command-scaffolding/)** - Generate file processing commands
diff --git a/examples/09-database-ops/README.md b/examples/09-database-ops/README.md
index 6788a8c..a33f8b5 100644
--- a/examples/09-database-ops/README.md
+++ b/examples/09-database-ops/README.md
@@ -599,3 +599,25 @@ class QueryOptimizer {
}
}
```
+## Related Examples
+
+### Prerequisites
+- **[01-basic-hello-world](../01-basic-hello-world/)** - Basic command structure
+- **[02-arguments-and-options](../02-arguments-and-options/)** - Database connection arguments
+
+### Enhanced Features
+- **[06-table-display](../06-table-display/)** - Display query results in tables
+- **[07-progress-bars](../07-progress-bars/)** - Progress for long database operations
+- **[05-interactive-commands](../05-interactive-commands/)** - Database management menus
+
+### User Interaction
+- **[03-user-input](../03-user-input/)** - Interactive database configuration
+- **[11-masked-input](../11-masked-input/)** - Secure database password input
+- **[04-output-formatting](../04-output-formatting/)** - Formatted database status
+
+### Data Processing
+- **[08-file-processing](../08-file-processing/)** - Import/export database data
+- **[10-multi-command-app](../10-multi-command-app/)** - Complete database CLI applications
+
+### Development Tools
+- **[12-command-scaffolding](../12-command-scaffolding/)** - Generate database operation commands
diff --git a/examples/10-multi-command-app/README.md b/examples/10-multi-command-app/README.md
index 7b1facd..afb40ad 100644
--- a/examples/10-multi-command-app/README.md
+++ b/examples/10-multi-command-app/README.md
@@ -713,3 +713,23 @@ class ApiClient {
}
}
```
+## Related Examples
+
+### Building Blocks (Start Here)
+- **[01-basic-hello-world](../01-basic-hello-world/)** - Basic command structure
+- **[02-arguments-and-options](../02-arguments-and-options/)** - Command arguments and validation
+- **[03-user-input](../03-user-input/)** - User input and interaction
+
+### Feature Integration
+- **[04-output-formatting](../04-output-formatting/)** - Professional output styling
+- **[05-interactive-commands](../05-interactive-commands/)** - Menu-driven interfaces
+- **[06-table-display](../06-table-display/)** - Data presentation in tables
+- **[07-progress-bars](../07-progress-bars/)** - Visual progress feedback
+- **[11-masked-input](../11-masked-input/)** - Secure input handling
+
+### Specialized Operations
+- **[08-file-processing](../08-file-processing/)** - File management commands
+- **[09-database-ops](../09-database-ops/)** - Database operation commands
+
+### Development Tools
+- **[12-command-scaffolding](../12-command-scaffolding/)** - Generate commands for your application
diff --git a/examples/11-masked-input/README.md b/examples/11-masked-input/README.md
index 8bb8c7e..92bf0cb 100644
--- a/examples/11-masked-input/README.md
+++ b/examples/11-masked-input/README.md
@@ -163,3 +163,25 @@ $this->assertContains('Password received: secret123', $output);
---
**Ready to secure your CLI applications?** Try the different demo modes to see masked input in action!
+## Related Examples
+
+### Prerequisites
+- **[01-basic-hello-world](../01-basic-hello-world/)** - Basic command structure
+- **[03-user-input](../03-user-input/)** - User input fundamentals
+
+### Enhanced Input Methods
+- **[02-arguments-and-options](../02-arguments-and-options/)** - Command arguments with validation
+- **[05-interactive-commands](../05-interactive-commands/)** - Interactive menus and workflows
+
+### Visual Enhancement
+- **[04-output-formatting](../04-output-formatting/)** - Colors and formatting for prompts
+- **[06-table-display](../06-table-display/)** - Display collected data in tables
+- **[07-progress-bars](../07-progress-bars/)** - Progress indicators for data processing
+
+### Complete Applications
+- **[10-multi-command-app](../10-multi-command-app/)** - Full applications with secure authentication
+- **[09-database-ops](../09-database-ops/)** - Database operations with secure credentials
+- **[08-file-processing](../08-file-processing/)** - File operations with secure paths
+
+### Development Tools
+- **[12-command-scaffolding](../12-command-scaffolding/)** - Generate commands with masked input
diff --git a/examples/12-command-scaffolding/README.md b/examples/12-command-scaffolding/README.md
new file mode 100644
index 0000000..09d94ca
--- /dev/null
+++ b/examples/12-command-scaffolding/README.md
@@ -0,0 +1,302 @@
+# Command Scaffolding Tools
+
+This example demonstrates the **command scaffolding functionality** in WebFiori CLI, which allows developers to quickly generate new command classes with proper structure, documentation, and templates.
+
+## Features
+
+- **Multiple Templates**: Basic, Interactive, CRUD, and File Processor templates
+- **Smart Naming**: Automatic class name generation from command names
+- **Namespace Support**: Generate commands with custom namespaces
+- **Argument Generation**: Automatically create command arguments
+- **Validation**: Input validation for command and class names
+- **Overwrite Protection**: Confirmation prompts for existing files
+
+## Running the Example
+
+### Basic Command Generation
+```bash
+# Generate a basic command
+php main.php make:command --name=hello-world
+
+# Generate with custom class name
+php main.php make:command --name=user:create --class=CreateUserCommand
+
+# Generate with namespace
+php main.php make:command --name=process-data --namespace="App\\Commands"
+```
+
+### Template-Based Generation
+```bash
+# Interactive command template
+php main.php make:command --name=setup-wizard --template=interactive
+
+# CRUD operations template
+php main.php make:command --name=user-manager --template=crud
+
+# File processor template
+php main.php make:command --name=file-converter --template=file-processor
+```
+
+### Advanced Options
+```bash
+# Generate with arguments
+php main.php make:command --name=backup-db --args="database,output-path,compress"
+
+# Custom output directory
+php main.php make:command --name=deploy --path=src/Commands
+
+# All options combined
+php main.php make:command \
+ --name=api:sync \
+ --class=ApiSyncCommand \
+ --namespace="MyApp\\Commands" \
+ --template=interactive \
+ --args="endpoint,token,timeout" \
+ --path=app/Commands
+```
+
+## Available Templates
+
+### 1. Basic Template
+Simple command structure with minimal boilerplate:
+```php
+class HelloWorldCommand extends Command {
+ public function __construct() {
+ parent::__construct('hello-world', [], 'Description');
+ }
+
+ public function exec(): int {
+ $this->println('🚀 Executing hello-world command...');
+ // TODO: Implement your command logic here
+ $this->success('✅ Command completed successfully!');
+ return 0;
+ }
+}
+```
+
+### 2. Interactive Template
+Command with user input and validation:
+```php
+public function exec(): int {
+ $name = $this->getInput('Enter name: ');
+ $email = $this->getInput('Enter email: ', null, new InputValidator(function($email) {
+ return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
+ }, 'Please enter a valid email address!'));
+
+ if ($this->confirm('Proceed with the operation?')) {
+ $this->processData($name, $email);
+ $this->success('✅ Operation completed successfully!');
+ }
+
+ return 0;
+}
+```
+
+### 3. CRUD Template
+Full CRUD operations structure:
+```php
+public function exec(): int {
+ $action = $this->getArgValue('--action') ?? 'list';
+
+ switch ($action) {
+ case 'create': return $this->createRecord();
+ case 'read': return $this->showRecord();
+ case 'update': return $this->updateRecord();
+ case 'delete': return $this->deleteRecord();
+ case 'list':
+ default: return $this->listRecords();
+ }
+}
+```
+
+### 4. File Processor Template
+File processing with error handling:
+```php
+public function exec(): int {
+ $inputFile = $this->getArgValue('--input');
+
+ if (!$inputFile || !file_exists($inputFile)) {
+ $this->error('Input file is required and must exist!');
+ return 1;
+ }
+
+ try {
+ $this->processFile($inputFile, $this->getArgValue('--output'));
+ $this->success('✅ File processed successfully!');
+ return 0;
+ } catch (\Exception $e) {
+ $this->error('❌ Error: ' . $e->getMessage());
+ return 1;
+ }
+}
+```
+
+## Generated Command Structure
+
+All generated commands include:
+
+### Proper Documentation
+```php
+/**
+ * CommandName - Generated CLI command.
+ *
+ * Description for command-name command
+ */
+class CommandNameCommand extends Command {
+```
+
+### Constructor with Arguments
+```php
+public function __construct() {
+ parent::__construct('command-name', [
+ '--input-file' => [
+ ArgumentOption::DESCRIPTION => 'Description for input file',
+ ArgumentOption::OPTIONAL => true
+ ]
+ ], 'Command description');
+}
+```
+
+### Structured Exec Method
+```php
+public function exec(): int {
+ // Template-specific implementation
+ return 0;
+}
+```
+
+### Additional Helper Methods
+Template-specific helper methods for common operations.
+
+## Smart Naming Conventions
+
+The scaffolding tool automatically converts command names to proper class names:
+
+| Command Name | Generated Class Name |
+|--------------|---------------------|
+| `hello-world` | `HelloWorldCommand` |
+| `user:create` | `UserCreateCommand` |
+| `api_sync` | `ApiSyncCommand` |
+| `process-data-file` | `ProcessDataFileCommand` |
+
+## Validation Features
+
+### Command Name Validation
+- Must start with a letter
+- Can contain lowercase letters, numbers, hyphens, colons, underscores
+- Examples: `hello`, `user:create`, `process-data`
+
+### Class Name Validation
+- Must be valid PHP class name (PascalCase)
+- Automatically generated if not provided
+- Examples: `HelloCommand`, `UserCreateCommand`
+
+### File Overwrite Protection
+```bash
+$ php main.php make:command --name=existing-command
+File /path/to/ExistingCommand.php already exists. Overwrite? (y/n): n
+❌ Failed to generate command: File already exists and overwrite was declined.
+```
+
+## Integration with Your Application
+
+After generating commands, integrate them into your application:
+
+### 1. Register the Command
+```php
+use App\Commands\GeneratedCommand;
+
+$runner = new Runner();
+$runner->register(new GeneratedCommand());
+```
+
+### 2. Implement Logic
+Edit the generated `exec()` method to add your specific functionality.
+
+### 3. Add Tests
+Create unit tests for your generated commands using `CommandTestCase`.
+
+## Best Practices
+
+### Naming Conventions
+- Use kebab-case for command names: `user-create`, `data-export`
+- Use namespaces for organization: `user:create`, `db:migrate`
+- Keep names descriptive but concise
+
+### Template Selection
+- **Basic**: Simple commands with minimal logic
+- **Interactive**: Commands requiring user input
+- **CRUD**: Data management commands
+- **File Processor**: File manipulation commands
+
+### Organization
+```
+app/
+├── Commands/
+│ ├── User/
+│ │ ├── CreateUserCommand.php
+│ │ └── DeleteUserCommand.php
+│ ├── Database/
+│ │ ├── MigrateCommand.php
+│ │ └── SeedCommand.php
+│ └── File/
+│ ├── ProcessCommand.php
+│ └── ConvertCommand.php
+```
+
+## Example Workflow
+
+### 1. Generate User Management Commands
+```bash
+# Create user command
+php main.php make:command --name=user:create --template=interactive --namespace="App\\Commands\\User"
+
+# List users command
+php main.php make:command --name=user:list --template=crud --namespace="App\\Commands\\User"
+
+# Delete user command
+php main.php make:command --name=user:delete --namespace="App\\Commands\\User"
+```
+
+### 2. Generate File Processing Commands
+```bash
+# CSV processor
+php main.php make:command --name=csv:process --template=file-processor --args="input,output,delimiter"
+
+# Image converter
+php main.php make:command --name=image:convert --template=file-processor --args="input,format,quality"
+```
+
+### 3. Generate API Commands
+```bash
+# API sync command
+php main.php make:command --name=api:sync --template=interactive --args="endpoint,token"
+
+# Data export command
+php main.php make:command --name=data:export --args="format,output,filter"
+```
+
+---
+
+**Ready to boost your development speed?** Use the scaffolding tools to generate well-structured commands in seconds!
+## Related Examples
+
+### Generated Command Examples
+- **[01-basic-hello-world](../01-basic-hello-world/)** - Basic template structure
+- **[02-arguments-and-options](../02-arguments-and-options/)** - Commands with arguments (CRUD template)
+- **[03-user-input](../03-user-input/)** - Interactive commands (Interactive template)
+- **[08-file-processing](../08-file-processing/)** - File operations (File Processor template)
+
+### Enhanced Features for Generated Commands
+- **[04-output-formatting](../04-output-formatting/)** - Add formatting to generated commands
+- **[05-interactive-commands](../05-interactive-commands/)** - Interactive workflows
+- **[06-table-display](../06-table-display/)** - Data display in generated commands
+- **[07-progress-bars](../07-progress-bars/)** - Progress indicators
+- **[11-masked-input](../11-masked-input/)** - Secure input in generated commands
+
+### Complete Applications
+- **[10-multi-command-app](../10-multi-command-app/)** - Applications built with scaffolded commands
+- **[09-database-ops](../09-database-ops/)** - Database commands (perfect for CRUD template)
+
+### Development Workflow
+Use this scaffolding tool to quickly generate commands for any of the above examples!
diff --git a/examples/12-command-scaffolding/main.php b/examples/12-command-scaffolding/main.php
new file mode 100644
index 0000000..f568443
--- /dev/null
+++ b/examples/12-command-scaffolding/main.php
@@ -0,0 +1,23 @@
+register(new MakeCommand());
+
+// Start the application
+exit($runner->start());
diff --git a/tests/WebFiori/Tests/Cli/MakeCommandTest.php b/tests/WebFiori/Tests/Cli/MakeCommandTest.php
new file mode 100644
index 0000000..7ce555b
--- /dev/null
+++ b/tests/WebFiori/Tests/Cli/MakeCommandTest.php
@@ -0,0 +1,236 @@
+testOutputDir)) {
+ $this->removeDirectory($this->testOutputDir);
+ }
+ }
+
+ protected function tearDown(): void {
+ parent::tearDown();
+ // Clean up test directory
+ if (is_dir($this->testOutputDir)) {
+ $this->removeDirectory($this->testOutputDir);
+ }
+ }
+
+ /**
+ * Test basic command generation.
+ *
+ * @test
+ */
+ public function testBasicCommandGeneration() {
+ $command = new MakeCommand();
+
+ $output = $this->executeSingleCommand($command, [
+ '--name' => 'test-command',
+ '--class' => 'TestCommand',
+ '--path' => $this->testOutputDir
+ ], ['', 'y']); // Empty namespace, then confirm overwrite if needed
+
+ // Check for success message (flexible matching)
+ $found = false;
+ foreach ($output as $line) {
+ if (strpos($line, 'Command generated successfully!') !== false) {
+ $found = true;
+ break;
+ }
+ }
+ $this->assertTrue($found, 'Success message not found in output');
+ $this->assertEquals(0, $this->getExitCode());
+
+ // Check if file was created
+ $expectedFile = $this->testOutputDir . '/TestCommand.php';
+ $this->assertTrue(file_exists($expectedFile), "Command file should be created");
+
+ // Check file content
+ $content = file_get_contents($expectedFile);
+ $this->assertStringContainsString('class TestCommand extends Command', $content);
+ $this->assertStringContainsString("'test-command'", $content);
+ }
+
+ /**
+ * Test command generation with namespace.
+ *
+ * @test
+ */
+ public function testCommandGenerationWithNamespace() {
+ $command = new MakeCommand();
+
+ $output = $this->executeSingleCommand($command, [
+ '--name' => 'user:create',
+ '--namespace' => 'App\\Commands',
+ '--path' => $this->testOutputDir
+ ]);
+
+ $this->assertEquals(0, $this->getExitCode());
+
+ $expectedFile = $this->testOutputDir . '/UserCreateCommand.php';
+ $this->assertTrue(file_exists($expectedFile));
+
+ $content = file_get_contents($expectedFile);
+ $this->assertStringContainsString('namespace App\\Commands;', $content);
+ $this->assertStringContainsString('class UserCreateCommand extends Command', $content);
+ }
+
+ /**
+ * Test interactive template generation.
+ *
+ * @test
+ */
+ public function testInteractiveTemplate() {
+ $command = new MakeCommand();
+
+ $output = $this->executeSingleCommand($command, [
+ '--name' => 'setup-wizard',
+ '--template' => 'interactive',
+ '--path' => $this->testOutputDir
+ ], ['']); // Empty namespace input
+
+ $this->assertEquals(0, $this->getExitCode());
+
+ $expectedFile = $this->testOutputDir . '/SetupWizardCommand.php';
+ $content = file_get_contents($expectedFile);
+
+ $this->assertStringContainsString('InputValidator', $content);
+ $this->assertStringContainsString('getInput(', $content);
+ $this->assertStringContainsString('confirm(', $content);
+ }
+
+ /**
+ * Test CRUD template generation.
+ *
+ * @test
+ */
+ public function testCrudTemplate() {
+ $command = new MakeCommand();
+
+ $output = $this->executeSingleCommand($command, [
+ '--name' => 'user-manager',
+ '--template' => 'crud',
+ '--path' => $this->testOutputDir
+ ], ['']); // Empty namespace input
+
+ $this->assertEquals(0, $this->getExitCode());
+
+ $expectedFile = $this->testOutputDir . '/UserManagerCommand.php';
+ $content = file_get_contents($expectedFile);
+
+ $this->assertStringContainsString('createRecord()', $content);
+ $this->assertStringContainsString('updateRecord()', $content);
+ $this->assertStringContainsString('deleteRecord()', $content);
+ $this->assertStringContainsString('listRecords()', $content);
+ }
+
+ /**
+ * Test command generation with arguments.
+ *
+ * @test
+ */
+ public function testCommandWithArguments() {
+ $command = new MakeCommand();
+
+ $output = $this->executeSingleCommand($command, [
+ '--name' => 'process-data',
+ '--args' => 'input file,output format,verbose mode',
+ '--path' => $this->testOutputDir
+ ], ['']); // Empty namespace input
+
+ $this->assertEquals(0, $this->getExitCode());
+
+ $expectedFile = $this->testOutputDir . '/ProcessDataCommand.php';
+ $content = file_get_contents($expectedFile);
+
+ $this->assertStringContainsString('--input-file', $content);
+ $this->assertStringContainsString('--output-format', $content);
+ $this->assertStringContainsString('--verbose-mode', $content);
+ }
+
+ /**
+ * Test invalid command name validation.
+ *
+ * @test
+ */
+ public function testInvalidCommandName() {
+ $command = new MakeCommand();
+
+ $output = $this->executeSingleCommand($command, [
+ '--name' => 'Invalid Command Name!',
+ '--path' => $this->testOutputDir
+ ], ['']); // Empty namespace input
+
+ $this->assertEquals(1, $this->getExitCode());
+ // Check for validation error message (flexible matching)
+ $found = false;
+ foreach ($output as $line) {
+ if (strpos($line, 'Command name must start with a letter') !== false) {
+ $found = true;
+ break;
+ }
+ }
+ $this->assertTrue($found, 'Validation error message not found in output');
+ }
+
+ /**
+ * Test file overwrite confirmation.
+ *
+ * @test
+ */
+ public function testFileOverwriteConfirmation() {
+ $command = new MakeCommand();
+
+ // Create file first
+ $this->executeSingleCommand($command, [
+ '--name' => 'existing-command',
+ '--path' => $this->testOutputDir
+ ], ['']); // Empty namespace input
+
+ // Try to create again with 'no' confirmation
+ $output = $this->executeSingleCommand($command, [
+ '--name' => 'existing-command',
+ '--path' => $this->testOutputDir
+ ], ['', 'n']); // Empty namespace, then 'no' to overwrite
+
+ $this->assertEquals(1, $this->getExitCode());
+ // Check for overwrite declined message (flexible matching)
+ $found = false;
+ foreach ($output as $line) {
+ if (strpos($line, 'File already exists and overwrite was declined') !== false) {
+ $found = true;
+ break;
+ }
+ }
+ $this->assertTrue($found, 'Overwrite declined message not found in output');
+ }
+
+ /**
+ * Helper method to remove directory recursively.
+ */
+ private function removeDirectory(string $dir): void {
+ if (!is_dir($dir)) {
+ return;
+ }
+
+ $files = array_diff(scandir($dir), ['.', '..']);
+ foreach ($files as $file) {
+ $path = $dir . '/' . $file;
+ is_dir($path) ? $this->removeDirectory($path) : unlink($path);
+ }
+ rmdir($dir);
+ }
+}