Skip to content

Commit ca74220

Browse files
update
1 parent 968b968 commit ca74220

File tree

8 files changed

+622
-7
lines changed

8 files changed

+622
-7
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@
66
/.env
77
/.env.example
88
/.zencoder
9+
tame
910
composer.lock

Capsule/Artisan.php

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tamedevelopers\Support\Capsule;
6+
7+
use Tamedevelopers\Support\Capsule\Logger;
8+
use Tamedevelopers\Support\Capsule\Manager;
9+
use Tamedevelopers\Support\Capsule\Traits\ArtisanTrait;
10+
11+
/**
12+
* Minimal artisan-like dispatcher for Tamedevelopers Support
13+
*
14+
* Supports Laravel-like syntax:
15+
* php tame <command>[:subcommand] [--flag] [--option=value]
16+
* Examples:
17+
* php tame key:generate
18+
* php tame migrate:fresh --seed --database=mysql
19+
*/
20+
class Artisan
21+
{
22+
use ArtisanTrait;
23+
24+
/**
25+
* Command registry
26+
* @var string
27+
*/
28+
public $cliname;
29+
30+
/**
31+
* Registered commands map
32+
* @var array<string, array{instance?: object, handler?: callable, description: string}>
33+
*/
34+
protected static array $commands = [];
35+
36+
public function __construct()
37+
{
38+
// Ensure environment variables are loaded before accessing them
39+
Manager::startEnvIFNotStarted();
40+
}
41+
42+
/**
43+
* Register a command by name with description
44+
*
45+
* @param string $name
46+
* @param callable|object $handler Either a callable or a command class instance
47+
* @param string $description Short description for `list`
48+
*/
49+
public function register(string $name, $handler, string $description = ''): void
50+
{
51+
if (\is_object($handler) && !\is_callable($handler)) {
52+
self::$commands[$name] = [
53+
'instance' => $handler,
54+
'description' => $description,
55+
];
56+
return;
57+
}
58+
59+
// Fallback to callable handler registration
60+
self::$commands[$name] = [
61+
'handler' => $handler,
62+
'description' => $description,
63+
];
64+
}
65+
66+
/**
67+
* Handle argv input and dispatch
68+
*/
69+
public function run(array $argv): int
70+
{
71+
// In PHP CLI, $argv[0] is the script name (tame), so command starts at index 1
72+
$commandInput = $argv[1] ?? 'list';
73+
$rawArgs = array_slice($argv, 2);
74+
75+
if ($commandInput === 'list') {
76+
$this->renderList();
77+
return 0;
78+
}
79+
80+
// Parse base and optional subcommand: e.g., key:generate -> [key, generate]
81+
[$base, $sub] = $this->splitCommand($commandInput);
82+
83+
if (!isset(self::$commands[$base])) {
84+
Logger::error("Command \"{$commandInput}\" is not defined.\n\n");
85+
return 1;
86+
}
87+
88+
$entry = self::$commands[$base];
89+
90+
// Parse flags/options once and pass where applicable
91+
[$positionals, $options] = $this->parseArgs($rawArgs);
92+
93+
// If registered with a class instance, we support subcommands and flag-to-method routing
94+
if (isset($entry['instance']) && \is_object($entry['instance'])) {
95+
$instance = $entry['instance'];
96+
97+
// Resolve primary method to call
98+
$primaryMethod = $sub ?: 'handle';
99+
if (!method_exists($instance, $primaryMethod)) {
100+
// command instance name
101+
$instanceName = get_class($instance);
102+
Logger::error("Missing method \"{$primaryMethod}()\" in {$instanceName}.");
103+
104+
// Show small hint for available methods on the instance (public only)
105+
$hints = $this->introspectPublicMethods($instance);
106+
107+
if ( !empty($hints)) {
108+
Logger::info("Available methods: {$hints}\n");
109+
}
110+
return 1;
111+
}
112+
113+
$exitCode = (int) ($this->invokeCommandMethod($instance, $primaryMethod, $positionals, $options) ?? 0);
114+
115+
// Route flags as methods on the same instance
116+
$invalidFlags = [];
117+
foreach ($options as $flag => $value) {
118+
$method = $this->optionToMethodName($flag);
119+
// Skip if this flag matches the already-run primary method
120+
if ($method === $primaryMethod) {
121+
continue;
122+
}
123+
if (method_exists($instance, $method)) {
124+
$this->invokeCommandMethod($instance, $method, $positionals, $options, $flag);
125+
} else {
126+
$invalidFlags[] = $flag;
127+
}
128+
}
129+
130+
if (!empty($invalidFlags)) {
131+
Logger::error("Invalid option/method: --" . implode(', --', $invalidFlags) . "\n");
132+
}
133+
134+
return $exitCode;
135+
}
136+
137+
// Fallback: callable handler (no subcommands/flags routing)
138+
if (isset($entry['handler']) && \is_callable($entry['handler'])) {
139+
$handler = $entry['handler'];
140+
return (int) ($handler($rawArgs) ?? 0);
141+
}
142+
143+
Logger::error("Command not properly registered: {$commandInput}\n");
144+
return 1;
145+
}
146+
147+
/**
148+
* Render list of available commands
149+
*/
150+
private function renderList(): void
151+
{
152+
$this->cliname = "{$this->cliname}\n" ?: "Tamedevelopers Support CLI\n";
153+
154+
Logger::helpHeader($this->cliname);
155+
Logger::writeln('<yellow>Usage:</yellow>');
156+
Logger::writeln(' php tame <command> [:option] [arguments]');
157+
Logger::writeln('');
158+
159+
$grouped = $this->buildGroupedCommandList(self::$commands);
160+
161+
// Root commands first
162+
if (isset($grouped['__root'])) {
163+
Logger::helpHeader('Available commands:', 'method');
164+
foreach ($grouped['__root'] as $cmd => $desc) {
165+
// Label with color
166+
$label = Logger::segments([
167+
['text' => $cmd, 'style' => 'green'],
168+
]);
169+
170+
// Pad description to align
171+
$visibleLen = strlen(preg_replace('/<\/?[a-zA-Z0-9_-]+>/', '', ' ' . $label) ?? '');
172+
$spaces = max(1, 35 - $visibleLen);
173+
174+
Logger::writeln(' ' . $label . str_repeat(' ', $spaces) . Logger::segments([
175+
['text' => $desc, 'style' => 'desc'],
176+
]));
177+
}
178+
Logger::writeln('');
179+
unset($grouped['__root']);
180+
}
181+
182+
// Then grouped by base (e.g., auth, cache, migrate:*)
183+
foreach ($grouped as $group => $items) {
184+
Logger::helpHeader($group, 'method');
185+
foreach ($items as $full => $desc) {
186+
[$ns, $method] = explode(':', $full, 2);
187+
// method (yellow), description (white)
188+
Logger::helpItem($ns, $method, null, $desc, 35, false, ['green', 'green']);
189+
}
190+
Logger::writeln('');
191+
}
192+
}
193+
194+
}

Capsule/Dummy/dummyTame.dum

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#!/usr/bin/env php
2+
<?php
3+
4+
// Unix timestamp with microseconds
5+
define('TAME_START', microtime(true));
6+
7+
/*
8+
|--------------------------------------------------------------------------
9+
| Ensure <composer autoload> is registered
10+
|--------------------------------------------------------------------------
11+
|
12+
| This file makes sure that composer's autoloader is loaded before any otherthing
13+
| in this application. It also ensures that all of our dependencies are installed
14+
| and ready to use by the developer.
15+
|
16+
*/
17+
18+
include_once __DIR__ . "/vendor/autoload.php";
19+
20+
/*
21+
|--------------------------------------------------------------------------
22+
| Run The Application CLI <dispatcher>
23+
|--------------------------------------------------------------------------
24+
|
25+
| This file is where your <CLI commands> will be dispatched from
26+
| using the command line, the response sent back to a terminal
27+
| or another output device for the developers.
28+
|
29+
*/
30+
31+
$artisan = new Tamedevelopers\Support\Capsule\Artisan();
32+
33+
exit($artisan->run($argv));

Capsule/Logger.php

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,13 @@ protected static function registerDefaultStyles(ConsoleOutput $output): void
6565

6666
$styles = [
6767
// existing (headers use white text on colored background)
68-
'success' => ['white', 'green', []],
69-
'error' => ['red', 'red', ['bold']],
70-
'info' => ['white', 'cyan', []],
68+
'success' => ['white', 'green', ['bold']],
69+
'error' => ['bright-white', 'red', ['bold']],
70+
'info' => ['bright-white', 'cyan', ['bold']],
71+
72+
// dedicated header tags (avoid conflicts with built-ins)
73+
'success_header' => ['bright-white', 'green', ['bold']],
74+
'info_header' => ['bright-white', 'cyan', ['bold']],
7175

7276
// extras
7377
'yellow' => ['yellow', null, ['bold']],
@@ -142,13 +146,13 @@ public static function segments(array $segments, string $separator = ''): string
142146
* - Old: helpHeader('blog-store', true) => STDERR with default 'title' style
143147
* - New: helpHeader('blog-store', 'green', true)
144148
*/
145-
public static function helpHeader(string $title, $style = 'title', bool $stderr = false): void
149+
public static function helpHeader(string $message, $style = 'title', bool $stderr = false): void
146150
{
147151
if (is_bool($style)) {
148152
$stderr = $style;
149153
$style = 'title';
150154
}
151-
static::writeln("<{$style}>{$title}</{$style}>", $stderr);
155+
static::writeln("<{$style}>{$message}</{$style}>", $stderr);
152156
}
153157

154158
/**

0 commit comments

Comments
 (0)