Subcommand integration
Closed this issue · 5 comments
Write XPCLIs as XP subcommands (xp-framework/rfc#303)
<?php namespace com\example\cmd;
class Migrate extends \util\cmd\Command {
public function run() {
$this->out->writeLine('Migrating...');
}
}
# Current
$ xp cmd com.example.cmd.Migrate
$ xp cmd de.thekid.dialog.cmd.AddAlbum
# New
$ xp cmd.migrate
$ xp cmd.add-album
# ...or maybe even
$ xp migrate
$ xp add-album
How could this work?
Subcommands with a dot inside currently don't work, so we could reserve this:
$ xp cmd.migrate
Uncaught exception: Error (Class 'cmd\migrate' not found)
at <source> [line 366 of C:\tools\cygwin\home\Timm\bin\class-main.php]
at <main>() [line 0 of cmd\migrate.class.php]
There could, however, be ambiguities w/ classes, like e.g. cmd.Migrate, so maybe a ":" would be better?
A special handling if the command contains a ":" would be easiest:
- If strstr($cmd, ":") -> $spec= explode(":", $cmd) and continue with above, searching for $spec[0]
- Pass $spec[1] as further command line argument
...but then again, there's no advantage I can see over just typing "xp cmd migrate"
xp <cmd>
will currently search the following for a file called bin/xp.*.<cmd>
- Module path
- Current directory
- ./vendor/bin
- $COMPOSER_HOME/vendor/bin
To create a subcommand from an XPCLI, the following steps would be necessary:
- Create bin/xp.example.cmd.migrate (assuming example/cmd is the module name)
- Rename the Migrate class to MigrateRunner
- Move it to the xp.cmd namespace (the package name cmd comes from the above module name)
- Create a static main method inside it, and place
return \xp\command\CmdRunner::main($args)
in it
The missing piece between xp migrate
and com.example.cmd.Migrate
really only is the package name, com.example.cmd
.
The XPCLI package could install an autoloader (if we had a possibility for globally installed modules to do so, but the compiler would benefit from this, too!) to check for this package, search the class path recursively for a class Migrate extending util.cmd.Command, though that would be slowish:
private function findCommand($cl, $name, $package) {
$file= $name.\xp::CLASS_FILE_EXT;
$prefix= ($package ? $package.'.' : '');
foreach ($cl->packageContents($package) as $resource) {
if ($file === $resource) {
return $prefix.$name;
} else if ('/' === $resource{strlen($resource) - 1}) {
if ($uri= $this->findCommand($cl, $name, $prefix.substr($resource, 0, -1))) return $uri;
}
}
return null;
}
private function findClass($name) {
$class= implode('', array_map('ucfirst', explode('-', $name)));
foreach (ClassLoader::getLoaders() as $cl) {
if ($command= $this->findCommand($cl, $class, null)) return $cl->loadClass($command);
}
throw new ClassNotFoundException($class);
}
$class= $this->findClass('add-album'); // de.thekid.dialog.cmd.AddAlbum