-
Notifications
You must be signed in to change notification settings - Fork 17
Add alias and forget commands for user-defined package aliases #23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: 2.x
Are you sure you want to change the base?
Changes from all commits
bafdaec
f1cac96
a35ceeb
6a64f35
ab1d86a
40458bb
e9922a5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,112 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace Cpx\Commands; | ||
|
|
||
| use Cpx\Packages\Package; | ||
| use Cpx\Packages\UserAliases; | ||
| use InvalidArgumentException; | ||
| use Laravel\Prompts\Exceptions\NonInteractiveValidationException; | ||
| use Laravel\Prompts\Prompt; | ||
| use Symfony\Component\Console\Attribute\AsCommand; | ||
| use Symfony\Component\Console\Command\Command; | ||
| use Symfony\Component\Console\Input\InputArgument; | ||
| use Symfony\Component\Console\Input\InputInterface; | ||
| use Symfony\Component\Console\Output\OutputInterface; | ||
|
|
||
| use function Laravel\Prompts\error; | ||
| use function Laravel\Prompts\info; | ||
| use function Laravel\Prompts\text; | ||
|
|
||
| #[AsCommand( | ||
| name: 'alias', | ||
| description: 'Create a shortcut command for a Composer package', | ||
| )] | ||
| class AliasCommand extends Command | ||
| { | ||
| protected function configure(): void | ||
| { | ||
| $this->addArgument('package', InputArgument::OPTIONAL, 'The package to alias, e.g. <vendor>/<package>[:version]'); | ||
| $this->addArgument('name', InputArgument::OPTIONAL, 'The alias name to run the package as, e.g. "cpx <name>"'); | ||
| } | ||
|
|
||
| protected function execute(InputInterface $input, OutputInterface $output): int | ||
| { | ||
| Prompt::setOutput($output); | ||
|
|
||
| try { | ||
| $package = $this->resolvePackage($input); | ||
| $name = $this->resolveName($input, $package); | ||
| } catch (InvalidArgumentException|NonInteractiveValidationException $e) { | ||
|
TitasGailius marked this conversation as resolved.
|
||
| error($e->getMessage()); | ||
|
|
||
| return self::FAILURE; | ||
| } | ||
|
|
||
| UserAliases::open()->put($name, $package)->save(); | ||
|
|
||
| info("Alias created: cpx {$name} now runs {$package}."); | ||
|
|
||
| return self::SUCCESS; | ||
| } | ||
|
|
||
| private function resolvePackage(InputInterface $input): Package | ||
| { | ||
| if ($package = $input->getArgument('package')) { | ||
| if ($error = $this->validatePackage($package)) { | ||
| throw new InvalidArgumentException($error); | ||
| } | ||
|
|
||
| return Package::parse($package); | ||
| } | ||
|
|
||
| return Package::parse(text( | ||
|
TitasGailius marked this conversation as resolved.
|
||
| label: 'Which package would you like to alias?', | ||
| placeholder: '<vendor>/<package>[:version]', | ||
| required: 'A package name must be provided.', | ||
| validate: $this->validatePackage(...), | ||
| )); | ||
| } | ||
|
|
||
| private function validatePackage(string $value): ?string | ||
| { | ||
| try { | ||
| Package::parse($value); | ||
|
|
||
| return null; | ||
| } catch (InvalidArgumentException $e) { | ||
| return $e->getMessage(); | ||
| } | ||
| } | ||
|
|
||
| private function resolveName(InputInterface $input, Package $package): string | ||
| { | ||
| if ($name = $input->getArgument('name')) { | ||
| if ($error = $this->validateName($name)) { | ||
| throw new InvalidArgumentException($error); | ||
| } | ||
|
|
||
| return $name; | ||
| } | ||
|
|
||
| return text( | ||
| label: 'What should the alias be called?', | ||
| default: $package->name, | ||
| validate: $this->validateName(...), | ||
| ); | ||
| } | ||
|
|
||
| private function validateName(string $name): ?string | ||
| { | ||
| if (preg_match('/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/', $name) !== 1) { | ||
| return 'An alias name may only contain letters, numbers, dots, dashes and underscores.'; | ||
| } | ||
|
|
||
| if ($this->getApplication()?->has($name) === true) { | ||
| return "\"{$name}\" is already a cpx command and cannot be used as an alias name."; | ||
| } | ||
|
|
||
| return null; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace Cpx\Commands; | ||
|
|
||
| use Cpx\Packages\UserAliases; | ||
| use InvalidArgumentException; | ||
| use Laravel\Prompts\Exceptions\NonInteractiveValidationException; | ||
| use Laravel\Prompts\Prompt; | ||
| use Symfony\Component\Console\Attribute\AsCommand; | ||
| use Symfony\Component\Console\Command\Command; | ||
| use Symfony\Component\Console\Input\InputArgument; | ||
| use Symfony\Component\Console\Input\InputInterface; | ||
| use Symfony\Component\Console\Output\OutputInterface; | ||
|
|
||
| use function Laravel\Prompts\error; | ||
| use function Laravel\Prompts\info; | ||
| use function Laravel\Prompts\select; | ||
|
|
||
| #[AsCommand( | ||
| name: 'forget', | ||
| description: 'Remove a user-defined alias', | ||
| )] | ||
| class ForgetCommand extends Command | ||
|
TitasGailius marked this conversation as resolved.
|
||
| { | ||
| protected function configure(): void | ||
| { | ||
| $this->addArgument('name', InputArgument::OPTIONAL, 'The alias name to remove'); | ||
| } | ||
|
|
||
| protected function execute(InputInterface $input, OutputInterface $output): int | ||
| { | ||
| Prompt::setOutput($output); | ||
|
|
||
| $aliases = UserAliases::open(); | ||
|
|
||
| if ($aliases->all() === []) { | ||
| info('You have no aliases to forget.'); | ||
|
|
||
| return self::SUCCESS; | ||
| } | ||
|
|
||
| try { | ||
| $name = $this->resolveName($input, $aliases); | ||
| } catch (InvalidArgumentException|NonInteractiveValidationException $e) { | ||
| error($e->getMessage()); | ||
|
|
||
| return self::FAILURE; | ||
| } | ||
|
|
||
| $aliases->forget($name)->save(); | ||
|
|
||
| info("Alias \"{$name}\" removed."); | ||
|
|
||
| return self::SUCCESS; | ||
| } | ||
|
|
||
| private function resolveName(InputInterface $input, UserAliases $aliases): string | ||
| { | ||
| if ($name = $input->getArgument('name')) { | ||
| if ($error = $this->validateName($name, $aliases)) { | ||
| throw new InvalidArgumentException($error); | ||
| } | ||
|
|
||
| return $name; | ||
| } | ||
|
|
||
| return (string) select( | ||
| label: 'Which alias would you like to forget?', | ||
| options: array_keys($aliases->all()), | ||
| required: 'An alias name must be provided.', | ||
| validate: fn (string $name): ?string => $this->validateName($name, $aliases), | ||
| info: fn (string $name): string => (string) $aliases->find($name), | ||
| ); | ||
| } | ||
|
|
||
| private function validateName(string $name, UserAliases $aliases): ?string | ||
| { | ||
| if (! $aliases->has($name)) { | ||
| return "No alias named \"{$name}\" was found."; | ||
| } | ||
|
|
||
| return null; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace Cpx\Packages; | ||
|
|
||
| use Cpx\Support\Filesystem; | ||
|
|
||
| class UserAliases | ||
| { | ||
| private const FILE = 'aliases.json'; | ||
|
|
||
| /** | ||
| * @param array<string, Package> $aliases | ||
| */ | ||
| protected function __construct( | ||
| protected array $aliases = [], | ||
| ) {} | ||
|
|
||
| public static function open(): self | ||
| { | ||
| $file = cpx_path(self::FILE); | ||
|
|
||
| if (! file_exists($file)) { | ||
| return new self; | ||
| } | ||
|
|
||
| $json = json_decode((string) file_get_contents($file), true); | ||
|
|
||
| return new self(array_map( | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A malformed |
||
| fn (string $value): Package => Package::parse($value), | ||
| $json, | ||
| )); | ||
| } | ||
|
|
||
| /** @return array<string, Package> */ | ||
| public function all(): array | ||
| { | ||
| return $this->aliases; | ||
| } | ||
|
|
||
| public function has(string $name): bool | ||
| { | ||
| return array_key_exists($name, $this->aliases); | ||
| } | ||
|
|
||
| public function find(string $name): ?Package | ||
| { | ||
| return $this->aliases[$name] ?? null; | ||
| } | ||
|
|
||
| public function put(string $name, Package $package): self | ||
| { | ||
| $this->aliases[$name] = $package; | ||
|
|
||
| return $this; | ||
| } | ||
|
|
||
| public function forget(string $name): self | ||
| { | ||
| unset($this->aliases[$name]); | ||
|
|
||
| return $this; | ||
| } | ||
|
|
||
| public function save(): void | ||
|
TitasGailius marked this conversation as resolved.
|
||
| { | ||
| Filesystem::writeAtomic(cpx_path(self::FILE), (string) json_encode($this->toArray(), JSON_PRETTY_PRINT)); | ||
| } | ||
|
|
||
| /** @return array<string, string> */ | ||
| public function toArray(): array | ||
| { | ||
| return array_map( | ||
| fn (Package $package): string => $package->fullPackageString(), | ||
| $this->aliases, | ||
| ); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we display a warning/confirmation when user is overriding an existing alias?