Skip to content

Commit 1f91e4c

Browse files
Default Tame-Artisan Command added
1 parent f5ad213 commit 1f91e4c

File tree

2 files changed

+105
-56
lines changed

2 files changed

+105
-56
lines changed

Capsule/Artisan.php

Lines changed: 69 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ class Artisan extends CommandHelper
3131
public $cli_name;
3232

3333
/**
34-
* Registered commands map
35-
* @var array<string, array{instance?: object, handler?: callable, description: string}>
34+
* Registered commands map (supports multiple providers per base command)
35+
* @var array<string, array<int, array{instance?: object, handler?: callable, description: string}>>
3636
*/
3737
protected static array $commands = [];
3838

@@ -57,16 +57,22 @@ public function __construct()
5757
*/
5858
public function register(string $name, $handler, string $description = ''): void
5959
{
60+
// Ensure bucket exists for this base name
61+
if (!isset(self::$commands[$name]) || !is_array(self::$commands[$name])) {
62+
self::$commands[$name] = [];
63+
}
64+
65+
// Object instance (class-based command)
6066
if (\is_object($handler) && !\is_callable($handler)) {
61-
self::$commands[$name] = [
67+
self::$commands[$name][] = [
6268
'instance' => $handler,
6369
'description' => $description,
6470
];
6571
return;
6672
}
6773

68-
// Fallback to callable handler registration
69-
self::$commands[$name] = [
74+
// Callable handler registration
75+
self::$commands[$name][] = [
7076
'handler' => $handler,
7177
'description' => $description,
7278
];
@@ -97,63 +103,82 @@ public function run(array $argv): int
97103
return 1;
98104
}
99105

100-
$entry = self::$commands[$base];
106+
// Normalize entries to array of providers for this base command
107+
$entries = self::$commands[$base];
108+
// Normalize: if entries look like a single assoc, wrap into list
109+
if (!is_array($entries) || (is_array($entries) && !isset($entries[0]))) {
110+
$entries = [$entries];
111+
}
101112

102113
// Parse flags/options once and pass where applicable
103114
[$positionals, $options] = $this->parseArgs($rawArgs);
104115

105-
// If registered with a class instance, we support subcommands and flag-to-method routing
106-
if (isset($entry['instance']) && \is_object($entry['instance'])) {
107-
$instance = $entry['instance'];
116+
$exitCode = 0;
117+
$handled = false;
108118

109-
// Resolve primary method to call
110-
$primaryMethod = $sub ?: 'handle';
111-
if (!method_exists($instance, $primaryMethod)) {
112-
// command instance name
113-
$instanceName = get_class($instance);
114-
Logger::error("Missing method \"{$primaryMethod}()\" in {$instanceName}.");
115-
116-
// Show small hint for available methods on the instance (public only)
117-
$hints = $this->introspectPublicMethods($instance);
118-
119-
if ( !empty($hints)) {
120-
Logger::info("Available methods: {$hints}\n");
121-
}
122-
return 1;
123-
}
119+
// Resolve primary once and track unresolved flags across providers
120+
$primaryMethod = $sub ?: 'handle';
121+
$unresolvedFlags = array_keys($options);
124122

125-
$exitCode = (int) ($this->invokeCommandMethod($instance, $primaryMethod, $positionals, $options) ?? 0);
123+
foreach ($entries as $entry) {
124+
// If registered with a class instance, support subcommands and flag-to-method routing
125+
if (isset($entry['instance']) && \is_object($entry['instance'])) {
126+
$instance = $entry['instance'];
126127

127-
// Route flags as methods on the same instance
128-
$invalidFlags = [];
129-
foreach ($options as $flag => $value) {
130-
$method = $this->optionToMethodName($flag);
131-
// Skip if this flag matches the already-run primary method
132-
if ($method === $primaryMethod) {
128+
// Skip instances that don't implement the requested primary method
129+
if (!method_exists($instance, $primaryMethod)) {
133130
continue;
134131
}
135-
if (method_exists($instance, $method)) {
136-
$this->invokeCommandMethod($instance, $method, $positionals, $options, $flag);
137-
} else {
138-
$invalidFlags[] = $flag;
132+
133+
$result = (int) ($this->invokeCommandMethod($instance, $primaryMethod, $positionals, $options) ?? 0);
134+
$exitCode = max($exitCode, $result);
135+
$handled = true;
136+
137+
// Route flags as methods on the same instance and mark them as resolved
138+
foreach ($unresolvedFlags as $i => $flag) {
139+
$method = $this->optionToMethodName($flag);
140+
if ($method === $primaryMethod) {
141+
unset($unresolvedFlags[$i]);
142+
continue;
143+
}
144+
if (method_exists($instance, $method)) {
145+
$this->invokeCommandMethod($instance, $method, $positionals, $options, $flag);
146+
unset($unresolvedFlags[$i]);
147+
}
139148
}
149+
150+
continue;
140151
}
141152

142-
if (!empty($invalidFlags)) {
143-
Logger::error("Invalid option/method: --" . implode(', --', $invalidFlags) . "\n");
153+
// Fallback: callable handler (no subcommands/flags routing)
154+
if (isset($entry['handler']) && \is_callable($entry['handler'])) {
155+
$handler = $entry['handler'];
156+
$result = (int) ($handler($rawArgs) ?? 0);
157+
$exitCode = max($exitCode, $result);
158+
$handled = true;
159+
continue;
144160
}
145161

146-
return $exitCode;
162+
// Unknown provider shape; ignore but keep iterating
163+
}
164+
165+
if (!$handled) {
166+
if ($sub !== null) {
167+
Logger::error("Command \"{$commandInput}\" is not defined.\n\n");
168+
} else {
169+
Logger::error("No valid providers handled command: {$commandInput}\n");
170+
}
171+
return max($exitCode, 1);
147172
}
148173

149-
// Fallback: callable handler (no subcommands/flags routing)
150-
if (isset($entry['handler']) && \is_callable($entry['handler'])) {
151-
$handler = $entry['handler'];
152-
return (int) ($handler($rawArgs) ?? 0);
174+
// Any flags not resolved by any provider are invalid
175+
$unresolvedFlags = array_values($unresolvedFlags);
176+
if (!empty($unresolvedFlags)) {
177+
Logger::error("Invalid option/method: --" . implode(', --', $unresolvedFlags) . "\n");
178+
$exitCode = max($exitCode, 1);
153179
}
154180

155-
Logger::error("Command not properly registered: {$commandInput}\n");
156-
return 1;
181+
return $exitCode;
157182
}
158183

159184
/**

Capsule/Traits/ArtisanTrait.php

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ trait ArtisanTrait
1414
* Build a grouped list keyed by the base command name.
1515
* Root commands without a colon are under key '__root'.
1616
*
17-
* @param array<string, array{instance?: object, handler?: callable, description: string}> $registry
17+
* @param array<string, array<int, array{instance?: object, handler?: callable, description: string}>> $registry
1818
* @return array<string, array<string,string>>
1919
*/
2020
protected function buildGroupedCommandList(array $registry): array
@@ -24,7 +24,9 @@ protected function buildGroupedCommandList(array $registry): array
2424
foreach ($flat as $cmd => $desc) {
2525
$pos = strpos($cmd, ':');
2626
if ($pos === false) {
27-
$grouped['__root'][$cmd] = $desc;
27+
// Merge root entries with same name; prefer non-empty description
28+
$existing = $grouped['__root'][$cmd] ?? '';
29+
$grouped['__root'][$cmd] = $existing !== '' ? $existing : $desc;
2830
} else {
2931
$group = substr($cmd, 0, $pos);
3032
$grouped[$group][$cmd] = $desc;
@@ -42,24 +44,46 @@ protected function buildGroupedCommandList(array $registry): array
4244
* Build a flat associative list of commands => description.
4345
* Includes base commands and public subcommand methods (e.g., foo:bar).
4446
*
45-
* @param array<string, array{instance?: object, handler?: callable, description: string}> $registry
47+
* @param array<string, array<int, array{instance?: object, handler?: callable, description: string}>> $registry
4648
* @return array<string,string>
4749
*/
4850
private function buildCommandList(array $registry): array
4951
{
5052
$list = [];
5153

52-
foreach ($registry as $name => $entry) {
53-
$desc = (string)($entry['description'] ?? '');
54-
$list[$name] = $desc;
54+
foreach ($registry as $name => $entries) {
55+
// Normalize entries to array of providers (list if index 0 exists)
56+
$providers = is_array($entries) && isset($entries[0]) ? $entries : [$entries];
5557

56-
if (isset($entry['instance']) && is_object($entry['instance'])) {
57-
$instance = $entry['instance'];
58-
foreach ($this->introspectPublicMethodsArray($instance) as $method => $summary) {
59-
if ($method === 'handle') {
60-
continue;
58+
$rootDesc = '';
59+
foreach ($providers as $entry) {
60+
$desc = (string)($entry['description'] ?? '');
61+
if ($rootDesc === '' && $desc !== '') {
62+
$rootDesc = $desc; // prefer first non-empty description
63+
}
64+
65+
if (isset($entry['instance']) && is_object($entry['instance'])) {
66+
$instance = $entry['instance'];
67+
foreach ($this->introspectPublicMethodsArray($instance) as $method => $summary) {
68+
if ($method === 'handle') {
69+
continue;
70+
}
71+
// If subcommand already exists, prefer a non-empty summary
72+
$key = $name . ':' . $method;
73+
if (!isset($list[$key]) || $list[$key] === '') {
74+
$list[$key] = $summary;
75+
}
6176
}
62-
$list[$name . ':' . $method] = $summary;
77+
}
78+
}
79+
80+
// Root command entry
81+
if (!isset($list[$name])) {
82+
$list[$name] = $rootDesc;
83+
} else {
84+
// Prefer non-empty description if existing was empty
85+
if ($list[$name] === '' && $rootDesc !== '') {
86+
$list[$name] = $rootDesc;
6387
}
6488
}
6589
}

0 commit comments

Comments
 (0)