aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe/src/console
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-08-19 23:45:37 +0900
committernsfisis <nsfisis@gmail.com>2026-08-19 23:45:37 +0900
commit0800c90a7ec0bc7a3d4c13063dfe6d2298a557bc (patch)
treeba5602549ce20b8d71136423e85dffc25deafff4 /crates/shirabe/src/console
parent6ed3a85dd636366d194c9810fd777db7f25e263f (diff)
downloadphp-shirabe-0800c90a7ec0bc7a3d4c13063dfe6d2298a557bc.tar.gz
php-shirabe-0800c90a7ec0bc7a3d4c13063dfe6d2298a557bc.tar.zst
php-shirabe-0800c90a7ec0bc7a3d4c13063dfe6d2298a557bc.zip
fix(input): thread a typed InputValue through the input layer
Options and arguments were stored and passed as PhpMixed even though Symfony only ever puts a string, a bool, a list of strings or null in one. get_option already narrowed to InputOptionValue at the boundary; this widens that enum into InputValue and pushes it through InputInterface, InputOption/InputArgument defaults, the Input storage, ArgvInput/ArrayInput/StringInput/CompletionInput, Command::add_option and add_argument, and the Composer-side wrappers. Two neighbouring string|int unions get types of their own: InputDefinition::{get_argument,has_argument} take an ArgumentName, and ArrayInput keys its parameters by ParameterName. has_parameter_option and get_parameter_option take the values they look for as &[&str], which is what PHP's `(array) $values` cast produced anyway. Two behaviours change along the way. Input::set_option on a negated option now negates with PHP's loose bool cast rather than treating a non-bool as false, matching `!$value`. ArrayInput::parse now resolves an integer key to an argument position instead of looking up an argument literally named "0". Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe/src/console')
-rw-r--r--crates/shirabe/src/console/application.rs195
-rw-r--r--crates/shirabe/src/console/input/input_argument.rs8
-rw-r--r--crates/shirabe/src/console/input/input_option.rs16
3 files changed, 80 insertions, 139 deletions
diff --git a/crates/shirabe/src/console/application.rs b/crates/shirabe/src/console/application.rs
index d73a1c6b..c9b06412 100644
--- a/crates/shirabe/src/console/application.rs
+++ b/crates/shirabe/src/console/application.rs
@@ -83,12 +83,15 @@ use shirabe_symfony_console::helper::Helper;
use shirabe_symfony_console::helper::HelperSet;
use shirabe_symfony_console::helper::QuestionHelper;
use shirabe_symfony_console::helper::{FormatBlockMessages, FormatterHelper};
+use shirabe_symfony_console::input::ArgumentName;
use shirabe_symfony_console::input::ArgvInput;
use shirabe_symfony_console::input::ArrayInput;
use shirabe_symfony_console::input::InputArgument;
use shirabe_symfony_console::input::InputDefinition;
use shirabe_symfony_console::input::InputInterface;
use shirabe_symfony_console::input::InputOption;
+use shirabe_symfony_console::input::InputValue;
+use shirabe_symfony_console::input::ParameterName;
use shirabe_symfony_console::output::ConsoleOutput;
use shirabe_symfony_console::output::ConsoleOutputInterface;
use shirabe_symfony_console::output::{OutputInterface, output_interface};
@@ -207,11 +210,7 @@ impl Application {
) -> anyhow::Result<Option<String>> {
let working_dir = input
.borrow()
- .get_parameter_option(
- PhpMixed::from(vec!["--working-dir", "-d"]),
- PhpMixed::Null,
- true,
- )
+ .get_parameter_option(&["--working-dir", "-d"], InputValue::Null, true)
.as_string()
.map(|s| s.to_string());
if let Some(ref wd) = working_dir
@@ -527,38 +526,38 @@ impl Application {
let mut definition = self.base_get_default_input_definition();
definition.add_option(InputOption::new(
"--profile",
- PhpMixed::Null,
+ None,
Some(InputOption::VALUE_NONE),
"Display timing and memory usage information".to_string(),
- PhpMixed::Null,
+ InputValue::Null,
)?)?;
definition.add_option(InputOption::new(
"--no-plugins",
- PhpMixed::Null,
+ None,
Some(InputOption::VALUE_NONE),
"Whether to disable plugins.".to_string(),
- PhpMixed::Null,
+ InputValue::Null,
)?)?;
definition.add_option(InputOption::new(
"--no-scripts",
- PhpMixed::Null,
+ None,
Some(InputOption::VALUE_NONE),
"Skips the execution of all scripts defined in composer.json file.".to_string(),
- PhpMixed::Null,
+ InputValue::Null,
)?)?;
definition.add_option(InputOption::new(
"--working-dir",
- PhpMixed::from("-d"),
+ Some("-d"),
Some(InputOption::VALUE_REQUIRED),
"If specified, use the given directory as working directory.".to_string(),
- PhpMixed::Null,
+ InputValue::Null,
)?)?;
definition.add_option(InputOption::new(
"--no-cache",
- PhpMixed::Null,
+ None,
Some(InputOption::VALUE_NONE),
"Prevent use of the cache".to_string(),
- PhpMixed::Null,
+ InputValue::Null,
)?)?;
Ok(definition)
@@ -1414,25 +1413,16 @@ impl Application {
input: &std::rc::Rc<std::cell::RefCell<dyn InputInterface>>,
output: &std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>,
) -> anyhow::Result<()> {
- if input.borrow().has_parameter_option(
- PhpMixed::from(vec![PhpMixed::from("--ansi".to_string())]),
- true,
- ) {
+ if input.borrow().has_parameter_option(&["--ansi"], true) {
output.borrow().set_decorated(true);
- } else if input.borrow().has_parameter_option(
- PhpMixed::from(vec![PhpMixed::from("--no-ansi".to_string())]),
- true,
- ) {
+ } else if input.borrow().has_parameter_option(&["--no-ansi"], true) {
output.borrow().set_decorated(false);
}
- if input.borrow().has_parameter_option(
- PhpMixed::from(vec![
- PhpMixed::from("--no-interaction".to_string()),
- PhpMixed::from("-n".to_string()),
- ]),
- true,
- ) {
+ if input
+ .borrow()
+ .has_parameter_option(&["--no-interaction", "-n"], true)
+ {
input.borrow_mut().set_interactive(false);
}
@@ -1468,63 +1458,39 @@ impl Application {
}
}
- if input.borrow().has_parameter_option(
- PhpMixed::from(vec![
- PhpMixed::from("--quiet".to_string()),
- PhpMixed::from("-q".to_string()),
- ]),
- true,
- ) {
+ if input
+ .borrow()
+ .has_parameter_option(&["--quiet", "-q"], true)
+ {
output
.borrow()
.set_verbosity(output_interface::VERBOSITY_QUIET);
shell_verbosity = -1;
- } else if input
- .borrow()
- .has_parameter_option(PhpMixed::from("-vvv".to_string()), true)
- || input
- .borrow()
- .has_parameter_option(PhpMixed::from("--verbose=3".to_string()), true)
- || input.borrow().get_parameter_option(
- PhpMixed::from("--verbose".to_string()),
- PhpMixed::Bool(false),
- true,
- ) == PhpMixed::from(3i64)
+ } else if input.borrow().has_parameter_option(&["-vvv"], true)
+ || input.borrow().has_parameter_option(&["--verbose=3"], true)
+ // TODO(type-model): PHP also matches when `--verbose` carries the int 3;
+ // `InputValue` has no int variant, so only the flag spellings above are checked.
{
output
.borrow()
.set_verbosity(output_interface::VERBOSITY_DEBUG);
shell_verbosity = 3;
- } else if input
- .borrow()
- .has_parameter_option(PhpMixed::from("-vv".to_string()), true)
- || input
- .borrow()
- .has_parameter_option(PhpMixed::from("--verbose=2".to_string()), true)
- || input.borrow().get_parameter_option(
- PhpMixed::from("--verbose".to_string()),
- PhpMixed::Bool(false),
- true,
- ) == PhpMixed::from(2i64)
+ } else if input.borrow().has_parameter_option(&["-vv"], true)
+ || input.borrow().has_parameter_option(&["--verbose=2"], true)
+ // TODO(type-model): PHP also matches when `--verbose` carries the int 2;
+ // `InputValue` has no int variant, so only the flag spellings above are checked.
{
output
.borrow()
.set_verbosity(output_interface::VERBOSITY_VERY_VERBOSE);
shell_verbosity = 2;
- } else if input
- .borrow()
- .has_parameter_option(PhpMixed::from("-v".to_string()), true)
+ } else if input.borrow().has_parameter_option(&["-v"], true)
+ || input.borrow().has_parameter_option(&["--verbose=1"], true)
+ || input.borrow().has_parameter_option(&["--verbose"], true)
|| input
.borrow()
- .has_parameter_option(PhpMixed::from("--verbose=1".to_string()), true)
- || input
- .borrow()
- .has_parameter_option(PhpMixed::from("--verbose".to_string()), true)
- || shirabe_php_shim::php_truthy(&input.borrow().get_parameter_option(
- PhpMixed::from("--verbose".to_string()),
- PhpMixed::Bool(false),
- true,
- ))
+ .get_parameter_option(&["--verbose"], InputValue::Bool(false), true)
+ .to_bool()
{
output
.borrow()
@@ -1569,70 +1535,70 @@ impl Application {
"command".to_string(),
Some(InputArgument::REQUIRED),
"The command to execute".to_string(),
- PhpMixed::Null,
+ InputValue::Null,
)
.unwrap(),
),
DefinitionItem::InputOption(
InputOption::new(
"--help",
- PhpMixed::from("-h".to_string()),
+ Some("-h"),
Some(InputOption::VALUE_NONE),
format!(
"Display help for the given command. When no command is given display help for the <info>{}</info> command",
self.default_command
),
- PhpMixed::Null,
+ InputValue::Null,
)
.unwrap(),
),
DefinitionItem::InputOption(
InputOption::new(
"--quiet",
- PhpMixed::from("-q".to_string()),
+ Some("-q"),
Some(InputOption::VALUE_NONE),
"Do not output any message".to_string(),
- PhpMixed::Null,
+ InputValue::Null,
)
.unwrap(),
),
DefinitionItem::InputOption(
InputOption::new(
"--verbose",
- PhpMixed::from("-v|vv|vvv".to_string()),
+ Some("-v|vv|vvv"),
Some(InputOption::VALUE_NONE),
"Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug".to_string(),
- PhpMixed::Null,
+ InputValue::Null,
)
.unwrap(),
),
DefinitionItem::InputOption(
InputOption::new(
"--version",
- PhpMixed::from("-V".to_string()),
+ Some("-V"),
Some(InputOption::VALUE_NONE),
"Display this application version".to_string(),
- PhpMixed::Null,
+ InputValue::Null,
)
.unwrap(),
),
DefinitionItem::InputOption(
InputOption::new(
"--ansi",
- PhpMixed::from("".to_string()),
+ Some(""),
Some(InputOption::VALUE_NEGATABLE),
"Force (or disable --no-ansi) ANSI output".to_string(),
- PhpMixed::Null,
+ InputValue::Null,
)
.unwrap(),
),
DefinitionItem::InputOption(
InputOption::new(
"--no-interaction",
- PhpMixed::from("-n".to_string()),
+ Some("-n"),
Some(InputOption::VALUE_NONE),
"Do not ask any interactive question".to_string(),
- PhpMixed::Null,
+ InputValue::Null,
)
.unwrap(),
),
@@ -1955,10 +1921,10 @@ impl ApplicationHandle {
let application = &self.0;
application.borrow_mut().disable_plugins_by_default = input
.borrow()
- .has_parameter_option(PhpMixed::from(vec!["--no-plugins"]), false);
+ .has_parameter_option(&["--no-plugins"], false);
application.borrow_mut().disable_scripts_by_default = input
.borrow()
- .has_parameter_option(PhpMixed::from(vec!["--no-scripts"]), false);
+ .has_parameter_option(&["--no-scripts"], false);
let stdin = shirabe_php_shim::STDIN;
if Platform::get_env("COMPOSER_TESTS_ARE_RUNNING").as_deref() != Some("1")
@@ -1980,10 +1946,7 @@ impl ApplicationHandle {
// Register error handler again to pass it the IO instance
ErrorHandler::register(Some(io.clone()));
- if input
- .borrow()
- .has_parameter_option(PhpMixed::from(vec!["--no-cache"]), false)
- {
+ if input.borrow().has_parameter_option(&["--no-cache"], false) {
io.write_error3("Disabling cache usage", true, io_interface::DEBUG);
Platform::put_env(
"COMPOSER_CACHE_DIR",
@@ -2053,18 +2016,10 @@ impl ApplicationHandle {
&& !file_exists(Factory::get_composer_file().unwrap_or_default())
&& use_parent_dir_if_no_json_available.as_bool() != Some(false)
&& (command_name.as_deref() != Some("config")
- || (!input
- .borrow()
- .has_parameter_option(PhpMixed::from(vec!["--file"]), true)
- && !input
- .borrow()
- .has_parameter_option(PhpMixed::from(vec!["-f"]), true)))
- && !input
- .borrow()
- .has_parameter_option(PhpMixed::from(vec!["--help"]), true)
- && !input
- .borrow()
- .has_parameter_option(PhpMixed::from(vec!["-h"]), true)
+ || (!input.borrow().has_parameter_option(&["--file"], true)
+ && !input.borrow().has_parameter_option(&["-f"], true)))
+ && !input.borrow().has_parameter_option(&["--help"], true)
+ && !input.borrow().has_parameter_option(&["-h"], true)
{
let mut dir = dirname(&Platform::get_cwd(true).unwrap_or_default());
let home_value = Platform::get_env("HOME")
@@ -2141,7 +2096,7 @@ impl ApplicationHandle {
// if showing the version, we never need plugin commands
let may_need_plugin_command = !input
.borrow()
- .has_parameter_option(PhpMixed::from(vec!["--version", "-V"]), false)
+ .has_parameter_option(&["--version", "-V"], false)
&& (command_name.is_none()
|| matches!(command_name.as_deref().unwrap_or(""), "" | "list" | "help")
|| (command_name.as_deref() == Some("_complete") && !is_non_allowed_root));
@@ -2547,10 +2502,7 @@ impl ApplicationHandle {
let mut start_time: Option<f64> = None;
let result_outcome: anyhow::Result<i32> = (|| -> anyhow::Result<i32> {
- if input
- .borrow()
- .has_parameter_option(PhpMixed::from(vec!["--profile"]), false)
- {
+ if input.borrow().has_parameter_option(&["--profile"], false) {
start_time = Some(microtime());
io.borrow_mut().enable_debugging(start_time.unwrap());
}
@@ -2559,7 +2511,7 @@ impl ApplicationHandle {
if input
.borrow()
- .has_parameter_option(PhpMixed::from(vec!["--version", "-V"]), true)
+ .has_parameter_option(&["--version", "-V"], true)
{
io.write_error(&format!(
"<info>PHP</info> version <comment>{}</comment> ({})",
@@ -2764,13 +2716,10 @@ impl ApplicationHandle {
output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>,
) -> anyhow::Result<i32> {
let application = &self.0;
- if input.borrow().has_parameter_option(
- PhpMixed::from(vec![
- PhpMixed::from("--version".to_string()),
- PhpMixed::from("-V".to_string()),
- ]),
- true,
- ) {
+ if input
+ .borrow()
+ .has_parameter_option(&["--version", "-V"], true)
+ {
let long_version = application.borrow().get_long_version();
output
.borrow()
@@ -2793,20 +2742,14 @@ impl ApplicationHandle {
let mut input = input;
let mut name = application.borrow().get_command_name(&*input.borrow());
- if input.borrow().has_parameter_option(
- PhpMixed::from(vec![
- PhpMixed::from("--help".to_string()),
- PhpMixed::from("-h".to_string()),
- ]),
- true,
- ) {
+ if input.borrow().has_parameter_option(&["--help", "-h"], true) {
if name.is_none() {
name = Some("help".to_string());
let default_command = application.borrow().default_command.clone();
input = std::rc::Rc::new(std::cell::RefCell::new(ArrayInput::new(
vec![(
- PhpMixed::from("command_name".to_string()),
- PhpMixed::from(default_command),
+ ParameterName::of("command_name"),
+ InputValue::from(default_command),
)],
None,
)?));
@@ -2822,14 +2765,14 @@ impl ApplicationHandle {
let definition = application.borrow_mut().get_definition();
let command_description = definition
.borrow()
- .get_argument(&PhpMixed::from("command".to_string()))?
+ .get_argument(&ArgumentName::Name("command".to_string()))?
.get_description()
.to_string();
let new_command_argument = InputArgument::new(
"command".to_string(),
Some(InputArgument::OPTIONAL),
command_description,
- PhpMixed::from(name.clone()),
+ InputValue::from(name.clone()),
)?;
// $definition->setArguments(array_merge($definition->getArguments(),
// ['command' => new InputArgument('command', InputArgument::OPTIONAL, ...)]))
diff --git a/crates/shirabe/src/console/input/input_argument.rs b/crates/shirabe/src/console/input/input_argument.rs
index 1e550926..8f7d526c 100644
--- a/crates/shirabe/src/console/input/input_argument.rs
+++ b/crates/shirabe/src/console/input/input_argument.rs
@@ -1,10 +1,10 @@
//! ref: composer/src/Composer/Console/Input/InputArgument.php
use crate::console::input::SuggestedValues;
-use shirabe_php_shim::PhpMixed;
use shirabe_symfony_console::completion::CompletionInput;
use shirabe_symfony_console::completion::CompletionSuggestions;
use shirabe_symfony_console::input::InputArgument as BaseInputArgument;
+use shirabe_symfony_console::input::InputValue;
#[derive(Debug)]
pub struct InputArgument {
@@ -21,7 +21,7 @@ impl InputArgument {
name: &str,
mode: Option<i64>,
description: &str,
- default: Option<PhpMixed>,
+ default: Option<InputValue>,
) -> anyhow::Result<Self> {
Self::new5(
name,
@@ -37,14 +37,14 @@ impl InputArgument {
name: &str,
mode: Option<i64>,
description: &str,
- default: Option<PhpMixed>,
+ default: Option<InputValue>,
suggested_values: SuggestedValues,
) -> anyhow::Result<Self> {
let inner = BaseInputArgument::new(
name.to_string(),
mode,
description.to_string(),
- default.unwrap_or(PhpMixed::Null),
+ default.unwrap_or(InputValue::Null),
)?;
Ok(Self {
inner,
diff --git a/crates/shirabe/src/console/input/input_option.rs b/crates/shirabe/src/console/input/input_option.rs
index 976f006f..6e84967e 100644
--- a/crates/shirabe/src/console/input/input_option.rs
+++ b/crates/shirabe/src/console/input/input_option.rs
@@ -1,10 +1,10 @@
//! ref: composer/src/Composer/Console/Input/InputOption.php
use crate::console::input::SuggestedValues;
-use shirabe_php_shim::PhpMixed;
use shirabe_symfony_console::completion::CompletionInput;
use shirabe_symfony_console::completion::CompletionSuggestions;
use shirabe_symfony_console::input::InputOption as BaseInputOption;
+use shirabe_symfony_console::input::InputValue;
#[derive(Debug)]
pub struct InputOption {
@@ -21,10 +21,10 @@ impl InputOption {
pub fn new(
name: &str,
- shortcut: Option<PhpMixed>,
+ shortcut: Option<&str>,
mode: Option<i64>,
description: &str,
- default: Option<PhpMixed>,
+ default: Option<InputValue>,
) -> anyhow::Result<Self> {
Self::new6(
name,
@@ -39,16 +39,14 @@ impl InputOption {
/// PHP's constructor with the sixth parameter, `$suggestedValues`.
pub fn new6(
name: &str,
- shortcut: Option<PhpMixed>,
+ shortcut: Option<&str>,
mode: Option<i64>,
description: &str,
- default: Option<PhpMixed>,
+ default: Option<InputValue>,
suggested_values: SuggestedValues,
) -> anyhow::Result<Self> {
- let shortcut = shortcut.unwrap_or(PhpMixed::Null);
- let default_mixed = default.unwrap_or(PhpMixed::Null);
- let inner =
- BaseInputOption::new(name, shortcut, mode, description.to_string(), default_mixed)?;
+ let default = default.unwrap_or(InputValue::Null);
+ let inner = BaseInputOption::new(name, shortcut, mode, description.to_string(), default)?;
// PHP throws LogicException here; suggested values on a valueless option cannot happen
// at runtime unless a configure() is wrong, so this is a programming error.
assert!(