diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-06-12 01:01:35 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-06-12 01:01:43 +0900 |
| commit | 1e44f5723e4c0e0903d00a61f254901e612fe5e1 (patch) | |
| tree | 9c2e1eec364bbf6418a4072ee7767b04c3df0297 /crates/shirabe/src | |
| parent | ddf0a624145b618c05c2ee47414545b99b685f37 (diff) | |
| download | php-shirabe-1e44f5723e4c0e0903d00a61f254901e612fe5e1.tar.gz php-shirabe-1e44f5723e4c0e0903d00a61f254901e612fe5e1.tar.zst php-shirabe-1e44f5723e4c0e0903d00a61f254901e612fe5e1.zip | |
feat(symfony-console): port Symfony Console and make the workspace compile
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe/src')
44 files changed, 820 insertions, 594 deletions
diff --git a/crates/shirabe/src/advisory/auditor.rs b/crates/shirabe/src/advisory/auditor.rs index 2b71237..c12e131 100644 --- a/crates/shirabe/src/advisory/auditor.rs +++ b/crates/shirabe/src/advisory/auditor.rs @@ -417,12 +417,12 @@ impl Auditor { } .into()); } - self.output_advisories_table(io_as_console.unwrap(), advisories); + self.output_advisories_table(io_as_console.unwrap(), advisories)?; Ok(()) } Self::FORMAT_PLAIN => { - self.output_advisories_plain(io, advisories); + self.output_advisories_plain(io, advisories)?; Ok(()) } @@ -440,7 +440,7 @@ impl Auditor { &self, io: &ConsoleIO, advisories: &IndexMap<String, Vec<AnySecurityAdvisory>>, - ) { + ) -> Result<()> { for package_advisories in advisories.values() { for advisory in package_advisories { let mut headers: Vec<String> = vec![ @@ -475,22 +475,26 @@ impl Auditor { .unwrap_or_else(|| "None specified".to_string()), ); } - io.get_table() + let mut table = io.get_table(); + table .set_horizontal(true) - .set_headers(headers.into_iter().map(|h| h.into()).collect()) - .add_row(ConsoleIO::sanitize( - PhpMixed::List( - row.into_iter() - .map(|s| Box::new(PhpMixed::String(s))) - .collect(), - ), - false, - )) + .set_headers(headers.into_iter().map(|h| h.into()).collect()); + table.add_row(ConsoleIO::sanitize( + PhpMixed::List( + row.into_iter() + .map(|s| Box::new(PhpMixed::String(s))) + .collect(), + ), + false, + ))??; + table .set_column_width(1, 80) .set_column_max_width(1, 80) .render(); } } + + Ok(()) } /// @param array<string, array<SecurityAdvisory>> $advisories @@ -498,7 +502,7 @@ impl Auditor { &self, io: &mut dyn IOInterface, advisories: &IndexMap<String, Vec<AnySecurityAdvisory>>, - ) { + ) -> Result<()> { let mut error: Vec<String> = vec![]; let mut first_advisory = true; for package_advisories in advisories.values() { @@ -513,11 +517,11 @@ impl Auditor { error.push(format!("Severity: {}", self.get_severity(sa))); error.push(format!("Advisory ID: {}", self.get_advisory_id(sa))); error.push(format!("CVE: {}", self.get_cve(sa))); - error.push(format!("Title: {}", OutputFormatter::escape(&sa.title))); + error.push(format!("Title: {}", OutputFormatter::escape(&sa.title)?)); error.push(format!("URL: {}", self.get_url(sa))); error.push(format!( "Affected versions: {}", - OutputFormatter::escape(&sa.affected_versions().get_pretty_string()) + OutputFormatter::escape(&sa.affected_versions().get_pretty_string())? )); error.push(format!("Reported at: {}", sa.reported_at.format(DATE_ATOM))); if let Some(ignored) = advisory.as_ignored() { @@ -535,6 +539,8 @@ impl Auditor { for line in &error { io.write_error(line); } + + Ok(()) } /// @param array<CompletePackageInterface> $packages @@ -625,7 +631,8 @@ impl Auditor { if package_url.is_some() { format!( "<href={}>{}</>", - OutputFormatter::escape(&package_url.unwrap()), + OutputFormatter::escape(&package_url.unwrap()) + .expect("OutputFormatter::escape does not fail"), package.get_pretty_name() ) } else { @@ -673,8 +680,8 @@ impl Auditor { let link = advisory.link.as_ref().unwrap(); format!( "<href={}>{}</>", - OutputFormatter::escape(link), - OutputFormatter::escape(link) + OutputFormatter::escape(link).expect("OutputFormatter::escape does not fail"), + OutputFormatter::escape(link).expect("OutputFormatter::escape does not fail") ) } diff --git a/crates/shirabe/src/autoload/autoload_generator.rs b/crates/shirabe/src/autoload/autoload_generator.rs index a380763..7167f04 100644 --- a/crates/shirabe/src/autoload/autoload_generator.rs +++ b/crates/shirabe/src/autoload/autoload_generator.rs @@ -464,7 +464,7 @@ impl AutoloadGenerator { if !ambiguous_classes.is_empty() { self.io.write_error(&format!( "<info>To resolve ambiguity in classes not under your control you can ignore them by path using <href={}>exclude-from-classmap</>", - OutputFormatter::escape("https://getcomposer.org/doc/04-schema.md#exclude-files-from-classmaps") + OutputFormatter::escape("https://getcomposer.org/doc/04-schema.md#exclude-files-from-classmaps")? )); } diff --git a/crates/shirabe/src/command/archive_command.rs b/crates/shirabe/src/command/archive_command.rs index 53159f8..f657ef4 100644 --- a/crates/shirabe/src/command/archive_command.rs +++ b/crates/shirabe/src/command/archive_command.rs @@ -88,7 +88,7 @@ impl ArchiveCommand { let format = input .borrow() - .get_option("format") + .get_option("format")? .as_string() .map(|s| s.to_string()) .unwrap_or_else(|| { @@ -102,7 +102,7 @@ impl ArchiveCommand { let dir = input .borrow() - .get_option("dir") + .get_option("dir")? .as_string() .map(|s| s.to_string()) .unwrap_or_else(|| { @@ -120,24 +120,24 @@ impl ArchiveCommand { &config, input .borrow() - .get_argument("package") + .get_argument("package")? .as_string() .map(|s| s.to_string()), input .borrow() - .get_argument("version") + .get_argument("version")? .as_string() .map(|s| s.to_string()), &format, &dir, input .borrow() - .get_option("file") + .get_option("file")? .as_string() .map(|s| s.to_string()), input .borrow() - .get_option("ignore-filters") + .get_option("ignore-filters")? .as_bool() .unwrap_or(false), composer.as_ref(), diff --git a/crates/shirabe/src/command/audit_command.rs b/crates/shirabe/src/command/audit_command.rs index 0e050cc..cd7a799 100644 --- a/crates/shirabe/src/command/audit_command.rs +++ b/crates/shirabe/src/command/audit_command.rs @@ -85,7 +85,7 @@ impl AuditCommand { let abandoned = input .borrow() - .get_option("abandoned") + .get_option("abandoned")? .as_string() .map(|s| s.to_string()); if abandoned.is_some() @@ -115,7 +115,7 @@ impl AuditCommand { let mut ignore_severities: indexmap::IndexMap<String, Option<String>> = indexmap::IndexMap::new(); - let cli_severities = input.borrow().get_option("ignore-severity"); + let cli_severities = input.borrow().get_option("ignore-severity")?; if let Some(list) = cli_severities.as_list() { for sev in list { if let Some(s) = sev.as_string() { @@ -128,7 +128,7 @@ impl AuditCommand { } let ignore_unreachable = input .borrow() - .get_option("ignore-unreachable") + .get_option("ignore-unreachable")? .as_bool() .unwrap_or(false) || audit_config.ignore_unreachable; @@ -158,7 +158,7 @@ impl AuditCommand { let mut composer = crate::command::composer_full_mut(composer); if input .borrow() - .get_option("locked") + .get_option("locked")? .as_bool() .unwrap_or(false) { @@ -173,7 +173,7 @@ impl AuditCommand { let locked_repo = locker.get_locked_repository( !input .borrow() - .get_option("no-dev") + .get_option("no-dev")? .as_bool() .unwrap_or(false), )?; diff --git a/crates/shirabe/src/command/base_command.rs b/crates/shirabe/src/command/base_command.rs index 9218497..af06ad9 100644 --- a/crates/shirabe/src/command/base_command.rs +++ b/crates/shirabe/src/command/base_command.rs @@ -369,10 +369,10 @@ impl<C: HasBaseCommandData> BaseCommand for C { // shared Application handle is the prerequisite, so only the input flags are honoured here. let mut disable_plugins = input .borrow() - .has_parameter_option(&["--no-plugins"], false); + .has_parameter_option(PhpMixed::from(vec!["--no-plugins"]), false); let mut disable_scripts = input .borrow() - .has_parameter_option(&["--no-scripts"], false); + .has_parameter_option(PhpMixed::from(vec!["--no-scripts"]), false); if self.is_self_update_command() { disable_plugins = true; @@ -411,7 +411,9 @@ impl<C: HasBaseCommandData> BaseCommand for C { )?; } - if input.borrow().has_parameter_option(&["--no-ansi"], false) + if input + .borrow() + .has_parameter_option(PhpMixed::from(vec!["--no-ansi"]), false) && input.borrow().has_option("no-progress") { input @@ -443,7 +445,7 @@ impl<C: HasBaseCommandData> BaseCommand for C { if false == input .borrow() - .get_option(option_name) + .get_option(option_name)? .as_bool() .unwrap_or(false) && Platform::get_env(env_name).map_or(false, |s| !s.is_empty() && s != "0") @@ -459,7 +461,7 @@ impl<C: HasBaseCommandData> BaseCommand for C { if true == input.borrow().has_option("ignore-platform-reqs") { if !input .borrow() - .get_option("ignore-platform-reqs") + .get_option("ignore-platform-reqs")? .as_bool() .unwrap_or(false) && Platform::get_env("COMPOSER_IGNORE_PLATFORM_REQS") @@ -477,13 +479,13 @@ impl<C: HasBaseCommandData> BaseCommand for C { && (!input.borrow().has_option("ignore-platform-reqs") || !input .borrow() - .get_option("ignore-platform-reqs") + .get_option("ignore-platform-reqs")? .as_bool() .unwrap_or(false)) { let ignore_platform_req_env = Platform::get_env("COMPOSER_IGNORE_PLATFORM_REQ"); let ignore_str = ignore_platform_req_env.clone().unwrap_or_default(); - if 0 == count(&input.borrow().get_option("ignore-platform-req")) + if 0 == count(&input.borrow().get_option("ignore-platform-req")?) && ignore_platform_req_env.is_some() && "" != ignore_str { @@ -519,11 +521,11 @@ impl<C: HasBaseCommandData> BaseCommand for C { let disable_plugins = disable_plugins || input .borrow() - .has_parameter_option(&["--no-plugins"], false); + .has_parameter_option(PhpMixed::from(vec!["--no-plugins"]), false); let disable_scripts = disable_scripts.unwrap_or(false) || input .borrow() - .has_parameter_option(&["--no-scripts"], false); + .has_parameter_option(PhpMixed::from(vec!["--no-scripts"]), false); // PHP: if ($app instanceof Application && $app->getDisablePluginsByDefault()) $disablePlugins = true; // (same for getDisableScriptsByDefault()). @@ -565,11 +567,11 @@ impl<C: HasBaseCommandData> BaseCommand for C { } if input.borrow().has_option("prefer-install") - && is_string(&input.borrow().get_option("prefer-install")) + && is_string(&input.borrow().get_option("prefer-install")?) { if input .borrow() - .get_option("prefer-source") + .get_option("prefer-source")? .as_bool() .unwrap_or(false) { @@ -582,7 +584,7 @@ impl<C: HasBaseCommandData> BaseCommand for C { } if input .borrow() - .get_option("prefer-dist") + .get_option("prefer-dist")? .as_bool() .unwrap_or(false) { @@ -593,7 +595,7 @@ impl<C: HasBaseCommandData> BaseCommand for C { } .into()); } - let prefer_install = input.borrow().get_option("prefer-install"); + let prefer_install = input.borrow().get_option("prefer-install")?; match prefer_install.as_string().unwrap_or("") { "dist" => { input @@ -624,37 +626,37 @@ impl<C: HasBaseCommandData> BaseCommand for C { if input .borrow() - .get_option("prefer-source") + .get_option("prefer-source")? .as_bool() .unwrap_or(false) || input .borrow() - .get_option("prefer-dist") + .get_option("prefer-dist")? .as_bool() .unwrap_or(false) || (keep_vcs_requires_prefer_source && input.borrow().has_option("keep-vcs") && input .borrow() - .get_option("keep-vcs") + .get_option("keep-vcs")? .as_bool() .unwrap_or(false)) { prefer_source = input .borrow() - .get_option("prefer-source") + .get_option("prefer-source")? .as_bool() .unwrap_or(false) || (keep_vcs_requires_prefer_source && input.borrow().has_option("keep-vcs") && input .borrow() - .get_option("keep-vcs") + .get_option("keep-vcs")? .as_bool() .unwrap_or(false)); prefer_dist = input .borrow() - .get_option("prefer-dist") + .get_option("prefer-dist")? .as_bool() .unwrap_or(false); } @@ -681,14 +683,14 @@ impl<C: HasBaseCommandData> BaseCommand for C { if true == input .borrow() - .get_option("ignore-platform-reqs") + .get_option("ignore-platform-reqs")? .as_bool() .unwrap_or(false) { return Ok(PlatformRequirementFilterFactory::ignore_all()); } - let ignores = input.borrow().get_option("ignore-platform-req"); + let ignores = input.borrow().get_option("ignore-platform-req")?; if count(&ignores) > 0 { return Ok(PlatformRequirementFilterFactory::from_bool_or_list( ignores, @@ -737,7 +739,10 @@ impl<C: HasBaseCommandData> BaseCommand for C { output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>, ) { let mut renderer = Table::new(output); - renderer.set_style("compact"); + renderer + .set_style(PhpMixed::from("compact")) + .expect("Table::set_style I/O cannot fail") + .expect("'compact' is a built-in table style"); renderer.set_rows(table).render(); let _ = TableSeparator::new(); } @@ -771,7 +776,7 @@ impl<C: HasBaseCommandData> BaseCommand for C { .into()); } - let val = input.borrow().get_option(opt_name); + let val = input.borrow().get_option(opt_name)?; let formats: Vec<Box<PhpMixed>> = Auditor::FORMATS .iter() .map(|s| Box::new(PhpMixed::String(s.to_string()))) @@ -800,14 +805,14 @@ impl<C: HasBaseCommandData> BaseCommand for C { let audit = if input.borrow().has_option("audit") { input .borrow() - .get_option("audit") + .get_option("audit")? .as_bool() .unwrap_or(false) } else { !(input.borrow().has_option("no-audit") && input .borrow() - .get_option("no-audit") + .get_option("no-audit")? .as_bool() .unwrap_or(false)) }; @@ -824,7 +829,7 @@ impl<C: HasBaseCommandData> BaseCommand for C { || (input.borrow().has_option("no-security-blocking") && input .borrow() - .get_option("no-security-blocking") + .get_option("no-security-blocking")? .as_bool() .unwrap_or(false)) { diff --git a/crates/shirabe/src/command/base_config_command.rs b/crates/shirabe/src/command/base_config_command.rs index bfdd33a..4f6fd59 100644 --- a/crates/shirabe/src/command/base_config_command.rs +++ b/crates/shirabe/src/command/base_config_command.rs @@ -31,10 +31,10 @@ pub trait BaseConfigCommand: BaseCommand { if input .borrow() - .get_option("global") + .get_option("global")? .as_bool() .unwrap_or(false) - && !input.borrow().get_option("file").is_null() + && !input.borrow().get_option("file")?.is_null() { return Err(anyhow::anyhow!("--file and --global can not be combined")); } @@ -48,7 +48,7 @@ pub trait BaseConfigCommand: BaseCommand { // When using --global flag, set baseDir to home directory for correct absolute path resolution if input .borrow() - .get_option("global") + .get_option("global")? .as_bool() .unwrap_or(false) { @@ -56,7 +56,7 @@ pub trait BaseConfigCommand: BaseCommand { config_rc.borrow_mut().set_base_dir(Some(home)); } - let config_file = self.get_composer_config_file(input.clone(), &*config_rc.borrow()); + let config_file = self.get_composer_config_file(input.clone(), &*config_rc.borrow())?; // Create global composer.json if invoked using `composer global [config-cmd]` if (config_file == "composer.json" || config_file == "./composer.json") @@ -77,7 +77,7 @@ pub trait BaseConfigCommand: BaseCommand { // Initialize the global file if it's not there, ignoring any warnings or notices if input .borrow() - .get_option("global") + .get_option("global")? .as_bool() .unwrap_or(false) && !self.config_file().unwrap().borrow().exists() @@ -116,21 +116,21 @@ pub trait BaseConfigCommand: BaseCommand { &self, input: std::rc::Rc<std::cell::RefCell<dyn InputInterface>>, config: &Config, - ) -> String { + ) -> anyhow::Result<String> { if input .borrow() - .get_option("global") + .get_option("global")? .as_bool() .unwrap_or(false) { - format!("{}/config.json", config.get("home")) + Ok(format!("{}/config.json", config.get("home"))) } else { - input + Ok(input .borrow() - .get_option("file") + .get_option("file")? .as_string() .map(|s| s.to_string()) - .unwrap_or_else(|| Factory::get_composer_file().unwrap_or_default()) + .unwrap_or_else(|| Factory::get_composer_file().unwrap_or_default())) } } @@ -140,21 +140,21 @@ pub trait BaseConfigCommand: BaseCommand { &self, input: std::rc::Rc<std::cell::RefCell<dyn InputInterface>>, config: &Config, - ) -> String { + ) -> anyhow::Result<String> { if input .borrow() - .get_option("global") + .get_option("global")? .as_bool() .unwrap_or(false) { - format!("{}/auth.json", config.get("home")) + Ok(format!("{}/auth.json", config.get("home"))) } else { - let composer_config = self.get_composer_config_file(input, config); + let composer_config = self.get_composer_config_file(input, config)?; let parent = std::path::Path::new(&composer_config) .parent() .map(|p| p.to_string_lossy().to_string()) .unwrap_or_default(); - format!("{}/auth.json", parent) + Ok(format!("{}/auth.json", parent)) } } } diff --git a/crates/shirabe/src/command/base_dependency_command.rs b/crates/shirabe/src/command/base_dependency_command.rs index 27f3544..1ca8a76 100644 --- a/crates/shirabe/src/command/base_dependency_command.rs +++ b/crates/shirabe/src/command/base_dependency_command.rs @@ -59,7 +59,7 @@ pub trait BaseDependencyCommand: BaseCommand { if input .borrow() - .get_option("locked") + .get_option("locked")? .as_bool() .unwrap_or(false) { @@ -94,7 +94,7 @@ pub trait BaseDependencyCommand: BaseCommand { && (root_pkg.get_requires().len() > 0 || root_pkg.get_dev_requires().len() > 0) { output.borrow().writeln( - "<warning>No dependencies installed. Try running composer install or update, or use --locked.</warning>", + &["<warning>No dependencies installed. Try running composer install or update, or use --locked.</warning>".to_string()], shirabe_external_packages::symfony::console::output::OUTPUT_NORMAL, ); @@ -122,14 +122,14 @@ pub trait BaseDependencyCommand: BaseCommand { let needle = input .borrow() - .get_argument(Self::ARGUMENT_PACKAGE) + .get_argument(Self::ARGUMENT_PACKAGE)? .as_string() .unwrap_or_default() .to_string(); let text_constraint: String = if input.borrow().has_argument(Self::ARGUMENT_CONSTRAINT) { input .borrow() - .get_argument(Self::ARGUMENT_CONSTRAINT) + .get_argument(Self::ARGUMENT_CONSTRAINT)? .as_string() .unwrap_or("*") .to_string() @@ -246,13 +246,13 @@ pub trait BaseDependencyCommand: BaseCommand { let render_tree = input .borrow() - .get_option(Self::OPTION_TREE) + .get_option(Self::OPTION_TREE)? .as_bool() .unwrap_or(false); let recursive = render_tree || input .borrow() - .get_option(Self::OPTION_RECURSIVE) + .get_option(Self::OPTION_RECURSIVE)? .as_bool() .unwrap_or(false); @@ -357,7 +357,7 @@ pub trait BaseDependencyCommand: BaseCommand { let name_with_link = match &package_url { Some(url) => format!( "<href={}>{}</>", - OutputFormatter::escape(url), + OutputFormatter::escape(url).expect("OutputFormatter::escape never fails"), package.get_pretty_name() ), None => package.get_pretty_name().to_string(), @@ -399,12 +399,12 @@ pub trait BaseDependencyCommand: BaseCommand { "blue".to_string(), ]; for color in self.colors() { - let style = OutputFormatterStyle::new(Some(color), None, None); + let style = OutputFormatterStyle::new(Some(color), None, vec![]); output .borrow() .get_formatter() .borrow_mut() - .set_style(color, style); + .set_style(color, Box::new(style)); } } @@ -428,7 +428,7 @@ pub trait BaseDependencyCommand: BaseCommand { let name_with_link = match &package_url { Some(url) => format!( "<href={}>{}</>", - OutputFormatter::escape(url), + OutputFormatter::escape(url).expect("OutputFormatter::escape never fails"), package.get_pretty_name() ), None => package.get_pretty_name().to_string(), diff --git a/crates/shirabe/src/command/bump_command.rs b/crates/shirabe/src/command/bump_command.rs index 92b1331..6f947c2 100644 --- a/crates/shirabe/src/command/bump_command.rs +++ b/crates/shirabe/src/command/bump_command.rs @@ -62,7 +62,7 @@ impl BumpCommand { ) -> Result<i64> { let packages_filter: Vec<String> = input .borrow() - .get_argument("packages") + .get_argument("packages")? .as_list() .map(|l| { l.iter() @@ -73,17 +73,17 @@ impl BumpCommand { let dev_only = input .borrow() - .get_option("dev-only") + .get_option("dev-only")? .as_bool() .unwrap_or(false); let no_dev_only = input .borrow() - .get_option("no-dev-only") + .get_option("no-dev-only")? .as_bool() .unwrap_or(false); let dry_run = input .borrow() - .get_option("dry-run") + .get_option("dry-run")? .as_bool() .unwrap_or(false); let io = self.get_io().clone(); diff --git a/crates/shirabe/src/command/check_platform_reqs_command.rs b/crates/shirabe/src/command/check_platform_reqs_command.rs index 2742f6f..658d049 100644 --- a/crates/shirabe/src/command/check_platform_reqs_command.rs +++ b/crates/shirabe/src/command/check_platform_reqs_command.rs @@ -60,7 +60,7 @@ impl CheckPlatformReqsCommand { let no_dev = input .borrow() - .get_option("no-dev") + .get_option("no-dev")? .as_bool() .unwrap_or(false); @@ -69,7 +69,7 @@ impl CheckPlatformReqsCommand { let installed_repo_base: crate::repository::RepositoryInterfaceHandle = if input .borrow() - .get_option("lock") + .get_option("lock")? .as_bool() .unwrap_or(false) { @@ -248,7 +248,7 @@ impl CheckPlatformReqsCommand { let format = input .borrow() - .get_option("format") + .get_option("format")? .as_string() .unwrap_or("text") .to_string(); diff --git a/crates/shirabe/src/command/config_command.rs b/crates/shirabe/src/command/config_command.rs index 135b47f..342797c 100644 --- a/crates/shirabe/src/command/config_command.rs +++ b/crates/shirabe/src/command/config_command.rs @@ -126,7 +126,7 @@ impl ConfigCommand { self.initialize(input.clone(), output)?; let auth_config_file = - self.get_auth_config_file(input.clone(), &*self.config.as_ref().unwrap().borrow()); + self.get_auth_config_file(input.clone(), &*self.config.as_ref().unwrap().borrow())?; let auth_config_file_jf = std::rc::Rc::new(std::cell::RefCell::new(JsonFile::new( auth_config_file, @@ -137,7 +137,7 @@ impl ConfigCommand { self.auth_config_source = Some(JsonConfigSource::new(auth_config_file_jf, true)); // Initialize the global file if it's not there, ignoring any warnings or notices - if input.borrow().get_option("global").as_bool() == Some(true) + if input.borrow().get_option("global")?.as_bool() == Some(true) && !self.auth_config_file.as_ref().unwrap().borrow().exists() { touch(self.auth_config_file.as_ref().unwrap().borrow().get_path()); @@ -182,7 +182,7 @@ impl ConfigCommand { output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>, ) -> anyhow::Result<i64> { // Open file in editor - if input.borrow().get_option("editor").as_bool() == Some(true) { + if input.borrow().get_option("editor")?.as_bool() == Some(true) { let mut editor = Platform::get_env("EDITOR"); if editor.is_none() || editor.as_deref() == Some("") { if Platform::is_windows() { @@ -202,7 +202,7 @@ impl ConfigCommand { editor = Some(escapeshellcmd(&editor.unwrap())); } - let file = if input.borrow().get_option("auth").as_bool() == Some(true) { + let file = if input.borrow().get_option("auth")?.as_bool() == Some(true) { self.auth_config_file .as_ref() .unwrap() @@ -234,7 +234,7 @@ impl ConfigCommand { return Ok(0); } - if input.borrow().get_option("global").as_bool() != Some(true) { + if input.borrow().get_option("global")?.as_bool() != Some(true) { let config_read = self.config_file.as_ref().unwrap().borrow_mut().read()?; let config_map = match config_read { PhpMixed::Array(m) => m @@ -272,7 +272,7 @@ impl ConfigCommand { } // List the configuration of the file settings - if input.borrow().get_option("list").as_bool() == Some(true) { + if input.borrow().get_option("list")?.as_bool() == Some(true) { let all_map = self.config.as_ref().unwrap().borrow_mut().all(0)?; let raw_map = self.config.as_ref().unwrap().borrow().raw(); let to_mixed = |m: IndexMap<String, PhpMixed>| -> PhpMixed { @@ -283,20 +283,20 @@ impl ConfigCommand { to_mixed(raw_map), output, None, - input.borrow().get_option("source").as_bool() == Some(true), + input.borrow().get_option("source")?.as_bool() == Some(true), ); return Ok(0); } - let setting_key_arg = input.borrow().get_argument("setting-key"); + let setting_key_arg = input.borrow().get_argument("setting-key")?; let setting_key = match setting_key_arg.as_string() { Some(s) => s.to_string(), None => return Ok(0), }; // If the user enters in a config variable, parse it and save to file - let setting_values_raw = input.borrow().get_argument("setting-value"); + let setting_values_raw = input.borrow().get_argument("setting-value")?; let setting_values: Vec<String> = setting_values_raw .as_list() .map(|l| { @@ -305,7 +305,7 @@ impl ConfigCommand { .collect() }) .unwrap_or_default(); - if !setting_values.is_empty() && input.borrow().get_option("unset").as_bool() == Some(true) + if !setting_values.is_empty() && input.borrow().get_option("unset")?.as_bool() == Some(true) { return Err(RuntimeException { message: "You can not combine a setting value with --unset".to_string(), @@ -315,7 +315,8 @@ impl ConfigCommand { } // show the value if no value is provided - if setting_values.is_empty() && input.borrow().get_option("unset").as_bool() != Some(true) { + if setting_values.is_empty() && input.borrow().get_option("unset")?.as_bool() != Some(true) + { let properties: Vec<&'static str> = Self::CONFIGURABLE_PACKAGE_PROPERTIES.to_vec(); let mut properties_defaults: IndexMap<String, PhpMixed> = IndexMap::new(); properties_defaults.insert("type".to_string(), PhpMixed::String("library".to_string())); @@ -421,7 +422,7 @@ impl ConfigCommand { { value = self.config.as_ref().unwrap().borrow_mut().get_with_flags( &setting_key, - if input.borrow().get_option("absolute").as_bool() == Some(true) { + if input.borrow().get_option("absolute")?.as_bool() == Some(true) { 0 } else { Config::RELATIVE_PATHS @@ -504,7 +505,7 @@ impl ConfigCommand { }; let mut source_of_config_value = String::new(); - if input.borrow().get_option("source").as_bool() == Some(true) { + if input.borrow().get_option("source")?.as_bool() == Some(true) { source_of_config_value = format!(" ({})", source); } @@ -542,7 +543,7 @@ impl ConfigCommand { let multi_config_values = build_multi_config_values(); // allow unsetting audit config entirely - if input.borrow().get_option("unset").as_bool() == Some(true) && setting_key == "audit" { + if input.borrow().get_option("unset")?.as_bool() == Some(true) && setting_key == "audit" { self.config_source .as_mut() .unwrap() @@ -551,7 +552,7 @@ impl ConfigCommand { return Ok(0); } - if input.borrow().get_option("unset").as_bool() == Some(true) + if input.borrow().get_option("unset")?.as_bool() == Some(true) && (unique_config_values.contains_key(&setting_key) || multi_config_values.contains_key(&setting_key)) { @@ -596,7 +597,7 @@ impl ConfigCommand { ) .unwrap_or(false) { - if input.borrow().get_option("unset").as_bool() == Some(true) { + if input.borrow().get_option("unset")?.as_bool() == Some(true) { self.config_source .as_mut() .unwrap() @@ -637,7 +638,7 @@ impl ConfigCommand { ) .unwrap_or(false) { - if input.borrow().get_option("unset").as_bool() == Some(true) { + if input.borrow().get_option("unset")?.as_bool() == Some(true) { self.config_source .as_mut() .unwrap() @@ -668,7 +669,7 @@ impl ConfigCommand { let unique_props = build_unique_props(); let multi_props = build_multi_props(); - if input.borrow().get_option("global").as_bool() == Some(true) + if input.borrow().get_option("global")?.as_bool() == Some(true) && (unique_props.contains_key(&setting_key) || multi_props.contains_key(&setting_key) || strpos(&setting_key, "extra.") == Some(0)) @@ -679,7 +680,7 @@ impl ConfigCommand { } .into()); } - if input.borrow().get_option("unset").as_bool() == Some(true) + if input.borrow().get_option("unset")?.as_bool() == Some(true) && (unique_props.contains_key(&setting_key) || multi_props.contains_key(&setting_key)) { self.config_source @@ -709,7 +710,7 @@ impl ConfigCommand { ) .unwrap_or(false) { - if input.borrow().get_option("unset").as_bool() == Some(true) { + if input.borrow().get_option("unset")?.as_bool() == Some(true) { self.config_source .as_mut() .unwrap() @@ -731,7 +732,7 @@ impl ConfigCommand { self.config_source.as_mut().unwrap().add_repository( &matches[1], PhpMixed::Array(repo), - input.borrow().get_option("append").as_bool() == Some(true), + input.borrow().get_option("append")?.as_bool() == Some(true), ); return Ok(0); @@ -747,7 +748,7 @@ impl ConfigCommand { self.config_source.as_mut().unwrap().add_repository( &matches[1], PhpMixed::Bool(false), - input.borrow().get_option("append").as_bool() == Some(true), + input.borrow().get_option("append")?.as_bool() == Some(true), ); return Ok(0); @@ -757,7 +758,7 @@ impl ConfigCommand { self.config_source.as_mut().unwrap().add_repository( &matches[1], value, - input.borrow().get_option("append").as_bool() == Some(true), + input.borrow().get_option("append")?.as_bool() == Some(true), ); return Ok(0); @@ -774,7 +775,7 @@ impl ConfigCommand { // handle extra let mut matches: IndexMap<CaptureKey, String> = IndexMap::new(); if Preg::is_match3("/^extra\\.(.+)/", &setting_key, Some(&mut matches)).unwrap_or(false) { - if input.borrow().get_option("unset").as_bool() == Some(true) { + if input.borrow().get_option("unset")?.as_bool() == Some(true) { self.config_source .as_mut() .unwrap() @@ -784,9 +785,9 @@ impl ConfigCommand { } let mut value = PhpMixed::String(values[0].clone()); - if input.borrow().get_option("json").as_bool() == Some(true) { + if input.borrow().get_option("json")?.as_bool() == Some(true) { value = JsonFile::parse_json(Some(&values[0]), Some("composer.json"))?; - if input.borrow().get_option("merge").as_bool() == Some(true) { + if input.borrow().get_option("merge")?.as_bool() == Some(true) { let current_value_outer = self.config_file.as_ref().unwrap().borrow_mut().read()?; let bits = explode(".", &setting_key); @@ -833,7 +834,7 @@ impl ConfigCommand { // handle suggest let mut matches: IndexMap<CaptureKey, String> = IndexMap::new(); if Preg::is_match3("/^suggest\\.(.+)/", &setting_key, Some(&mut matches)).unwrap_or(false) { - if input.borrow().get_option("unset").as_bool() == Some(true) { + if input.borrow().get_option("unset")?.as_bool() == Some(true) { self.config_source .as_mut() .unwrap() @@ -855,7 +856,7 @@ impl ConfigCommand { setting_key.as_str().into(), &vec!["suggest".to_string(), "extra".to_string()].into(), true, - ) && input.borrow().get_option("unset").as_bool() == Some(true) + ) && input.borrow().get_option("unset")?.as_bool() == Some(true) { self.config_source .as_mut() @@ -869,7 +870,7 @@ impl ConfigCommand { let mut matches: IndexMap<CaptureKey, String> = IndexMap::new(); if Preg::is_match3("/^platform\\.(.+)/", &setting_key, Some(&mut matches)).unwrap_or(false) { - if input.borrow().get_option("unset").as_bool() == Some(true) { + if input.borrow().get_option("unset")?.as_bool() == Some(true) { self.config_source .as_mut() .unwrap() @@ -892,7 +893,8 @@ impl ConfigCommand { } // handle unsetting platform - if setting_key == "platform" && input.borrow().get_option("unset").as_bool() == Some(true) { + if setting_key == "platform" && input.borrow().get_option("unset")?.as_bool() == Some(true) + { self.config_source .as_mut() .unwrap() @@ -911,7 +913,7 @@ impl ConfigCommand { .into(), true, ) { - if input.borrow().get_option("unset").as_bool() == Some(true) { + if input.borrow().get_option("unset")?.as_bool() == Some(true) { self.config_source .as_mut() .unwrap() @@ -926,7 +928,7 @@ impl ConfigCommand { .map(|s| Box::new(PhpMixed::String(s.clone()))) .collect(), ); - if input.borrow().get_option("json").as_bool() == Some(true) { + if input.borrow().get_option("json")?.as_bool() == Some(true) { value = JsonFile::parse_json(Some(&values[0]), Some("composer.json"))?; if !is_array(&value) { return Err(RuntimeException { @@ -937,7 +939,7 @@ impl ConfigCommand { } } - if input.borrow().get_option("merge").as_bool() == Some(true) { + if input.borrow().get_option("merge")?.as_bool() == Some(true) { let current_config = self.config_file.as_ref().unwrap().borrow_mut().read()?; let key_suffix = str_replace("audit.", "", &setting_key); let current_value = current_config @@ -990,7 +992,7 @@ impl ConfigCommand { // handle auth let mut matches: IndexMap<CaptureKey, String> = IndexMap::new(); if Preg::is_match3("/^(bitbucket-oauth|github-oauth|gitlab-oauth|gitlab-token|http-basic|custom-headers|bearer|forgejo-token)\\.(.+)/", &setting_key, Some(&mut matches)).unwrap_or(false) { - if input.borrow().get_option("unset").as_bool() == Some(true) { + if input.borrow().get_option("unset")?.as_bool() == Some(true) { self.auth_config_source.as_mut().unwrap().remove_config_setting(&format!("{}.{}", matches[1], matches[2])); self.config_source.as_mut().unwrap().remove_config_setting(&format!("{}.{}", matches[1], matches[2])); @@ -1096,7 +1098,7 @@ impl ConfigCommand { // handle script let mut matches: IndexMap<CaptureKey, String> = IndexMap::new(); if Preg::is_match3("/^scripts\\.(.+)/", &setting_key, Some(&mut matches)).unwrap_or(false) { - if input.borrow().get_option("unset").as_bool() == Some(true) { + if input.borrow().get_option("unset")?.as_bool() == Some(true) { self.config_source .as_mut() .unwrap() @@ -1124,7 +1126,7 @@ impl ConfigCommand { } // handle unsetting other top level properties - if input.borrow().get_option("unset").as_bool() == Some(true) { + if input.borrow().get_option("unset")?.as_bool() == Some(true) { self.config_source .as_mut() .unwrap() diff --git a/crates/shirabe/src/command/create_project_command.rs b/crates/shirabe/src/command/create_project_command.rs index ba384d5..247941b 100644 --- a/crates/shirabe/src/command/create_project_command.rs +++ b/crates/shirabe/src/command/create_project_command.rs @@ -119,12 +119,12 @@ impl CreateProjectCommand { let (prefer_source, prefer_dist) = self.get_preferred_install_options(&config.borrow(), input.clone(), true)?; - if input.borrow().get_option("dev").as_bool().unwrap_or(false) { + if input.borrow().get_option("dev")?.as_bool().unwrap_or(false) { io.write_error("<warning>You are using the deprecated option \"dev\". Dev packages are installed by default now.</warning>"); } if input .borrow() - .get_option("no-custom-installers") + .get_option("no-custom-installers")? .as_bool() .unwrap_or(false) { @@ -135,9 +135,9 @@ impl CreateProjectCommand { } if input.borrow().is_interactive() - && input.borrow().get_option("ask").as_bool().unwrap_or(false) + && input.borrow().get_option("ask")?.as_bool().unwrap_or(false) { - let package = input.borrow().get_argument("package"); + let package = input.borrow().get_argument("package")?; if package.is_null() { return Err(RuntimeException { message: "Not enough arguments (missing: \"package\").".to_string(), @@ -156,8 +156,8 @@ impl CreateProjectCommand { .set_argument("directory", io.ask(prompt, PhpMixed::Null)); } - let repository_opt = input.borrow().get_option("repository"); - let repository_url_opt = input.borrow().get_option("repository-url"); + let repository_opt = input.borrow().get_option("repository")?; + let repository_url_opt = input.borrow().get_option("repository-url")?; let repositories = if repository_opt .as_list() .map(|l| l.len() > 0) @@ -174,61 +174,61 @@ impl CreateProjectCommand { input.clone(), input .borrow() - .get_argument("package") + .get_argument("package")? .as_string() .map(|s| s.to_string()), input .borrow() - .get_argument("directory") + .get_argument("directory")? .as_string() .map(|s| s.to_string()), input .borrow() - .get_argument("version") + .get_argument("version")? .as_string() .map(|s| s.to_string()), input .borrow() - .get_option("stability") + .get_option("stability")? .as_string() .map(|s| s.to_string()), prefer_source, prefer_dist, !input .borrow() - .get_option("no-dev") + .get_option("no-dev")? .as_bool() .unwrap_or(false), repositories, input .borrow() - .get_option("no-plugins") + .get_option("no-plugins")? .as_bool() .unwrap_or(false), input .borrow() - .get_option("no-scripts") + .get_option("no-scripts")? .as_bool() .unwrap_or(false), input .borrow() - .get_option("no-progress") + .get_option("no-progress")? .as_bool() .unwrap_or(false), input .borrow() - .get_option("no-install") + .get_option("no-install")? .as_bool() .unwrap_or(false), Some(self.get_platform_requirement_filter(input.clone())?), !input .borrow() - .get_option("no-secure-http") + .get_option("no-secure-http")? .as_bool() .unwrap_or(false), input .borrow() - .get_option("add-repository") + .get_option("add-repository")? .as_bool() .unwrap_or(false), ) @@ -478,9 +478,9 @@ impl CreateProjectCommand { } let mut has_vcs = installed_from_vcs; - let remove_vcs = !input.borrow().get_option("keep-vcs").as_bool().unwrap_or(false) + let remove_vcs = !input.borrow().get_option("keep-vcs")?.as_bool().unwrap_or(false) && installed_from_vcs - && (input.borrow().get_option("remove-vcs").as_bool().unwrap_or(false) + && (input.borrow().get_option("remove-vcs")?.as_bool().unwrap_or(false) || !io.is_interactive() || io.ask_confirmation( "<info>Do you want to remove the existing VCS (.git, .svn..) history?</info> [<comment>y,n</comment>]? ".to_string(), diff --git a/crates/shirabe/src/command/dump_autoload_command.rs b/crates/shirabe/src/command/dump_autoload_command.rs index de0a57d..ae51eac 100644 --- a/crates/shirabe/src/command/dump_autoload_command.rs +++ b/crates/shirabe/src/command/dump_autoload_command.rs @@ -86,7 +86,7 @@ impl DumpAutoloadCommand { let optimize = input .borrow() - .get_option("optimize") + .get_option("optimize")? .as_bool() .unwrap_or(false) || config @@ -96,7 +96,7 @@ impl DumpAutoloadCommand { .unwrap_or(false); let authoritative = input .borrow() - .get_option("classmap-authoritative") + .get_option("classmap-authoritative")? .as_bool() .unwrap_or(false) || config @@ -106,11 +106,15 @@ impl DumpAutoloadCommand { .unwrap_or(false); let apcu_prefix = input .borrow() - .get_option("apcu-prefix") + .get_option("apcu-prefix")? .as_string() .map(|s| s.to_string()); let apcu = apcu_prefix.is_some() - || input.borrow().get_option("apcu").as_bool().unwrap_or(false) + || input + .borrow() + .get_option("apcu")? + .as_bool() + .unwrap_or(false) || config .borrow_mut() .get("apcu-autoloader") @@ -119,7 +123,7 @@ impl DumpAutoloadCommand { if input .borrow() - .get_option("strict-psr") + .get_option("strict-psr")? .as_bool() .unwrap_or(false) && !optimize @@ -133,7 +137,7 @@ impl DumpAutoloadCommand { } if input .borrow() - .get_option("strict-ambiguous") + .get_option("strict-ambiguous")? .as_bool() .unwrap_or(false) && !optimize @@ -160,7 +164,7 @@ impl DumpAutoloadCommand { let platform_requirement_filter = self.get_platform_requirement_filter(input.clone())?; if input .borrow() - .get_option("dry-run") + .get_option("dry-run")? .as_bool() .unwrap_or(false) { @@ -171,7 +175,7 @@ impl DumpAutoloadCommand { } if input .borrow() - .get_option("no-dev") + .get_option("no-dev")? .as_bool() .unwrap_or(false) { @@ -180,10 +184,10 @@ impl DumpAutoloadCommand { .borrow_mut() .set_dev_mode(false); } - if input.borrow().get_option("dev").as_bool().unwrap_or(false) { + if input.borrow().get_option("dev")?.as_bool().unwrap_or(false) { if input .borrow() - .get_option("no-dev") + .get_option("no-dev")? .as_bool() .unwrap_or(false) { @@ -218,7 +222,7 @@ impl DumpAutoloadCommand { .set_platform_requirement_filter(platform_requirement_filter); let strict_ambiguous = input .borrow() - .get_option("strict-ambiguous") + .get_option("strict-ambiguous")? .as_bool() .unwrap_or(false); @@ -266,7 +270,7 @@ impl DumpAutoloadCommand { if missing_dependencies || (input .borrow() - .get_option("strict-psr") + .get_option("strict-psr")? .as_bool() .unwrap_or(false) && !class_map.get_psr_violations().is_empty()) @@ -276,7 +280,7 @@ impl DumpAutoloadCommand { if input .borrow() - .get_option("strict-ambiguous") + .get_option("strict-ambiguous")? .as_bool() .unwrap_or(false) && !class_map.get_ambiguous_classes(None)?.is_empty() diff --git a/crates/shirabe/src/command/exec_command.rs b/crates/shirabe/src/command/exec_command.rs index 3aec024..3c9cee2 100644 --- a/crates/shirabe/src/command/exec_command.rs +++ b/crates/shirabe/src/command/exec_command.rs @@ -49,8 +49,12 @@ impl ExecCommand { return Ok(()); } - if input.borrow().get_argument("binary").as_string().is_some() - || input.borrow().get_option("list").as_bool().unwrap_or(false) + if input.borrow().get_argument("binary")?.as_string().is_some() + || input + .borrow() + .get_option("list")? + .as_bool() + .unwrap_or(false) { return Ok(()); } @@ -82,8 +86,12 @@ impl ExecCommand { ) -> Result<i64> { let composer = self.require_composer(None, None)?; - if input.borrow().get_option("list").as_bool().unwrap_or(false) - || input.borrow().get_argument("binary").as_string().is_none() + if input + .borrow() + .get_option("list")? + .as_bool() + .unwrap_or(false) + || input.borrow().get_argument("binary")?.as_string().is_none() { let bins = self.get_binaries(true)?; if bins.is_empty() { @@ -115,7 +123,7 @@ impl ExecCommand { let binary = input .borrow() - .get_argument("binary") + .get_argument("binary")? .as_string() .unwrap_or("") .to_string(); @@ -141,7 +149,7 @@ impl ExecCommand { let args = input .borrow() - .get_argument("args") + .get_argument("args")? .as_list() .map(|l| { l.iter() diff --git a/crates/shirabe/src/command/fund_command.rs b/crates/shirabe/src/command/fund_command.rs index 277d3e4..6223def 100644 --- a/crates/shirabe/src/command/fund_command.rs +++ b/crates/shirabe/src/command/fund_command.rs @@ -122,7 +122,7 @@ impl FundCommand { let format = input .borrow() - .get_option("format") + .get_option("format")? .as_string() .unwrap_or("text") .to_string(); @@ -150,7 +150,7 @@ impl FundCommand { } io.write(&format!( " <href={}>{}</>", - OutputFormatter::escape(url), + OutputFormatter::escape(url)?, url )); } diff --git a/crates/shirabe/src/command/global_command.rs b/crates/shirabe/src/command/global_command.rs index f066721..e978dff 100644 --- a/crates/shirabe/src/command/global_command.rs +++ b/crates/shirabe/src/command/global_command.rs @@ -4,10 +4,12 @@ use std::path::Path; use anyhow::Result; use shirabe_external_packages::composer::pcre::Preg; +use shirabe_external_packages::symfony::console::input::ArgvInput; +use shirabe_external_packages::symfony::console::input::ArrayInput; use shirabe_external_packages::symfony::console::input::InputInterface; use shirabe_external_packages::symfony::console::input::StringInput; use shirabe_external_packages::symfony::console::output::OutputInterface; -use shirabe_php_shim::{LogicException, RuntimeException, chdir}; +use shirabe_php_shim::{AsAny, LogicException, RuntimeException, chdir}; use crate::command::{BaseCommand, BaseCommandData, HasBaseCommandData}; use crate::console::input::InputArgument; @@ -56,12 +58,35 @@ impl GlobalCommand { ); } + // TODO remove for Symfony 6+ as it is then in the interface. + // Mirrors PHP's `method_exists($input, '__toString')` guard followed by + // `$input->__toString()`. `InputInterface` does not declare `__toString`, so the + // concrete stringable input types are matched explicitly. + fn input_to_string(input: &dyn InputInterface) -> Result<String> { + let input_any = input.as_any(); + if let Some(argv_input) = input_any.downcast_ref::<ArgvInput>() { + Ok(argv_input.to_string()) + } else if let Some(array_input) = input_any.downcast_ref::<ArrayInput>() { + Ok(array_input.to_string()) + } else if input_any.is::<StringInput>() { + // StringInput's stringification is not exposed by shirabe-external-packages + // (its inner ArgvInput is pub(crate) and it defines no public __toString). + todo!("StringInput::__toString is not accessible from the shirabe crate") + } else { + Err(LogicException { + message: "Expected an Input instance that is stringable".to_string(), + code: 0, + } + .into()) + } + } + pub fn run( &mut self, input: std::rc::Rc<std::cell::RefCell<dyn InputInterface>>, output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>, ) -> Result<i64> { - let tokens = Preg::split(r"{\s+}", &input.borrow().to_input_string())?; + let tokens = Preg::split(r"{\s+}", &Self::input_to_string(&*input.borrow())?)?; let mut args: Vec<String> = vec![]; for token in &tokens { if !token.is_empty() && !token.starts_with('-') { @@ -123,12 +148,12 @@ impl GlobalCommand { let new_input_str = Preg::replace4( r"{\bg(?:l(?:o(?:b(?:a(?:l)?)?)?)?)?\b}", "", - &input.borrow().to_input_string(), + &Self::input_to_string(&*input.borrow())?, 1, )?; self.get_application()?.reset_composer(); - Ok(StringInput::new(&new_input_str)) + Ok(StringInput::new(&new_input_str)?) } pub fn is_proxy_command(&self) -> bool { diff --git a/crates/shirabe/src/command/home_command.rs b/crates/shirabe/src/command/home_command.rs index 6ddf0ca..029eaab 100644 --- a/crates/shirabe/src/command/home_command.rs +++ b/crates/shirabe/src/command/home_command.rs @@ -78,7 +78,7 @@ impl HomeCommand { let packages: Vec<String> = input .borrow() - .get_argument("packages") + .get_argument("packages")? .as_list() .map(|l| { l.iter() @@ -98,10 +98,14 @@ impl HomeCommand { let show_homepage = input .borrow() - .get_option("homepage") + .get_option("homepage")? + .as_bool() + .unwrap_or(false); + let show_only = input + .borrow() + .get_option("show")? .as_bool() .unwrap_or(false); - let show_only = input.borrow().get_option("show").as_bool().unwrap_or(false); for package_name in &packages { let mut handled = false; diff --git a/crates/shirabe/src/command/init_command.rs b/crates/shirabe/src/command/init_command.rs index 1bafd74..aea9faf 100644 --- a/crates/shirabe/src/command/init_command.rs +++ b/crates/shirabe/src/command/init_command.rs @@ -5,6 +5,7 @@ use anyhow::Result; use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::{CaptureKey, Preg}; use shirabe_external_packages::composer::spdx_licenses::SpdxLicenses; +use shirabe_external_packages::symfony::console::helper::FormatBlockMessages; use shirabe_external_packages::symfony::console::helper::FormatterHelper; use shirabe_external_packages::symfony::console::input::ArrayInput; use shirabe_external_packages::symfony::console::input::InputInterface; @@ -189,7 +190,7 @@ impl InitCommand { let repositories: Vec<String> = input .borrow() - .get_option("repository") + .get_option("repository")? .as_list() .map(|l| { l.iter() @@ -287,7 +288,7 @@ impl InitCommand { autoload_path = Some(ap.clone()); let name = input .borrow() - .get_option("name") + .get_option("name")? .as_string() .unwrap_or("") .to_string(); @@ -395,7 +396,7 @@ impl InitCommand { if autoload_path.is_some() { let name = input .borrow() - .get_option("name") + .get_option("name")? .as_string() .unwrap_or("") .to_string(); @@ -420,11 +421,11 @@ impl InitCommand { &mut self, input: std::rc::Rc<std::cell::RefCell<dyn InputInterface>>, output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>, - ) { - self.initialize(input.clone(), output); + ) -> anyhow::Result<()> { + self.initialize(input.clone(), output)?; if !input.borrow().is_interactive() { - if input.borrow().get_option("name").is_null() { + if input.borrow().get_option("name")?.is_null() { let name = self.get_default_package_name(); input .borrow_mut() @@ -432,7 +433,7 @@ impl InitCommand { .expect("name option is defined"); } - if input.borrow().get_option("author").is_null() { + if input.borrow().get_option("author")?.is_null() { let author = self.get_default_author(); input .borrow_mut() @@ -440,6 +441,8 @@ impl InitCommand { .expect("author option is defined"); } } + + Ok(()) } pub(crate) fn interact( @@ -459,7 +462,7 @@ impl InitCommand { // initialize repos if configured let repositories: Vec<String> = input .borrow() - .get_option("repository") + .get_option("repository")? .as_list() .map(|l| { l.iter() @@ -532,7 +535,9 @@ impl InitCommand { &format!( "\n{}\n", formatter.format_block( - &["Welcome to the Composer config generator"], + FormatBlockMessages::String( + "Welcome to the Composer config generator".to_string(), + ), "bg=blue;fg=white", true, ) @@ -550,7 +555,7 @@ impl InitCommand { let mut name = input .borrow() - .get_option("name") + .get_option("name")? .as_string() .map(|s| s.to_string()) .unwrap_or_else(|| self.get_default_package_name()); @@ -598,7 +603,7 @@ impl InitCommand { let description = input .borrow() - .get_option("description") + .get_option("description")? .as_string() .map(|s| s.to_string()); let description_default = description.clone(); @@ -615,7 +620,7 @@ impl InitCommand { let author = input .borrow() - .get_option("author") + .get_option("author")? .as_string() .map(|s| s.to_string()) .unwrap_or_else(|| self.get_default_author().unwrap_or_default()); @@ -663,7 +668,7 @@ impl InitCommand { let minimum_stability = input .borrow() - .get_option("stability") + .get_option("stability")? .as_string() .map(|s| s.to_string()); let minimum_stability_default = minimum_stability.clone(); @@ -710,7 +715,7 @@ impl InitCommand { .borrow_mut() .set_option("stability", minimum_stability_value); - let type_val = input.borrow().get_option("type"); + let type_val = input.borrow().get_option("type")?; let type_str = type_val.as_string().unwrap_or("").to_string(); let mut type_value = io.ask( format!( @@ -726,7 +731,7 @@ impl InitCommand { let mut license = input .borrow() - .get_option("license") + .get_option("license")? .as_string() .map(|s| s.to_string()); if license.is_none() { @@ -792,7 +797,7 @@ impl InitCommand { let question = "Would you like to define your dependencies (require) interactively [<comment>yes</comment>]? ".to_string(); let require: Vec<String> = input .borrow() - .get_option("require") + .get_option("require")? .as_list() .map(|l| { l.iter() @@ -826,7 +831,7 @@ impl InitCommand { let question = "Would you like to define your dev dependencies (require-dev) interactively [<comment>yes</comment>]? ".to_string(); let require_dev: Vec<String> = input .borrow() - .get_option("require-dev") + .get_option("require-dev")? .as_list() .map(|l| { l.iter() @@ -861,14 +866,14 @@ impl InitCommand { // --autoload - input and validation let mut autoload = input .borrow() - .get_option("autoload") + .get_option("autoload")? .as_string() .map(|s| s.to_string()) .filter(|s| !s.is_empty()) .unwrap_or_else(|| "src/".to_string()); let name_str = input .borrow() - .get_option("name") + .get_option("name")? .as_string() .unwrap_or("") .to_string(); @@ -1098,7 +1103,7 @@ impl InitCommand { // TODO(phase-c): Application::find returns PhpMixed (the Symfony command registry is a // todo!() stub), so the resolved command's run() cannot be invoked until the typed // command registry is modelled. - let _ = ArrayInput::new(IndexMap::new(), None); + let _ = ArrayInput::new(vec![], None); let _ = output; Ok(()) }); @@ -1121,7 +1126,7 @@ impl InitCommand { // PHP: $command->run(new ArrayInput([]), $output); // TODO(phase-c): same blocker as update_dependencies — Application::find returns // PhpMixed (Symfony command registry todo!() stub), so run() cannot be invoked. - let _ = ArrayInput::new(IndexMap::new(), None); + let _ = ArrayInput::new(vec![], None); let _ = output; Ok(()) }); diff --git a/crates/shirabe/src/command/install_command.rs b/crates/shirabe/src/command/install_command.rs index f491820..182f855 100644 --- a/crates/shirabe/src/command/install_command.rs +++ b/crates/shirabe/src/command/install_command.rs @@ -69,19 +69,19 @@ impl InstallCommand { ) -> Result<i64> { let io = self.get_io().clone(); - if input.borrow().get_option("dev").as_bool().unwrap_or(false) { + if input.borrow().get_option("dev")?.as_bool().unwrap_or(false) { io.write_error("<warning>You are using the deprecated option \"--dev\". It has no effect and will break in Composer 3.</warning>"); } if input .borrow() - .get_option("no-suggest") + .get_option("no-suggest")? .as_bool() .unwrap_or(false) { io.write_error("<warning>You are using the deprecated option \"--no-suggest\". It has no effect and will break in Composer 3.</warning>"); } - let args = input.borrow().get_argument("packages"); + let args = input.borrow().get_argument("packages")?; let args_vec: Vec<String> = args .as_list() .map(|l| { @@ -101,7 +101,7 @@ impl InstallCommand { if input .borrow() - .get_option("no-install") + .get_option("no-install")? .as_bool() .unwrap_or(false) { @@ -132,7 +132,7 @@ impl InstallCommand { let optimize = input .borrow() - .get_option("optimize-autoloader") + .get_option("optimize-autoloader")? .as_bool() .unwrap_or(false) || config @@ -142,7 +142,7 @@ impl InstallCommand { .unwrap_or(false); let authoritative = input .borrow() - .get_option("classmap-authoritative") + .get_option("classmap-authoritative")? .as_bool() .unwrap_or(false) || config @@ -152,13 +152,13 @@ impl InstallCommand { .unwrap_or(false); let apcu_prefix = input .borrow() - .get_option("apcu-autoloader-prefix") + .get_option("apcu-autoloader-prefix")? .as_string() .map(|s| s.to_string()); let apcu = apcu_prefix.is_some() || input .borrow() - .get_option("apcu-autoloader") + .get_option("apcu-autoloader")? .as_bool() .unwrap_or(false) || config @@ -173,7 +173,7 @@ impl InstallCommand { .set_output_progress( !input .borrow() - .get_option("no-progress") + .get_option("no-progress")? .as_bool() .unwrap_or(false), ); @@ -182,21 +182,21 @@ impl InstallCommand { .set_dry_run( input .borrow() - .get_option("dry-run") + .get_option("dry-run")? .as_bool() .unwrap_or(false), ) .set_download_only( input .borrow() - .get_option("download-only") + .get_option("download-only")? .as_bool() .unwrap_or(false), ) .set_verbose( input .borrow() - .get_option("verbose") + .get_option("verbose")? .as_bool() .unwrap_or(false), ) @@ -205,14 +205,14 @@ impl InstallCommand { .set_dev_mode( !input .borrow() - .get_option("no-dev") + .get_option("no-dev")? .as_bool() .unwrap_or(false), ) .set_dump_autoloader( !input .borrow() - .get_option("no-autoloader") + .get_option("no-autoloader")? .as_bool() .unwrap_or(false), ) @@ -226,14 +226,14 @@ impl InstallCommand { .set_error_on_audit( input .borrow() - .get_option("audit") + .get_option("audit")? .as_bool() .unwrap_or(false), ); if input .borrow() - .get_option("no-plugins") + .get_option("no-plugins")? .as_bool() .unwrap_or(false) { diff --git a/crates/shirabe/src/command/licenses_command.rs b/crates/shirabe/src/command/licenses_command.rs index b8fa01d..5be2b41 100644 --- a/crates/shirabe/src/command/licenses_command.rs +++ b/crates/shirabe/src/command/licenses_command.rs @@ -8,6 +8,7 @@ use shirabe_external_packages::symfony::console::formatter::OutputFormatter; use shirabe_external_packages::symfony::console::helper::Table; use shirabe_external_packages::symfony::console::input::InputInterface; use shirabe_external_packages::symfony::console::output::OutputInterface; +use shirabe_external_packages::symfony::console::style::StyleInterface; use shirabe_external_packages::symfony::console::style::SymfonyStyle; use shirabe_php_shim::{PhpMixed, RuntimeException, UnexpectedValueException}; @@ -98,7 +99,7 @@ impl LicensesCommand { let packages = if input .borrow() - .get_option("locked") + .get_option("locked")? .as_bool() .unwrap_or(false) { @@ -112,7 +113,7 @@ impl LicensesCommand { } let no_dev = input .borrow() - .get_option("no-dev") + .get_option("no-dev")? .as_bool() .unwrap_or(false); let repo = locker.get_locked_repository(!no_dev)?; @@ -124,7 +125,7 @@ impl LicensesCommand { if input .borrow() - .get_option("no-dev") + .get_option("no-dev")? .as_bool() .unwrap_or(false) { @@ -146,7 +147,7 @@ impl LicensesCommand { let format = input .borrow() - .get_option("format") + .get_option("format")? .as_string() .unwrap_or("text") .to_string(); @@ -171,7 +172,7 @@ impl LicensesCommand { io.write(""); let mut table = Table::new(output); - table.set_style("compact"); + table.set_style(shirabe_php_shim::PhpMixed::from("compact"))??; table.set_headers(vec![ PhpMixed::String("Name".to_string()), PhpMixed::String("Version".to_string()), @@ -182,7 +183,7 @@ impl LicensesCommand { let name = if let Some(link) = link { format!( "<href={}>{}</>", - OutputFormatter::escape(&link), + OutputFormatter::escape(&link)?, package.get_pretty_name() ) } else { diff --git a/crates/shirabe/src/command/outdated_command.rs b/crates/shirabe/src/command/outdated_command.rs index 8eb67e6..c7ea773 100644 --- a/crates/shirabe/src/command/outdated_command.rs +++ b/crates/shirabe/src/command/outdated_command.rs @@ -62,7 +62,7 @@ impl OutdatedCommand { if input .borrow() - .get_option("no-interaction") + .get_option("no-interaction")? .as_bool() .unwrap_or(false) { @@ -70,7 +70,7 @@ impl OutdatedCommand { } if input .borrow() - .get_option("no-plugins") + .get_option("no-plugins")? .as_bool() .unwrap_or(false) { @@ -78,7 +78,7 @@ impl OutdatedCommand { } if input .borrow() - .get_option("no-scripts") + .get_option("no-scripts")? .as_bool() .unwrap_or(false) { @@ -86,30 +86,30 @@ impl OutdatedCommand { } if input .borrow() - .get_option("no-cache") + .get_option("no-cache")? .as_bool() .unwrap_or(false) { args.insert("--no-cache".to_string(), PhpMixed::Bool(true)); } - if !input.borrow().get_option("all").as_bool().unwrap_or(false) { + if !input.borrow().get_option("all")?.as_bool().unwrap_or(false) { args.insert("--outdated".to_string(), PhpMixed::Bool(true)); } if input .borrow() - .get_option("direct") + .get_option("direct")? .as_bool() .unwrap_or(false) { args.insert("--direct".to_string(), PhpMixed::Bool(true)); } - let package_arg = input.borrow().get_argument("package"); + let package_arg = input.borrow().get_argument("package")?; if !matches!(package_arg, PhpMixed::Null) { args.insert("package".to_string(), package_arg); } if input .borrow() - .get_option("strict") + .get_option("strict")? .as_bool() .unwrap_or(false) { @@ -117,7 +117,7 @@ impl OutdatedCommand { } if input .borrow() - .get_option("major-only") + .get_option("major-only")? .as_bool() .unwrap_or(false) { @@ -125,7 +125,7 @@ impl OutdatedCommand { } if input .borrow() - .get_option("minor-only") + .get_option("minor-only")? .as_bool() .unwrap_or(false) { @@ -133,7 +133,7 @@ impl OutdatedCommand { } if input .borrow() - .get_option("patch-only") + .get_option("patch-only")? .as_bool() .unwrap_or(false) { @@ -141,7 +141,7 @@ impl OutdatedCommand { } if input .borrow() - .get_option("locked") + .get_option("locked")? .as_bool() .unwrap_or(false) { @@ -149,7 +149,7 @@ impl OutdatedCommand { } if input .borrow() - .get_option("no-dev") + .get_option("no-dev")? .as_bool() .unwrap_or(false) { @@ -157,7 +157,7 @@ impl OutdatedCommand { } if input .borrow() - .get_option("sort-by-age") + .get_option("sort-by-age")? .as_bool() .unwrap_or(false) { @@ -165,20 +165,25 @@ impl OutdatedCommand { } args.insert( "--ignore-platform-req".to_string(), - input.borrow().get_option("ignore-platform-req"), + input.borrow().get_option("ignore-platform-req")?, ); if input .borrow() - .get_option("ignore-platform-reqs") + .get_option("ignore-platform-reqs")? .as_bool() .unwrap_or(false) { args.insert("--ignore-platform-reqs".to_string(), PhpMixed::Bool(true)); } - args.insert("--format".to_string(), input.borrow().get_option("format")); - args.insert("--ignore".to_string(), input.borrow().get_option("ignore")); + args.insert("--format".to_string(), input.borrow().get_option("format")?); + args.insert("--ignore".to_string(), input.borrow().get_option("ignore")?); - let input = ArrayInput::new(args, None); + let input = ArrayInput::new( + args.into_iter() + .map(|(k, v)| (PhpMixed::String(k), v)) + .collect(), + None, + )?; let input: std::rc::Rc<std::cell::RefCell<dyn InputInterface>> = std::rc::Rc::new(std::cell::RefCell::new(input)); diff --git a/crates/shirabe/src/command/package_discovery_trait.rs b/crates/shirabe/src/command/package_discovery_trait.rs index b5e6cd5..aa12719 100644 --- a/crates/shirabe/src/command/package_discovery_trait.rs +++ b/crates/shirabe/src/command/package_discovery_trait.rs @@ -123,6 +123,7 @@ pub trait PackageDiscoveryTrait { &input .borrow() .get_option("stability") + .expect("get_minimum_stability returns String, not Result; stability option is guaranteed present by the has_option guard above") .as_string() .map(|s| s.to_string()) .unwrap_or_else(|| "stable".to_string()), diff --git a/crates/shirabe/src/command/reinstall_command.rs b/crates/shirabe/src/command/reinstall_command.rs index e6cbac1..48f48a5 100644 --- a/crates/shirabe/src/command/reinstall_command.rs +++ b/crates/shirabe/src/command/reinstall_command.rs @@ -74,9 +74,9 @@ impl ReinstallCommand { let mut packages_to_reinstall: Vec<crate::package::PackageInterfaceHandle> = vec![]; let mut package_names_to_reinstall: Vec<String> = vec![]; - let type_option = input.borrow().get_option("type"); + let type_option = input.borrow().get_option("type")?; let type_count = type_option.as_list().map_or(0, |l| l.len()); - let packages_arg = input.borrow().get_argument("packages"); + let packages_arg = input.borrow().get_argument("packages")?; let packages_count = packages_arg.as_list().map_or(0, |l| l.len()); if type_count > 0 { @@ -198,13 +198,13 @@ impl ReinstallCommand { installation_manager.borrow_mut().set_output_progress( !input .borrow() - .get_option("no-progress") + .get_option("no-progress")? .as_bool() .unwrap_or(false), ); if input .borrow() - .get_option("no-plugins") + .get_option("no-plugins")? .as_bool() .unwrap_or(false) { @@ -245,13 +245,13 @@ impl ReinstallCommand { if !input .borrow() - .get_option("no-autoloader") + .get_option("no-autoloader")? .as_bool() .unwrap_or(false) { let optimize = input .borrow() - .get_option("optimize-autoloader") + .get_option("optimize-autoloader")? .as_bool() .unwrap_or(false) || config @@ -261,7 +261,7 @@ impl ReinstallCommand { .unwrap_or(false); let authoritative = input .borrow() - .get_option("classmap-authoritative") + .get_option("classmap-authoritative")? .as_bool() .unwrap_or(false) || config @@ -271,13 +271,13 @@ impl ReinstallCommand { .unwrap_or(false); let apcu_prefix = input .borrow() - .get_option("apcu-autoloader-prefix") + .get_option("apcu-autoloader-prefix")? .as_string() .map(|s| s.to_string()); let apcu = apcu_prefix.is_some() || input .borrow() - .get_option("apcu-autoloader") + .get_option("apcu-autoloader")? .as_bool() .unwrap_or(false) || config diff --git a/crates/shirabe/src/command/remove_command.rs b/crates/shirabe/src/command/remove_command.rs index 6cd2424..b81b7ca 100644 --- a/crates/shirabe/src/command/remove_command.rs +++ b/crates/shirabe/src/command/remove_command.rs @@ -163,25 +163,27 @@ impl RemoveCommand { ) -> anyhow::Result<i64> { if input .borrow() - .get_argument("packages") + .get_argument("packages")? .as_list() .map(|l| l.is_empty()) .unwrap_or(true) && !input .borrow() - .get_option("unused") + .get_option("unused")? .as_bool() .unwrap_or(false) { - return Err(anyhow::anyhow!(InvalidArgumentException { - message: "Not enough arguments (missing: \"packages\").".to_string(), - code: 0, - })); + return Err(anyhow::anyhow!(InvalidArgumentException( + shirabe_php_shim::InvalidArgumentException { + message: "Not enough arguments (missing: \"packages\").".to_string(), + code: 0, + } + ))); } let mut packages: Vec<String> = input .borrow() - .get_argument("packages") + .get_argument("packages")? .as_list() .map(|l| { l.iter() @@ -192,7 +194,7 @@ impl RemoveCommand { if input .borrow() - .get_option("unused") + .get_option("unused")? .as_bool() .unwrap_or(false) { @@ -276,12 +278,12 @@ impl RemoveCommand { false, ); - let r#type = if input.borrow().get_option("dev").as_bool().unwrap_or(false) { + let r#type = if input.borrow().get_option("dev")?.as_bool().unwrap_or(false) { "require-dev" } else { "require" }; - let alt_type = if !input.borrow().get_option("dev").as_bool().unwrap_or(false) { + let alt_type = if !input.borrow().get_option("dev")?.as_bool().unwrap_or(false) { "require-dev" } else { "require" @@ -290,7 +292,7 @@ impl RemoveCommand { if input .borrow() - .get_option("update-with-dependencies") + .get_option("update-with-dependencies")? .as_bool() .unwrap_or(false) { @@ -317,7 +319,7 @@ impl RemoveCommand { let dry_run = input .borrow() - .get_option("dry-run") + .get_option("dry-run")? .as_bool() .unwrap_or(false); let mut to_remove: IndexMap<String, Vec<String>> = IndexMap::new(); @@ -447,7 +449,7 @@ impl RemoveCommand { if input .borrow() - .get_option("no-update") + .get_option("no-update")? .as_bool() .unwrap_or(false) { @@ -534,7 +536,7 @@ impl RemoveCommand { .set_output_progress( !input .borrow() - .get_option("no-progress") + .get_option("no-progress")? .as_bool() .unwrap_or(false), ); @@ -543,12 +545,12 @@ impl RemoveCommand { let update_dev_mode = !input .borrow() - .get_option("update-no-dev") + .get_option("update-no-dev")? .as_bool() .unwrap_or(false); let optimize = input .borrow() - .get_option("optimize-autoloader") + .get_option("optimize-autoloader")? .as_bool() .unwrap_or(false) || composer @@ -559,7 +561,7 @@ impl RemoveCommand { .unwrap_or(false); let authoritative = input .borrow() - .get_option("classmap-authoritative") + .get_option("classmap-authoritative")? .as_bool() .unwrap_or(false) || composer @@ -570,13 +572,13 @@ impl RemoveCommand { .unwrap_or(false); let apcu_prefix = input .borrow() - .get_option("apcu-autoloader-prefix") + .get_option("apcu-autoloader-prefix")? .as_string() .map(|s| s.to_string()); let apcu = apcu_prefix.is_some() || input .borrow() - .get_option("apcu-autoloader") + .get_option("apcu-autoloader")? .as_bool() .unwrap_or(false) || composer @@ -587,7 +589,7 @@ impl RemoveCommand { .unwrap_or(false); let minimal_changes = input .borrow() - .get_option("minimal-changes") + .get_option("minimal-changes")? .as_bool() .unwrap_or(false) || composer @@ -602,12 +604,12 @@ impl RemoveCommand { let mut flags = String::new(); if input .borrow() - .get_option("update-with-all-dependencies") + .get_option("update-with-all-dependencies")? .as_bool() .unwrap_or(false) || input .borrow() - .get_option("with-all-dependencies") + .get_option("with-all-dependencies")? .as_bool() .unwrap_or(false) { @@ -616,7 +618,7 @@ impl RemoveCommand { flags += " --with-all-dependencies"; } else if input .borrow() - .get_option("no-update-with-dependencies") + .get_option("no-update-with-dependencies")? .as_bool() .unwrap_or(false) { @@ -633,7 +635,7 @@ impl RemoveCommand { install.set_verbose( input .borrow() - .get_option("verbose") + .get_option("verbose")? .as_bool() .unwrap_or(false), ); @@ -645,7 +647,7 @@ impl RemoveCommand { install.set_install( !input .borrow() - .get_option("no-install") + .get_option("no-install")? .as_bool() .unwrap_or(false), ); diff --git a/crates/shirabe/src/command/repository_command.rs b/crates/shirabe/src/command/repository_command.rs index a44df0d..3e0292a 100644 --- a/crates/shirabe/src/command/repository_command.rs +++ b/crates/shirabe/src/command/repository_command.rs @@ -74,24 +74,24 @@ impl RepositoryCommand { let action = strtolower( &input .borrow() - .get_argument("action") + .get_argument("action")? .as_string() .unwrap_or("") .to_string(), ); let name = input .borrow() - .get_argument("name") + .get_argument("name")? .as_string() .map(|s| s.to_string()); let arg1 = input .borrow() - .get_argument("arg1") + .get_argument("arg1")? .as_string() .map(|s| s.to_string()); let arg2 = input .borrow() - .get_argument("arg2") + .get_argument("arg2")? .as_string() .map(|s| s.to_string()); @@ -154,12 +154,12 @@ impl RepositoryCommand { let before = input .borrow() - .get_option("before") + .get_option("before")? .as_string() .map(|s| s.to_string()); let after = input .borrow() - .get_option("after") + .get_option("after")? .as_string() .map(|s| s.to_string()); if before.is_some() && after.is_some() { @@ -190,7 +190,7 @@ impl RepositoryCommand { let append = input .borrow() - .get_option("append") + .get_option("append")? .as_bool() .unwrap_or(false); self.config_source.as_mut().unwrap().add_repository( @@ -291,7 +291,7 @@ impl RepositoryCommand { if ["packagist", "packagist.org"].contains(&name_str) { let append = input .borrow() - .get_option("append") + .get_option("append")? .as_bool() .unwrap_or(false); self.config_source.as_mut().unwrap().add_repository( diff --git a/crates/shirabe/src/command/require_command.rs b/crates/shirabe/src/command/require_command.rs index 3d15f86..dd91524 100644 --- a/crates/shirabe/src/command/require_command.rs +++ b/crates/shirabe/src/command/require_command.rs @@ -163,7 +163,7 @@ impl RequireCommand { if input .borrow() - .get_option("no-suggest") + .get_option("no-suggest")? .as_bool() .unwrap_or(false) { @@ -240,7 +240,7 @@ impl RequireCommand { return Ok(1); } - if input.borrow().get_option("fixed").as_bool() == Some(true) { + if input.borrow().get_option("fixed")?.as_bool() == Some(true) { let config = self.json.as_ref().unwrap().borrow_mut().read()?; let package_type = if empty(&config.get("type").cloned().unwrap_or(PhpMixed::Null)) { @@ -255,7 +255,7 @@ impl RequireCommand { /// @see https://github.com/composer/composer/pull/8313#issuecomment-532637955 if package_type != "project" - && !input.borrow().get_option("dev").as_bool().unwrap_or(false) + && !input.borrow().get_option("dev")?.as_bool().unwrap_or(false) { self.get_io().write_error3("<error>The \"--fixed\" option is only allowed for packages with a \"project\" type or for dev dependencies to prevent possible misuses.</error>", true, io_interface::NORMAL); @@ -301,7 +301,7 @@ impl RequireCommand { output.clone(), input .borrow() - .get_argument("packages") + .get_argument("packages")? .as_list() .map(|l| { l.iter() @@ -314,12 +314,12 @@ impl RequireCommand { // if there is no update, we need to use the best possible version constraint directly as we cannot rely on the solver to guess the best constraint input .borrow() - .get_option("no-update") + .get_option("no-update")? .as_bool() .unwrap_or(false), input .borrow() - .get_option("fixed") + .get_option("fixed")? .as_bool() .unwrap_or(false), ); @@ -346,7 +346,7 @@ impl RequireCommand { let mut requirements = self.format_requirements(requirements)?; - if !input.borrow().get_option("dev").as_bool().unwrap_or(false) + if !input.borrow().get_option("dev")?.as_bool().unwrap_or(false) && self.get_io().is_interactive() && !composer.is_global() { @@ -422,12 +422,12 @@ impl RequireCommand { // unset($devPackages, $pkgDevTags); } - let mut require_key = if input.borrow().get_option("dev").as_bool().unwrap_or(false) { + let mut require_key = if input.borrow().get_option("dev")?.as_bool().unwrap_or(false) { "require-dev" } else { "require" }; - let mut remove_key = if input.borrow().get_option("dev").as_bool().unwrap_or(false) { + let mut remove_key = if input.borrow().get_option("dev")?.as_bool().unwrap_or(false) { "require" } else { "require-dev" @@ -470,7 +470,7 @@ impl RequireCommand { PhpMixed::String(package.clone()), PhpMixed::String(remove_key.to_string()), PhpMixed::String( - if input.borrow().get_option("dev").as_bool().unwrap_or(false) { + if input.borrow().get_option("dev")?.as_bool().unwrap_or(false) { "with" } else { "without" @@ -499,7 +499,7 @@ impl RequireCommand { let q2 = sprintf( "<info>Do you want to re-run the command %s --dev?</info> [<comment>yes</comment>]? ", &[PhpMixed::String( - if input.borrow().get_option("dev").as_bool().unwrap_or(false) { + if input.borrow().get_option("dev")?.as_bool().unwrap_or(false) { "without" } else { "with" @@ -521,7 +521,7 @@ impl RequireCommand { let sort_packages = input .borrow() - .get_option("sort-packages") + .get_option("sort-packages")? .as_bool() .unwrap_or(false) || composer @@ -551,7 +551,7 @@ impl RequireCommand { if !input .borrow() - .get_option("dry-run") + .get_option("dry-run")? .as_bool() .unwrap_or(false) { @@ -573,7 +573,7 @@ impl RequireCommand { if input .borrow() - .get_option("no-update") + .get_option("no-update")? .as_bool() .unwrap_or(false) { @@ -596,7 +596,7 @@ impl RequireCommand { ); let dry_run = input .borrow() - .get_option("dry-run") + .get_option("dry-run")? .as_bool() .unwrap_or(false); @@ -611,7 +611,7 @@ impl RequireCommand { dry_run, input .borrow() - .get_option("fixed") + .get_option("fixed")? .as_bool() .unwrap_or(false), )? @@ -752,7 +752,7 @@ impl RequireCommand { if input .borrow() - .get_option("dry-run") + .get_option("dry-run")? .as_bool() .unwrap_or(false) { @@ -804,12 +804,12 @@ impl RequireCommand { let update_dev_mode = !input .borrow() - .get_option("update-no-dev") + .get_option("update-no-dev")? .as_bool() .unwrap_or(false); let optimize = input .borrow() - .get_option("optimize-autoloader") + .get_option("optimize-autoloader")? .as_bool() .unwrap_or(false) || composer @@ -820,7 +820,7 @@ impl RequireCommand { .unwrap_or(false); let authoritative = input .borrow() - .get_option("classmap-authoritative") + .get_option("classmap-authoritative")? .as_bool() .unwrap_or(false) || composer @@ -831,13 +831,13 @@ impl RequireCommand { .unwrap_or(false); let apcu_prefix = input .borrow() - .get_option("apcu-autoloader-prefix") + .get_option("apcu-autoloader-prefix")? .as_string() .map(|s| s.to_string()); let apcu = apcu_prefix.is_some() || input .borrow() - .get_option("apcu-autoloader") + .get_option("apcu-autoloader")? .as_bool() .unwrap_or(false) || composer @@ -848,7 +848,7 @@ impl RequireCommand { .unwrap_or(false); let minimal_changes = input .borrow() - .get_option("minimal-changes") + .get_option("minimal-changes")? .as_bool() .unwrap_or(false) || composer @@ -862,12 +862,12 @@ impl RequireCommand { let mut flags = String::new(); if input .borrow() - .get_option("update-with-all-dependencies") + .get_option("update-with-all-dependencies")? .as_bool() .unwrap_or(false) || input .borrow() - .get_option("with-all-dependencies") + .get_option("with-all-dependencies")? .as_bool() .unwrap_or(false) { @@ -876,12 +876,12 @@ impl RequireCommand { flags += " --with-all-dependencies"; } else if input .borrow() - .get_option("update-with-dependencies") + .get_option("update-with-dependencies")? .as_bool() .unwrap_or(false) || input .borrow() - .get_option("with-dependencies") + .get_option("with-dependencies")? .as_bool() .unwrap_or(false) { @@ -918,7 +918,7 @@ impl RequireCommand { .set_output_progress( !input .borrow() - .get_option("no-progress") + .get_option("no-progress")? .as_bool() .unwrap_or(false), ); @@ -935,14 +935,14 @@ impl RequireCommand { .set_dry_run( input .borrow() - .get_option("dry-run") + .get_option("dry-run")? .as_bool() .unwrap_or(false), ) .set_verbose( input .borrow() - .get_option("verbose") + .get_option("verbose")? .as_bool() .unwrap_or(false), ) @@ -956,7 +956,7 @@ impl RequireCommand { .set_install( !input .borrow() - .get_option("no-install") + .get_option("no-install")? .as_bool() .unwrap_or(false), ) @@ -968,14 +968,14 @@ impl RequireCommand { .set_prefer_stable( input .borrow() - .get_option("prefer-stable") + .get_option("prefer-stable")? .as_bool() .unwrap_or(false), ) .set_prefer_lowest( input .borrow() - .get_option("prefer-lowest") + .get_option("prefer-lowest")? .as_bool() .unwrap_or(false), ) @@ -1001,7 +1001,7 @@ impl RequireCommand { self, input .borrow() - .get_argument("packages") + .get_argument("packages")? .as_list() .map(|l| { l.iter() diff --git a/crates/shirabe/src/command/run_script_command.rs b/crates/shirabe/src/command/run_script_command.rs index 9a28b5f..199dbab 100644 --- a/crates/shirabe/src/command/run_script_command.rs +++ b/crates/shirabe/src/command/run_script_command.rs @@ -123,8 +123,12 @@ impl RunScriptCommand { return Ok(()); } - if input.borrow().get_argument("script").as_string().is_some() - || input.borrow().get_option("list").as_bool().unwrap_or(false) + if input.borrow().get_argument("script")?.as_string().is_some() + || input + .borrow() + .get_option("list")? + .as_bool() + .unwrap_or(false) { return Ok(()); } @@ -154,11 +158,16 @@ impl RunScriptCommand { input: std::rc::Rc<std::cell::RefCell<dyn InputInterface>>, output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>, ) -> Result<i64> { - if input.borrow().get_option("list").as_bool().unwrap_or(false) { + if input + .borrow() + .get_option("list")? + .as_bool() + .unwrap_or(false) + { return self.list_scripts(output); } - let script = match input.borrow().get_argument("script").as_string() { + let script = match input.borrow().get_argument("script")?.as_string() { None => { return Err(RuntimeException { message: "Missing required argument \"script\"".to_string(), @@ -184,10 +193,10 @@ impl RunScriptCommand { let dispatcher = crate::command::composer_full(&composer) .get_event_dispatcher() .clone(); - let dev_mode = input.borrow().get_option("dev").as_bool().unwrap_or(false) + let dev_mode = input.borrow().get_option("dev")?.as_bool().unwrap_or(false) || !input .borrow() - .get_option("no-dev") + .get_option("no-dev")? .as_bool() .unwrap_or(false); let io = self.get_io(); @@ -213,7 +222,7 @@ impl RunScriptCommand { let args: Vec<String> = input .borrow() - .get_argument("args") + .get_argument("args")? .as_list() .map(|l| { l.iter() @@ -222,7 +231,7 @@ impl RunScriptCommand { }) .unwrap_or_default(); - if let Some(timeout_val) = input.borrow().get_option("timeout").as_string() { + if let Some(timeout_val) = input.borrow().get_option("timeout")?.as_string() { let timeout_str = timeout_val.to_string(); if !timeout_str.chars().all(|c| c.is_ascii_digit()) { return Err(RuntimeException { diff --git a/crates/shirabe/src/command/script_alias_command.rs b/crates/shirabe/src/command/script_alias_command.rs index 5c23e26..29c38cf 100644 --- a/crates/shirabe/src/command/script_alias_command.rs +++ b/crates/shirabe/src/command/script_alias_command.rs @@ -118,10 +118,10 @@ impl ScriptAliasCommand { .into()); } - let dev_mode = input.borrow().get_option("dev").as_bool().unwrap_or(false) + let dev_mode = input.borrow().get_option("dev")?.as_bool().unwrap_or(false) || !input .borrow() - .get_option("no-dev") + .get_option("no-dev")? .as_bool() .unwrap_or(false); diff --git a/crates/shirabe/src/command/search_command.rs b/crates/shirabe/src/command/search_command.rs index b9eacf5..b2fb1df 100644 --- a/crates/shirabe/src/command/search_command.rs +++ b/crates/shirabe/src/command/search_command.rs @@ -53,7 +53,7 @@ impl SearchCommand { let format = input .borrow() - .get_option("format") + .get_option("format")? .as_string() .map(|s| s.to_string()) .unwrap_or_else(|| "text".to_string()); @@ -109,13 +109,13 @@ impl SearchCommand { let mut mode: i64 = repository_interface::SEARCH_FULLTEXT; if input .borrow() - .get_option("only-name") + .get_option("only-name")? .as_bool() .unwrap_or(false) { if input .borrow() - .get_option("only-vendor") + .get_option("only-vendor")? .as_bool() .unwrap_or(false) { @@ -128,7 +128,7 @@ impl SearchCommand { mode = repository_interface::SEARCH_NAME; } else if input .borrow() - .get_option("only-vendor") + .get_option("only-vendor")? .as_bool() .unwrap_or(false) { @@ -137,11 +137,11 @@ impl SearchCommand { let r#type = input .borrow() - .get_option("type") + .get_option("type")? .as_string() .map(|s| s.to_string()); - let tokens_arg = input.borrow().get_argument("tokens"); + let tokens_arg = input.borrow().get_argument("tokens")?; let token_strings: Vec<String> = tokens_arg .as_array() .map(|arr| { @@ -182,7 +182,7 @@ impl SearchCommand { if let Some(link) = link { io.write(&format!( "<href={}>{}</>{}{}{}", - OutputFormatter::escape(link), + OutputFormatter::escape(link)?, result.name, " ".repeat(name_length as usize - result.name.len()), warning, diff --git a/crates/shirabe/src/command/show_command.rs b/crates/shirabe/src/command/show_command.rs index 06abec4..27bda55 100644 --- a/crates/shirabe/src/command/show_command.rs +++ b/crates/shirabe/src/command/show_command.rs @@ -85,25 +85,25 @@ impl ShowCommand { output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>, ) -> anyhow::Result<i64> { self.version_parser = VersionParser::new(); - if input.borrow().get_option("tree").as_bool() == Some(true) { + if input.borrow().get_option("tree")?.as_bool() == Some(true) { self.init_styles(output.clone()); } let mut composer = self.try_composer(None, None); - if input.borrow().get_option("installed").as_bool() == Some(true) - && input.borrow().get_option("self").as_bool() != Some(true) + if input.borrow().get_option("installed")?.as_bool() == Some(true) + && input.borrow().get_option("self")?.as_bool() != Some(true) { self.get_io().write_error("<warning>You are using the deprecated option \"installed\". Only installed packages are shown by default now. The --all option can be used to show all packages.</warning>"); } - if input.borrow().get_option("outdated").as_bool() == Some(true) { + if input.borrow().get_option("outdated")?.as_bool() == Some(true) { input .borrow_mut() .set_option("latest", PhpMixed::Bool(true)); } else if input .borrow() - .get_option("ignore") + .get_option("ignore")? .as_list() .map_or(0, |l| l.len()) > 0 @@ -111,19 +111,19 @@ impl ShowCommand { self.get_io().write_error("<warning>You are using the option \"ignore\" for action other than \"outdated\", it will be ignored.</warning>"); } - if input.borrow().get_option("direct").as_bool() == Some(true) - && (input.borrow().get_option("all").as_bool() == Some(true) - || input.borrow().get_option("available").as_bool() == Some(true) - || input.borrow().get_option("platform").as_bool() == Some(true)) + if input.borrow().get_option("direct")?.as_bool() == Some(true) + && (input.borrow().get_option("all")?.as_bool() == Some(true) + || input.borrow().get_option("available")?.as_bool() == Some(true) + || input.borrow().get_option("platform")?.as_bool() == Some(true)) { self.get_io().write_error("The --direct (-D) option is not usable in combination with --all, --platform (-p) or --available (-a)"); return Ok(1); } - if input.borrow().get_option("tree").as_bool() == Some(true) - && (input.borrow().get_option("all").as_bool() == Some(true) - || input.borrow().get_option("available").as_bool() == Some(true)) + if input.borrow().get_option("tree")?.as_bool() == Some(true) + && (input.borrow().get_option("all")?.as_bool() == Some(true) + || input.borrow().get_option("available")?.as_bool() == Some(true)) { self.get_io().write_error("The --tree (-t) option is not usable in combination with --all or --available (-a)"); @@ -131,9 +131,9 @@ impl ShowCommand { } let only_count: usize = [ - input.borrow().get_option("patch-only").as_bool() == Some(true), - input.borrow().get_option("minor-only").as_bool() == Some(true), - input.borrow().get_option("major-only").as_bool() == Some(true), + input.borrow().get_option("patch-only")?.as_bool() == Some(true), + input.borrow().get_option("minor-only")?.as_bool() == Some(true), + input.borrow().get_option("major-only")?.as_bool() == Some(true), ] .iter() .filter(|b| **b) @@ -146,8 +146,8 @@ impl ShowCommand { return Ok(1); } - if input.borrow().get_option("tree").as_bool() == Some(true) - && input.borrow().get_option("latest").as_bool() == Some(true) + if input.borrow().get_option("tree")?.as_bool() == Some(true) + && input.borrow().get_option("latest")?.as_bool() == Some(true) { self.get_io().write_error( "The --tree (-t) option is not usable in combination with --latest (-l)", @@ -156,8 +156,8 @@ impl ShowCommand { return Ok(1); } - if input.borrow().get_option("tree").as_bool() == Some(true) - && input.borrow().get_option("path").as_bool() == Some(true) + if input.borrow().get_option("tree")?.as_bool() == Some(true) + && input.borrow().get_option("path")?.as_bool() == Some(true) { self.get_io().write_error( "The --tree (-t) option is not usable in combination with --path (-P)", @@ -168,7 +168,7 @@ impl ShowCommand { let format = input .borrow() - .get_option("format") + .get_option("format")? .as_string() .unwrap_or("text") .to_string(); @@ -214,20 +214,25 @@ impl ShowCommand { let installed_repo: RepositoryInterfaceHandle; let repos: RepositoryInterfaceHandle; - if input.borrow().get_option("self").as_bool() == Some(true) - && input.borrow().get_option("installed").as_bool() != Some(true) - && input.borrow().get_option("locked").as_bool() != Some(true) + if input.borrow().get_option("self")?.as_bool() == Some(true) + && input.borrow().get_option("installed")?.as_bool() != Some(true) + && input.borrow().get_option("locked")?.as_bool() != Some(true) { let composer = self.require_composer(None, None)?; let package = crate::package::RootPackageInterfaceHandle::dup( composer.borrow_partial().get_package(), ); - if input.borrow().get_option("name-only").as_bool() == Some(true) { + if input.borrow().get_option("name-only")?.as_bool() == Some(true) { self.get_io().write(&package.get_name()); return Ok(0); } - if input.borrow().get_argument("package").as_string().is_some() { + if input + .borrow() + .get_argument("package")? + .as_string() + .is_some() + { return Err(InvalidArgumentException { message: "You cannot use --self together with a package name".to_string(), code: 0, @@ -241,14 +246,14 @@ impl ShowCommand { RepositoryInterfaceHandle::new(RootPackageRepository::new(package.clone())), ])); single_package = Some(package.clone().into()); - } else if input.borrow().get_option("platform").as_bool() == Some(true) { + } else if input.borrow().get_option("platform")?.as_bool() == Some(true) { installed_repo = RepositoryInterfaceHandle::new(InstalledRepository::new(vec![ platform_repo.clone().into(), ])); repos = RepositoryInterfaceHandle::new(InstalledRepository::new(vec![ platform_repo.clone().into(), ])); - } else if input.borrow().get_option("available").as_bool() == Some(true) { + } else if input.borrow().get_option("available")?.as_bool() == Some(true) { let mut ir = InstalledRepository::new(vec![platform_repo.clone().into()]); if let Some(ref composer) = composer { let composer = crate::command::composer_full(composer); @@ -281,7 +286,7 @@ impl ShowCommand { )); installed_repo = RepositoryInterfaceHandle::new(ir); } - } else if input.borrow().get_option("all").as_bool() == Some(true) && composer.is_some() { + } else if input.borrow().get_option("all")?.as_bool() == Some(true) && composer.is_some() { let mut composer_ref = crate::command::composer_full_mut(composer.as_ref().unwrap()); let local_repo = composer_ref .get_repository_manager() @@ -321,7 +326,7 @@ impl ShowCommand { composite_input.push(r.clone()); } repos = RepositoryInterfaceHandle::new(CompositeRepository::new(composite_input)); - } else if input.borrow().get_option("all").as_bool() == Some(true) { + } else if input.borrow().get_option("all")?.as_bool() == Some(true) { let default_repos = RepositoryFactory::default_repos_with_default_manager(self.get_io())?; let names: Vec<String> = default_repos.keys().cloned().collect(); @@ -337,7 +342,7 @@ impl ShowCommand { composite_input.push(v); } repos = RepositoryInterfaceHandle::new(CompositeRepository::new(composite_input)); - } else if input.borrow().get_option("locked").as_bool() == Some(true) { + } else if input.borrow().get_option("locked")?.as_bool() == Some(true) { if composer.is_none() || !crate::command::composer_full_mut(composer.as_ref().unwrap()) .get_locker() @@ -354,9 +359,9 @@ impl ShowCommand { let locker_rc = composer_ref.get_locker().clone(); let mut locker = locker_rc.borrow_mut(); let lr = locker.get_locked_repository( - input.borrow().get_option("no-dev").as_bool() != Some(true), + input.borrow().get_option("no-dev")?.as_bool() != Some(true), )?; - if input.borrow().get_option("self").as_bool() == Some(true) { + if input.borrow().get_option("self")?.as_bool() == Some(true) { lr.add_package( crate::package::RootPackageInterfaceHandle::dup(composer_ref.get_package()) .into(), @@ -386,7 +391,7 @@ impl ShowCommand { let root_pkg = composer_local.get_package(); let root_repo: RepositoryInterfaceHandle = - if input.borrow().get_option("self").as_bool() == Some(true) { + if input.borrow().get_option("self")?.as_bool() == Some(true) { RepositoryInterfaceHandle::new(RootPackageRepository::new( crate::package::RootPackageInterfaceHandle::dup( composer_local.get_package(), @@ -395,7 +400,7 @@ impl ShowCommand { } else { RepositoryInterfaceHandle::new(InstalledArrayRepository::new()?) }; - if input.borrow().get_option("no-dev").as_bool() == Some(true) { + if input.borrow().get_option("no-dev")?.as_bool() == Some(true) { let local_packages = composer_local .get_repository_manager() .borrow() @@ -468,7 +473,7 @@ impl ShowCommand { .dispatch(Some(&command_event_name), Some(&mut command_event))?; } - if input.borrow().get_option("latest").as_bool() == Some(true) && composer.is_none() { + if input.borrow().get_option("latest")?.as_bool() == Some(true) && composer.is_none() { self.get_io().write_error( "No composer.json found in the current directory, disabling \"latest\" option", ); @@ -479,7 +484,7 @@ impl ShowCommand { let package_filter: Option<String> = input .borrow() - .get_argument("package") + .get_argument("package")? .as_string() .map(|s| s.to_string()); @@ -492,11 +497,11 @@ impl ShowCommand { &*installed_repo.borrow(), &repos, pf, - input.borrow().get_argument("version"), + input.borrow().get_argument("version")?, )?; if let Some(ref pkg) = matched_package { - if input.borrow().get_option("direct").as_bool() == Some(true) { + if input.borrow().get_option("direct")?.as_bool() == Some(true) { if !in_array( PhpMixed::String(pkg.get_name()), &PhpMixed::List( @@ -522,7 +527,7 @@ impl ShowCommand { if matched_package.is_none() { let options = input.borrow().get_options(); let mut hint = String::new(); - if input.borrow().get_option("locked").as_bool() == Some(true) { + if input.borrow().get_option("locked")?.as_bool() == Some(true) { hint.push_str(" in lock file"); } if options.contains_key("working-dir") { @@ -535,12 +540,12 @@ impl ShowCommand { )); } if PlatformRepository::is_platform_package(pf) - && input.borrow().get_option("platform").as_bool() != Some(true) + && input.borrow().get_option("platform")?.as_bool() != Some(true) { hint.push_str(", try using --platform (-p) to show platform packages"); } - if input.borrow().get_option("all").as_bool() != Some(true) - && input.borrow().get_option("available").as_bool() != Some(true) + if input.borrow().get_option("all")?.as_bool() != Some(true) + && input.borrow().get_option("available")?.as_bool() != Some(true) { hint.push_str( ", try using --available (-a) to show all available packages", @@ -562,7 +567,7 @@ impl ShowCommand { // assert(isset($versions)); let mut exit_code: i64 = 0; - if input.borrow().get_option("tree").as_bool() == Some(true) { + if input.borrow().get_option("tree")?.as_bool() == Some(true) { let array_tree = self.generate_package_tree( package.clone().into(), &*installed_repo.borrow(), @@ -591,31 +596,31 @@ impl ShowCommand { } let mut latest_package: Option<crate::package::PackageInterfaceHandle> = None; - if input.borrow().get_option("latest").as_bool() == Some(true) { + if input.borrow().get_option("latest")?.as_bool() == Some(true) { latest_package = self.find_latest_package( package.clone().into(), composer.as_ref().unwrap(), &platform_repo, input .borrow() - .get_option("major-only") + .get_option("major-only")? .as_bool() .unwrap_or(false), input .borrow() - .get_option("minor-only") + .get_option("minor-only")? .as_bool() .unwrap_or(false), input .borrow() - .get_option("patch-only") + .get_option("patch-only")? .as_bool() .unwrap_or(false), platform_req_filter.clone(), )?; } - if input.borrow().get_option("outdated").as_bool() == Some(true) - && input.borrow().get_option("strict").as_bool() == Some(true) + if input.borrow().get_option("outdated")?.as_bool() == Some(true) + && input.borrow().get_option("strict")?.as_bool() == Some(true) && latest_package.is_some() && latest_package .as_ref() @@ -631,7 +636,7 @@ impl ShowCommand { { exit_code = 1; } - if input.borrow().get_option("path").as_bool() == Some(true) { + if input.borrow().get_option("path")?.as_bool() == Some(true) { self.get_io().write_no_newline(&package.get_name()); let path = { let composer_ref = composer.as_ref().unwrap(); @@ -672,7 +677,7 @@ impl ShowCommand { } // show tree view if requested - if input.borrow().get_option("tree").as_bool() == Some(true) { + if input.borrow().get_option("tree")?.as_bool() == Some(true) { let root_requires = self.get_root_requires(); let mut packages = installed_repo.get_packages()?; packages.sort_by(|a, b| { @@ -734,11 +739,11 @@ impl ShowCommand { } let mut package_list_filter: Option<Vec<String>> = None; - if input.borrow().get_option("direct").as_bool() == Some(true) { + if input.borrow().get_option("direct")?.as_bool() == Some(true) { package_list_filter = Some(self.get_root_requires()); } - if input.borrow().get_option("path").as_bool() == Some(true) && composer.is_none() { + if input.borrow().get_option("path")?.as_bool() == Some(true) && composer.is_none() { self.get_io().write_error( "No composer.json found in the current directory, disabling \"path\" option", ); @@ -831,15 +836,15 @@ impl ShowCommand { } } - let show_all_types = input.borrow().get_option("all").as_bool() == Some(true); - let show_latest = input.borrow().get_option("latest").as_bool() == Some(true); - let show_major_only = input.borrow().get_option("major-only").as_bool() == Some(true); - let show_minor_only = input.borrow().get_option("minor-only").as_bool() == Some(true); - let show_patch_only = input.borrow().get_option("patch-only").as_bool() == Some(true); + let show_all_types = input.borrow().get_option("all")?.as_bool() == Some(true); + let show_latest = input.borrow().get_option("latest")?.as_bool() == Some(true); + let show_major_only = input.borrow().get_option("major-only")?.as_bool() == Some(true); + let show_minor_only = input.borrow().get_option("minor-only")?.as_bool() == Some(true); + let show_patch_only = input.borrow().get_option("patch-only")?.as_bool() == Some(true); let ignored_packages_regex = base_package::package_names_to_regexp( &input .borrow() - .get_option("ignore") + .get_option("ignore")? .as_list() .map(|l| { l.iter() @@ -898,21 +903,21 @@ impl ShowCommand { } } - let write_path = input.borrow().get_option("name-only").as_bool() != Some(true) - && input.borrow().get_option("path").as_bool() == Some(true); - write_version = input.borrow().get_option("name-only").as_bool() != Some(true) - && input.borrow().get_option("path").as_bool() != Some(true) + let write_path = input.borrow().get_option("name-only")?.as_bool() != Some(true) + && input.borrow().get_option("path")?.as_bool() == Some(true); + write_version = input.borrow().get_option("name-only")?.as_bool() != Some(true) + && input.borrow().get_option("path")?.as_bool() != Some(true) && *show_version; let write_latest = write_version && show_latest; - write_description = input.borrow().get_option("name-only").as_bool() != Some(true) - && input.borrow().get_option("path").as_bool() != Some(true); + write_description = input.borrow().get_option("name-only")?.as_bool() != Some(true) + && input.borrow().get_option("path")?.as_bool() != Some(true); let write_release_date = write_latest - && (input.borrow().get_option("sort-by-age").as_bool() == Some(true) + && (input.borrow().get_option("sort-by-age")?.as_bool() == Some(true) || format == "json"); let mut has_outdated_packages = false; - if input.borrow().get_option("sort-by-age").as_bool() == Some(true) { + if input.borrow().get_option("sort-by-age")?.as_bool() == Some(true) { type_packages.sort_by(|_ka, a, _kb, b| match (a, b) { (PackageOrName::Pkg(a), PackageOrName::Pkg(b)) => { a.get_release_date().cmp(&b.get_release_date()) @@ -950,14 +955,14 @@ impl ShowCommand { package_is_up_to_date || (latest_package.is_none() && show_major_only); let package_is_ignored = Preg::is_match(&ignored_packages_regex, &package.get_pretty_name())?; - if input.borrow().get_option("outdated").as_bool() == Some(true) + if input.borrow().get_option("outdated")?.as_bool() == Some(true) && (package_is_up_to_date || package_is_ignored) { continue; } - if input.borrow().get_option("outdated").as_bool() == Some(true) - || input.borrow().get_option("strict").as_bool() == Some(true) + if input.borrow().get_option("outdated")?.as_bool() == Some(true) + || input.borrow().get_option("strict")?.as_bool() == Some(true) { has_outdated_packages = true; } @@ -980,7 +985,7 @@ impl ShowCommand { )), ); if format != "json" - || input.borrow().get_option("name-only").as_bool() != Some(true) + || input.borrow().get_option("name-only")?.as_bool() != Some(true) { package_view_data.insert( "homepage".to_string(), @@ -1159,7 +1164,7 @@ impl ShowCommand { write_release_date, }, ); - if input.borrow().get_option("strict").as_bool() == Some(true) + if input.borrow().get_option("strict")?.as_bool() == Some(true) && has_outdated_packages { exit_code = 1; @@ -1194,7 +1199,7 @@ impl ShowCommand { .collect(), ))); } else { - if input.borrow().get_option("latest").as_bool() == Some(true) + if input.borrow().get_option("latest")?.as_bool() == Some(true) && view_data.values().any(|v| !v.is_empty()) { let io = self.get_io(); @@ -1202,7 +1207,7 @@ impl ShowCommand { io.write_error("Legend:"); io.write_error("! patch or minor release available - update recommended"); io.write_error("~ major release available - update possible"); - if input.borrow().get_option("outdated").as_bool() != Some(true) { + if input.borrow().get_option("outdated")?.as_bool() != Some(true) { io.write_error("= up to date version"); } } else { @@ -1211,7 +1216,7 @@ impl ShowCommand { io.write_error( "- <comment>major</comment> release available - update possible", ); - if input.borrow().get_option("outdated").as_bool() != Some(true) { + if input.borrow().get_option("outdated")?.as_bool() != Some(true) { io.write_error("- <info>up to date</info> version"); } } @@ -1254,7 +1259,7 @@ impl ShowCommand { } } - if write_latest && input.borrow().get_option("direct").as_bool() != Some(true) { + if write_latest && input.borrow().get_option("direct")?.as_bool() != Some(true) { let mut direct_deps: Vec<IndexMap<String, PhpMixed>> = Vec::new(); let mut transitive_deps: Vec<IndexMap<String, PhpMixed>> = Vec::new(); for pkg in packages.iter() { @@ -1387,7 +1392,7 @@ impl ShowCommand { io.write_no_newline(&format!( "{}<href={}>{}</>{}", indent, - OutputFormatter::escape(&link), + OutputFormatter::escape(&link).expect("OutputFormatter::escape failed"), name, " ".repeat(pad) )); @@ -2264,12 +2269,12 @@ impl ShowCommand { ]; for color in self.colors.iter() { - let style = OutputFormatterStyle::new(Some(color.as_str()), None, None); + let style = OutputFormatterStyle::new(Some(color.as_str()), None, vec![]); output .borrow() .get_formatter() .borrow_mut() - .set_style(color, style); + .set_style(color, Box::new(style)); } } diff --git a/crates/shirabe/src/command/status_command.rs b/crates/shirabe/src/command/status_command.rs index 0febf1e..bbff681 100644 --- a/crates/shirabe/src/command/status_command.rs +++ b/crates/shirabe/src/command/status_command.rs @@ -218,7 +218,7 @@ impl StatusCommand { for (path, changes) in &errors { if input .borrow() - .get_option("verbose") + .get_option("verbose")? .as_bool() .unwrap_or(false) { @@ -241,7 +241,7 @@ impl StatusCommand { for (path, changes) in &unpushed_changes { if input .borrow() - .get_option("verbose") + .get_option("verbose")? .as_bool() .unwrap_or(false) { @@ -266,7 +266,7 @@ impl StatusCommand { for (path, changes) in &vcs_version_changes { if input .borrow() - .get_option("verbose") + .get_option("verbose")? .as_bool() .unwrap_or(false) { @@ -332,7 +332,7 @@ impl StatusCommand { if (!errors.is_empty() || !unpushed_changes.is_empty() || !vcs_version_changes.is_empty()) && !input .borrow() - .get_option("verbose") + .get_option("verbose")? .as_bool() .unwrap_or(false) { diff --git a/crates/shirabe/src/command/suggests_command.rs b/crates/shirabe/src/command/suggests_command.rs index b93f7ec..393874f 100644 --- a/crates/shirabe/src/command/suggests_command.rs +++ b/crates/shirabe/src/command/suggests_command.rs @@ -69,7 +69,7 @@ impl SuggestsCommand { let locked_repo = composer.get_locker().borrow_mut().get_locked_repository( !input .borrow() - .get_option("no-dev") + .get_option("no-dev")? .as_bool() .unwrap_or(false), )?; @@ -95,7 +95,7 @@ impl SuggestsCommand { let mut installed_repo = InstalledRepository::new(installed_repos); let mut reporter = SuggestedPackagesReporter::new(self.get_io().clone()); - let filter = input.borrow().get_argument("packages"); + let filter = input.borrow().get_argument("packages")?; let mut packages = RepositoryInterface::get_packages(&mut installed_repo)?; let root_pkg_as_base: crate::package::BasePackageHandle = composer.get_package().clone().into(); @@ -111,7 +111,7 @@ impl SuggestsCommand { if input .borrow() - .get_option("by-suggestion") + .get_option("by-suggestion")? .as_bool() .unwrap_or(false) { @@ -119,18 +119,23 @@ impl SuggestsCommand { } if input .borrow() - .get_option("by-package") + .get_option("by-package")? .as_bool() .unwrap_or(false) { mode |= SuggestedPackagesReporter::MODE_BY_PACKAGE; } - if input.borrow().get_option("list").as_bool().unwrap_or(false) { + if input + .borrow() + .get_option("list")? + .as_bool() + .unwrap_or(false) + { mode = SuggestedPackagesReporter::MODE_LIST; } let only_dependents_of: Option<crate::package::PackageInterfaceHandle> = - if empty(&filter) && !input.borrow().get_option("all").as_bool().unwrap_or(false) { + if empty(&filter) && !input.borrow().get_option("all")?.as_bool().unwrap_or(false) { Some(composer.get_package().clone().into()) } else { None diff --git a/crates/shirabe/src/command/update_command.rs b/crates/shirabe/src/command/update_command.rs index eb618ca..60d2eac 100644 --- a/crates/shirabe/src/command/update_command.rs +++ b/crates/shirabe/src/command/update_command.rs @@ -78,7 +78,7 @@ impl UpdateCommand { output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>, ) -> Result<i64> { let io = self.get_io().clone(); - if input.borrow().get_option("dev").as_bool().unwrap_or(false) { + if input.borrow().get_option("dev")?.as_bool().unwrap_or(false) { io.write_error3( "<warning>You are using the deprecated option \"--dev\". It has no effect and will break in Composer 3.</warning>", true, @@ -87,7 +87,7 @@ impl UpdateCommand { } if input .borrow() - .get_option("no-suggest") + .get_option("no-suggest")? .as_bool() .unwrap_or(false) { @@ -111,7 +111,7 @@ impl UpdateCommand { let mut packages: Vec<String> = input .borrow() - .get_argument("packages") + .get_argument("packages")? .as_list() .map(|l| { l.iter() @@ -122,7 +122,7 @@ impl UpdateCommand { let mut reqs: IndexMap<String, String> = self.format_requirements( input .borrow() - .get_option("with") + .get_option("with")? .as_list() .map(|l| { l.iter() @@ -196,7 +196,7 @@ impl UpdateCommand { if input .borrow() - .get_option("patch-only") + .get_option("patch-only")? .as_bool() .unwrap_or(false) { @@ -241,7 +241,7 @@ impl UpdateCommand { if input .borrow() - .get_option("interactive") + .get_option("interactive")? .as_bool() .unwrap_or(false) { @@ -256,14 +256,14 @@ impl UpdateCommand { if input .borrow() - .get_option("root-reqs") + .get_option("root-reqs")? .as_bool() .unwrap_or(false) { let mut requires: Vec<String> = array_keys(&root_package.get_requires()); if !input .borrow() - .get_option("no-dev") + .get_option("no-dev")? .as_bool() .unwrap_or(false) { @@ -290,7 +290,11 @@ impl UpdateCommand { true, ) }); - let update_mirrors = input.borrow().get_option("lock").as_bool().unwrap_or(false) + let update_mirrors = input + .borrow() + .get_option("lock")? + .as_bool() + .unwrap_or(false) || filtered_packages.len() != packages.len(); packages = filtered_packages; @@ -314,7 +318,7 @@ impl UpdateCommand { .set_output_progress( !input .borrow() - .get_option("no-progress") + .get_option("no-progress")? .as_bool() .unwrap_or(false), ); @@ -327,7 +331,7 @@ impl UpdateCommand { let optimize = input .borrow() - .get_option("optimize-autoloader") + .get_option("optimize-autoloader")? .as_bool() .unwrap_or(false) || config @@ -337,7 +341,7 @@ impl UpdateCommand { .unwrap_or(false); let authoritative = input .borrow() - .get_option("classmap-authoritative") + .get_option("classmap-authoritative")? .as_bool() .unwrap_or(false) || config @@ -347,13 +351,13 @@ impl UpdateCommand { .unwrap_or(false); let apcu_prefix: Option<String> = input .borrow() - .get_option("apcu-autoloader-prefix") + .get_option("apcu-autoloader-prefix")? .as_string() .map(|s| s.to_string()); let apcu = apcu_prefix.is_some() || input .borrow() - .get_option("apcu-autoloader") + .get_option("apcu-autoloader")? .as_bool() .unwrap_or(false) || config @@ -363,7 +367,7 @@ impl UpdateCommand { .unwrap_or(false); let minimal_changes = input .borrow() - .get_option("minimal-changes") + .get_option("minimal-changes")? .as_bool() .unwrap_or(false) || config @@ -375,7 +379,7 @@ impl UpdateCommand { let mut update_allow_transitive_dependencies = UpdateAllowTransitiveDeps::UpdateOnlyListed; if input .borrow() - .get_option("with-all-dependencies") + .get_option("with-all-dependencies")? .as_bool() .unwrap_or(false) { @@ -383,7 +387,7 @@ impl UpdateCommand { UpdateAllowTransitiveDeps::UpdateListedWithTransitiveDeps; } else if input .borrow() - .get_option("with-dependencies") + .get_option("with-dependencies")? .as_bool() .unwrap_or(false) { @@ -395,14 +399,14 @@ impl UpdateCommand { .set_dry_run( input .borrow() - .get_option("dry-run") + .get_option("dry-run")? .as_bool() .unwrap_or(false), ) .set_verbose( input .borrow() - .get_option("verbose") + .get_option("verbose")? .as_bool() .unwrap_or(false), ) @@ -411,14 +415,14 @@ impl UpdateCommand { .set_dev_mode( !input .borrow() - .get_option("no-dev") + .get_option("no-dev")? .as_bool() .unwrap_or(false), ) .set_dump_autoloader( !input .borrow() - .get_option("no-autoloader") + .get_option("no-autoloader")? .as_bool() .unwrap_or(false), ) @@ -429,7 +433,7 @@ impl UpdateCommand { .set_install( !input .borrow() - .get_option("no-install") + .get_option("no-install")? .as_bool() .unwrap_or(false), ) @@ -440,14 +444,14 @@ impl UpdateCommand { .set_prefer_stable( input .borrow() - .get_option("prefer-stable") + .get_option("prefer-stable")? .as_bool() .unwrap_or(false), ) .set_prefer_lowest( input .borrow() - .get_option("prefer-lowest") + .get_option("prefer-lowest")? .as_bool() .unwrap_or(false), ) @@ -459,7 +463,7 @@ impl UpdateCommand { if input .borrow() - .get_option("no-plugins") + .get_option("no-plugins")? .as_bool() .unwrap_or(false) { @@ -468,8 +472,14 @@ impl UpdateCommand { let mut result = install.run()?; - if result == 0 && !input.borrow().get_option("lock").as_bool().unwrap_or(false) { - let mut bump_after_update = input.borrow().get_option("bump-after-update"); + if result == 0 + && !input + .borrow() + .get_option("lock")? + .as_bool() + .unwrap_or(false) + { + let mut bump_after_update = input.borrow().get_option("bump-after-update")?; // PHP: false === $bumpAfterUpdate (strict) if matches!(bump_after_update, PhpMixed::Bool(false)) { bump_after_update = composer.get_config().borrow().get("bump-after-update"); @@ -489,12 +499,12 @@ impl UpdateCommand { bump_after_update.as_string() == Some("no-dev"), input .borrow() - .get_option("dry-run") + .get_option("dry-run")? .as_bool() .unwrap_or(false), input .borrow() - .get_argument("packages") + .get_argument("packages")? .as_list() .map(|l| { l.iter() diff --git a/crates/shirabe/src/command/validate_command.rs b/crates/shirabe/src/command/validate_command.rs index 73de63b..1d9d036 100644 --- a/crates/shirabe/src/command/validate_command.rs +++ b/crates/shirabe/src/command/validate_command.rs @@ -115,7 +115,7 @@ impl ValidateCommand { ) -> Result<i64> { let file = input .borrow() - .get_argument("file") + .get_argument("file")? .as_string() .map(|s| s.to_string()) .map(Ok) @@ -134,7 +134,7 @@ impl ValidateCommand { let validator = ConfigValidator::new(io.clone()); let check_all = if input .borrow() - .get_option("no-check-all") + .get_option("no-check-all")? .as_bool() .unwrap_or(false) { @@ -144,17 +144,17 @@ impl ValidateCommand { }; let check_publish = !input .borrow() - .get_option("no-check-publish") + .get_option("no-check-publish")? .as_bool() .unwrap_or(false); let check_lock = !input .borrow() - .get_option("no-check-lock") + .get_option("no-check-lock")? .as_bool() .unwrap_or(false); let check_version = if input .borrow() - .get_option("no-check-version") + .get_option("no-check-version")? .as_bool() .unwrap_or(false) { @@ -164,7 +164,7 @@ impl ValidateCommand { }; let is_strict = input .borrow() - .get_option("strict") + .get_option("strict")? .as_bool() .unwrap_or(false); let (mut errors, mut publish_errors, mut warnings) = @@ -183,7 +183,7 @@ impl ValidateCommand { .unwrap_or(true)) || input .borrow() - .get_option("check-lock") + .get_option("check-lock")? .as_bool() .unwrap_or(false); let locker = composer.get_locker().clone(); @@ -221,7 +221,7 @@ impl ValidateCommand { if input .borrow() - .get_option("with-dependencies") + .get_option("with-dependencies")? .as_bool() .unwrap_or(false) { diff --git a/crates/shirabe/src/console/application.rs b/crates/shirabe/src/console/application.rs index 2eb9f9a..dcf30dc 100644 --- a/crates/shirabe/src/console/application.rs +++ b/crates/shirabe/src/console/application.rs @@ -6,12 +6,12 @@ use indexmap::IndexMap; use shirabe_external_packages::composer::xdebug_handler::XdebugHandler; use shirabe_external_packages::seld::json_lint::ParsingException; use shirabe_external_packages::symfony::console::Application as BaseApplication; -use shirabe_external_packages::symfony::console::SingleCommandApplication; use shirabe_external_packages::symfony::console::command::Command; use shirabe_external_packages::symfony::console::exception::CommandNotFoundException; use shirabe_external_packages::symfony::console::exception::ExceptionInterface; use shirabe_external_packages::symfony::console::helper::HelperInterface; use shirabe_external_packages::symfony::console::helper::HelperSet; +use shirabe_external_packages::symfony::console::helper::HelperSetKey; use shirabe_external_packages::symfony::console::helper::QuestionHelper; use shirabe_external_packages::symfony::console::input::InputDefinition; use shirabe_external_packages::symfony::console::input::InputInterface; @@ -103,8 +103,9 @@ impl Application { const LOGO: &'static str = " ______\n / ____/___ ____ ___ ____ ____ ________ _____\n / / / __ \\/ __ `__ \\/ __ \\/ __ \\/ ___/ _ \\/ ___/\n/ /___/ /_/ / / / / / / /_/ / /_/ (__ ) __/ /\n\\____/\\____/_/ /_/ /_/ .___/\\____/____/\\___/_/\n /_/\n"; pub fn new(name: String, mut version: String) -> Self { - let mut inner = BaseApplication::new(&name, &version); - inner.set_catch_errors(true); + // PHP: if (method_exists($this, 'setCatchErrors')) { $this->setCatchErrors(true); } + // This Symfony Console port does not provide `setCatchErrors`, so the guarded call + // is skipped, matching `method_exists` evaluating to false. // PHP: static $shutdownRegistered = false; — register only once globally static SHUTDOWN_REGISTERED: std::sync::OnceLock<()> = std::sync::OnceLock::new(); @@ -147,8 +148,7 @@ impl Application { let initial_working_directory = getcwd(); // PHP: parent::__construct($name, $version); - // BaseApplication is mock-imported; assume new(name, version) above also recorded the version. - let _ = (name, version); + let inner = BaseApplication::__construct(&name, &version); Self { inner, @@ -184,10 +184,10 @@ impl Application { ) -> anyhow::Result<i64> { self.disable_plugins_by_default = input .borrow() - .has_parameter_option(&["--no-plugins"], false); + .has_parameter_option(PhpMixed::from(vec!["--no-plugins"]), false); self.disable_scripts_by_default = input .borrow() - .has_parameter_option(&["--no-scripts"], false); + .has_parameter_option(PhpMixed::from(vec!["--no-scripts"]), false); // PHP: static $stdin = null; — cached across doRun calls so the php://stdin handle is // opened once. @@ -209,18 +209,29 @@ impl Application { } // PHP: $this->io = new ConsoleIO($input, $output, new HelperSet([new QuestionHelper()])); - let helpers: Vec<std::rc::Rc<std::cell::RefCell<dyn HelperInterface>>> = - vec![std::rc::Rc::new(std::cell::RefCell::new(QuestionHelper))]; + let mut helpers: IndexMap< + HelperSetKey, + std::rc::Rc<std::cell::RefCell<dyn HelperInterface>>, + > = IndexMap::new(); + helpers.insert( + HelperSetKey::Int(0), + std::rc::Rc::new(std::cell::RefCell::new(QuestionHelper::default())), + ); + let helper_set = std::rc::Rc::new(std::cell::RefCell::new(HelperSet::default())); + HelperSet::new(&helper_set, helpers); self.io = std::rc::Rc::new(std::cell::RefCell::new(ConsoleIO::new( input.clone(), output.clone(), - HelperSet::new(helpers), + helper_set.borrow().clone(), ))); // Register error handler again to pass it the IO instance ErrorHandler::register(Some(self.io.clone())); - if input.borrow().has_parameter_option(&["--no-cache"], false) { + if input + .borrow() + .has_parameter_option(PhpMixed::from(vec!["--no-cache"]), false) + { self.io .write_error3("Disabling cache usage", true, io_interface::DEBUG); Platform::put_env( @@ -308,10 +319,22 @@ impl Application { && !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(&["--file"], true) == false - && input.borrow().has_parameter_option(&["-f"], true) == false)) - && input.borrow().has_parameter_option(&["--help"], true) == false - && input.borrow().has_parameter_option(&["-h"], true) == false + || (input + .borrow() + .has_parameter_option(PhpMixed::from(vec!["--file"]), true) + == false + && input + .borrow() + .has_parameter_option(PhpMixed::from(vec!["-f"]), true) + == false)) + && input + .borrow() + .has_parameter_option(PhpMixed::from(vec!["--help"]), true) + == false + && input + .borrow() + .has_parameter_option(PhpMixed::from(vec!["-h"]), true) + == false { let mut dir = dirname(&Platform::get_cwd(true).unwrap_or_default()); let home_value = Platform::get_env("HOME") @@ -394,7 +417,7 @@ impl Application { ]); let may_need_plugin_command = !input .borrow() - .has_parameter_option(&["--version", "-V"], false) + .has_parameter_option(PhpMixed::from(vec!["--version", "-V"]), false) && (command_name.is_none() || in_array( command_name.as_deref().unwrap_or("").into(), @@ -692,7 +715,7 @@ impl Application { // reflection (instantiate_class) and stays PhpMixed; the // SingleCommandApplication / Command typed registry it // belongs to is an external-package todo!() stub. - let _ = SingleCommandApplication::new; + // let _ = SingleCommandApplication::new; // makes sure the command is find()'able by the name defined in composer.json, and the name isn't overridden in its configure() // TODO(phase-c): cmd is the PhpMixed result of reflection @@ -717,10 +740,15 @@ impl Application { }; // Compatibility layer for symfony/console <7.4 - // TODO(phase-c): self.inner.add() takes PhpMixed but must - // register a typed command instance; blocked on the Symfony - // command-registry model (external-package todo!() stub). - let _ = self.inner.add(cmd); + // TODO(phase-c): self.inner.add() takes Rc<RefCell<dyn Command>> + // but `cmd` here is the PhpMixed result of reflection-based + // plugin command instantiation; registering it as a typed + // command instance is blocked on the Symfony command-registry + // model (external-package todo!() stub). + let _ = &cmd; + todo!( + "plugin: register reflection-instantiated command on Application::add" + ); } } } @@ -731,7 +759,10 @@ impl Application { let mut start_time: Option<f64> = None; let result_outcome: anyhow::Result<i64> = (|| -> anyhow::Result<i64> { - if input.borrow().has_parameter_option(&["--profile"], false) { + if input + .borrow() + .has_parameter_option(PhpMixed::from(vec!["--profile"]), false) + { start_time = Some(microtime(true)); // PHP: $this->io->enableDebugging($startTime). // TODO(phase-c): enableDebugging exists only on ConsoleIO, not on IOInterface, @@ -745,7 +776,7 @@ impl Application { if input .borrow() - .has_parameter_option(&["--version", "-V"], true) + .has_parameter_option(PhpMixed::from(vec!["--version", "-V"]), true) { self.io.write_error(&sprintf( "<info>PHP</info> version <comment>%s</comment> (%s)", @@ -828,7 +859,11 @@ impl Application { ) -> anyhow::Result<Option<String>> { let working_dir = input .borrow() - .get_parameter_option(&["--working-dir", "-d"], PhpMixed::Null, true) + .get_parameter_option( + PhpMixed::from(vec!["--working-dir", "-d"]), + PhpMixed::Null, + true, + ) .as_string() .map(|s| s.to_string()); if let Some(ref wd) = working_dir { @@ -1089,7 +1124,6 @@ impl Application { // returns the application's typed InputDefinition, but the Symfony Application stub returns // PhpMixed. Until both land, getFirstArgument cannot be computed and we return None. let _ = input; - let _ = self.inner.get_definition(); None } @@ -1115,45 +1149,45 @@ impl Application { ) } - pub(crate) fn get_default_input_definition(&self) -> InputDefinition { + pub(crate) fn get_default_input_definition(&self) -> anyhow::Result<InputDefinition> { let mut definition = self.inner.get_default_input_definition(); definition.add_option(InputOption::new( "--profile", - None, + PhpMixed::Null, Some(InputOption::VALUE_NONE), - "Display timing and memory usage information", + "Display timing and memory usage information".to_string(), PhpMixed::Null, - )); + )?)?; definition.add_option(InputOption::new( "--no-plugins", - None, + PhpMixed::Null, Some(InputOption::VALUE_NONE), - "Whether to disable plugins.", + "Whether to disable plugins.".to_string(), PhpMixed::Null, - )); + )?)?; definition.add_option(InputOption::new( "--no-scripts", - None, + PhpMixed::Null, Some(InputOption::VALUE_NONE), - "Skips the execution of all scripts defined in composer.json file.", + "Skips the execution of all scripts defined in composer.json file.".to_string(), PhpMixed::Null, - )); + )?)?; definition.add_option(InputOption::new( "--working-dir", - Some("-d"), + PhpMixed::from("-d"), Some(InputOption::VALUE_REQUIRED), - "If specified, use the given directory as working directory.", + "If specified, use the given directory as working directory.".to_string(), PhpMixed::Null, - )); + )?)?; definition.add_option(InputOption::new( "--no-cache", - None, + PhpMixed::Null, Some(InputOption::VALUE_NONE), - "Prevent use of the cache", + "Prevent use of the cache".to_string(), PhpMixed::Null, - )); + )?)?; - definition + Ok(definition) } fn get_plugin_commands(&mut self) -> anyhow::Result<Vec<Box<dyn Command>>> { diff --git a/crates/shirabe/src/console/html_output_formatter.rs b/crates/shirabe/src/console/html_output_formatter.rs index a2158fa..b516c83 100644 --- a/crates/shirabe/src/console/html_output_formatter.rs +++ b/crates/shirabe/src/console/html_output_formatter.rs @@ -3,7 +3,8 @@ use indexmap::IndexMap; use shirabe_external_packages::composer::pcre::{CaptureKey, Preg}; use shirabe_external_packages::symfony::console::formatter::OutputFormatter; -use shirabe_external_packages::symfony::console::formatter::OutputFormatterStyle; +use shirabe_external_packages::symfony::console::formatter::OutputFormatterInterface; +use shirabe_external_packages::symfony::console::formatter::OutputFormatterStyleInterface; #[derive(Debug)] pub struct HtmlOutputFormatter { @@ -41,14 +42,19 @@ impl HtmlOutputFormatter { //8 => "conceal" ]; - pub fn new(styles: IndexMap<String, OutputFormatterStyle>) -> Self { + pub fn new(styles: IndexMap<String, Box<dyn OutputFormatterStyleInterface>>) -> Self { Self { inner: OutputFormatter::new(true, styles), } } - pub fn format(&self, message: Option<&str>) -> Option<String> { - let formatted = self.inner.format(message.unwrap_or("")); + pub fn format(&mut self, message: Option<&str>) -> anyhow::Result<Option<String>> { + let formatted = self.inner.format(message)?; + + let formatted = match formatted { + Some(formatted) => formatted, + None => return Ok(None), + }; let clear_escape_codes = "(?:39|49|0|22|24|25|27|28)"; let pattern = format!( @@ -56,7 +62,11 @@ impl HtmlOutputFormatter { clear_escape_codes, clear_escape_codes ); - Preg::replace_callback(&pattern, |matches| self.format_html(matches), &formatted).ok() + Ok(Some(Preg::replace_callback( + &pattern, + |matches| self.format_html(matches), + &formatted, + )?)) } fn format_html(&self, matches: &IndexMap<CaptureKey, String>) -> String { diff --git a/crates/shirabe/src/console/input/input_argument.rs b/crates/shirabe/src/console/input/input_argument.rs index 458ec61..81f38ac 100644 --- a/crates/shirabe/src/console/input/input_argument.rs +++ b/crates/shirabe/src/console/input/input_argument.rs @@ -21,7 +21,12 @@ impl InputArgument { default: Option<PhpMixed>, // TODO(cli-completion): suggested_values closure / list dropped along with completion support ) -> Result<Self> { - let inner = BaseInputArgument::new(name, mode, description, default); + let inner = BaseInputArgument::new( + name.to_string(), + mode, + description.to_string(), + default.unwrap_or(PhpMixed::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 92c4a51..3298be0 100644 --- a/crates/shirabe/src/console/input/input_option.rs +++ b/crates/shirabe/src/console/input/input_option.rs @@ -24,9 +24,10 @@ impl InputOption { default: Option<PhpMixed>, // TODO(cli-completion): suggested_values closure / list dropped along with completion support ) -> Result<Self> { - let shortcut_str = shortcut.as_ref().and_then(|s| s.as_string()); + let shortcut = shortcut.unwrap_or(PhpMixed::Null); let default_mixed = default.unwrap_or(PhpMixed::Null); - let inner = BaseInputOption::new(name, shortcut_str, mode, description, default_mixed); + let inner = + BaseInputOption::new(name, shortcut, mode, description.to_string(), default_mixed)?; Ok(Self { inner }) } } diff --git a/crates/shirabe/src/dependency_resolver/problem.rs b/crates/shirabe/src/dependency_resolver/problem.rs index 7841946..be2b0ad 100644 --- a/crates/shirabe/src/dependency_resolver/problem.rs +++ b/crates/shirabe/src/dependency_resolver/problem.rs @@ -816,7 +816,8 @@ impl Problem { OutputFormatter::escape(&format!( "https://packagist.org/security-advisories/{}", advisory_id - )), + )) + .expect("OutputFormatter::escape does not fail"), advisory_id ); } diff --git a/crates/shirabe/src/event_dispatcher/event_dispatcher.rs b/crates/shirabe/src/event_dispatcher/event_dispatcher.rs index 2124093..d209e1e 100644 --- a/crates/shirabe/src/event_dispatcher/event_dispatcher.rs +++ b/crates/shirabe/src/event_dispatcher/event_dispatcher.rs @@ -587,14 +587,11 @@ impl EventDispatcher { continue; } - let mut app = Application::new("", ""); + let mut app = Application::__construct("UNKNOWN", "UNKNOWN"); app.set_catch_exceptions(false); - if method_exists( - &PhpMixed::String("Application".to_string()), - "setCatchErrors", - ) { - app.set_catch_errors(false); - } + // PHP: if (method_exists($app, 'setCatchErrors')) { $app->setCatchErrors(false); } + // This Symfony Console port does not provide `setCatchErrors`, so the + // guarded call is skipped, matching `method_exists` evaluating to false. app.set_auto_exit(false); // TODO(plugin): instantiate command class dynamically: `new $className($event->getName())` todo!( @@ -615,7 +612,7 @@ impl EventDispatcher { let _refl_php_version_gate = PHP_VERSION_ID < 80100; todo!("\\ReflectionProperty on ConsoleIO::$output") } else { - ConsoleOutput::new(0, None, None) + ConsoleOutput::new(None, None, None)? }; let input_str = event .get_flags() @@ -623,8 +620,8 @@ impl EventDispatcher { .and_then(|v| v.as_string()) .unwrap_or(&args) .to_string(); - let mut input = StringInput::new(&input_str); - let mut output = output; + let input = StringInput::new(&input_str)?; + let output = output; Ok(app.run( Some(std::rc::Rc::new(std::cell::RefCell::new(input))), Some(std::rc::Rc::new(std::cell::RefCell::new(output))), diff --git a/crates/shirabe/src/factory.rs b/crates/shirabe/src/factory.rs index 975c558..78aa7ea 100644 --- a/crates/shirabe/src/factory.rs +++ b/crates/shirabe/src/factory.rs @@ -4,6 +4,7 @@ use indexmap::IndexMap; use shirabe_external_packages::symfony::console::formatter::OutputFormatter; use shirabe_external_packages::symfony::console::formatter::OutputFormatterStyle; +use shirabe_external_packages::symfony::console::formatter::OutputFormatterStyleInterface; use shirabe_external_packages::symfony::console::output::ConsoleOutput; use shirabe_php_shim::{ InvalidArgumentException, PATHINFO_EXTENSION, PHP_EOL, PHP_OS, Phar, PhpMixed, @@ -405,15 +406,19 @@ impl Factory { } } - pub fn create_additional_styles() -> IndexMap<String, OutputFormatterStyle> { - let mut styles: IndexMap<String, OutputFormatterStyle> = IndexMap::new(); + pub fn create_additional_styles() -> IndexMap<String, Box<dyn OutputFormatterStyleInterface>> { + let mut styles: IndexMap<String, Box<dyn OutputFormatterStyleInterface>> = IndexMap::new(); styles.insert( "highlight".to_string(), - OutputFormatterStyle::new(Some("red"), None, Some(vec![])), + Box::new(OutputFormatterStyle::new(Some("red"), None, vec![])), ); styles.insert( "warning".to_string(), - OutputFormatterStyle::new(Some("black"), Some("yellow"), Some(vec![])), + Box::new(OutputFormatterStyle::new( + Some("black"), + Some("yellow"), + vec![], + )), ); styles } @@ -423,10 +428,11 @@ impl Factory { let formatter = OutputFormatter::new(false, styles); ConsoleOutput::new( - shirabe_external_packages::symfony::console::output::output_interface::VERBOSITY_NORMAL, + Some(shirabe_external_packages::symfony::console::output::output_interface::VERBOSITY_NORMAL), None, Some(std::rc::Rc::new(std::cell::RefCell::new(formatter))), ) + .expect("ConsoleOutput::new does not fail for stdout/stderr streams") } /// Creates a Composer instance diff --git a/crates/shirabe/src/installer/suggested_packages_reporter.rs b/crates/shirabe/src/installer/suggested_packages_reporter.rs index 65977ac..875fdc6 100644 --- a/crates/shirabe/src/installer/suggested_packages_reporter.rs +++ b/crates/shirabe/src/installer/suggested_packages_reporter.rs @@ -201,6 +201,7 @@ impl SuggestedPackagesReporter { fn escape_output(&self, string: &str) -> String { OutputFormatter::escape(&self.remove_control_characters(string)) + .expect("OutputFormatter::escape does not fail") } fn remove_control_characters(&self, string: &str) -> String { diff --git a/crates/shirabe/src/io/buffer_io.rs b/crates/shirabe/src/io/buffer_io.rs index 988c153..8c10dee 100644 --- a/crates/shirabe/src/io/buffer_io.rs +++ b/crates/shirabe/src/io/buffer_io.rs @@ -6,11 +6,11 @@ use shirabe_external_packages::composer::pcre::Preg; use shirabe_external_packages::symfony::console::formatter::OutputFormatterInterface; use shirabe_external_packages::symfony::console::helper::HelperInterface; use shirabe_external_packages::symfony::console::helper::HelperSet; +use shirabe_external_packages::symfony::console::helper::HelperSetKey; use shirabe_external_packages::symfony::console::helper::QuestionHelper; use shirabe_external_packages::symfony::console::input::InputInterface; use shirabe_external_packages::symfony::console::input::StringInput; use shirabe_external_packages::symfony::console::output::OutputInterface; -use shirabe_external_packages::symfony::console::output::StreamOutput; use shirabe_php_shim::{ PHP_EOL, PhpMixed, RuntimeException, fopen, fseek, fwrite, rewind, stream_get_contents, strip_tags, @@ -27,7 +27,7 @@ impl BufferIO { verbosity: i64, formatter: Option<Box<dyn OutputFormatterInterface>>, ) -> Result<Self> { - let mut input_obj = StringInput::new(&input); + let mut input_obj = StringInput::new(&input)?; input_obj.set_interactive(false); let stream = fopen("php://memory", "rw"); @@ -39,21 +39,34 @@ impl BufferIO { .into()); } - let decorated = formatter.as_ref().map_or(false, |f| f.is_decorated()); - // TODO(phase-c): wire StreamOutput as the output. The console tree merge made StreamOutput - // implement the unified OutputInterface; StreamOutput::new is still a stub. + let _decorated = formatter.as_ref().map_or(false, |f| f.is_decorated()); + // TODO(phase-c): wire StreamOutput as the output. StreamOutput::new requires a + // PhpResource, but `fopen` here yields a PhpMixed; PhpMixed has no resource variant, + // so the stream cannot be passed through yet (same PhpResource/PhpMixed gap noted in + // QuestionHelper). The constructed StreamOutput is therefore not wired as the output. + // formatter, stream and verbosity feed the pending StreamOutput::new wiring below. let _ = formatter; - let _ = StreamOutput::new(stream, verbosity, Some(decorated)); + let _ = stream; + let _ = verbosity; let output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>> = - todo!("wire StreamOutput as the ConsoleIO output"); + todo!("wire StreamOutput as the ConsoleIO output (needs PhpResource stream)"); + + let question_helper: std::rc::Rc<std::cell::RefCell<dyn HelperInterface>> = + std::rc::Rc::new(std::cell::RefCell::new(QuestionHelper::default())); + let mut helpers: indexmap::IndexMap< + HelperSetKey, + std::rc::Rc<std::cell::RefCell<dyn HelperInterface>>, + > = indexmap::IndexMap::new(); + helpers.insert(HelperSetKey::Int(0), question_helper); + let helper_set = std::rc::Rc::new(std::cell::RefCell::new(HelperSet::default())); + HelperSet::new(&helper_set, helpers); + let helper_set = helper_set.borrow().clone(); - let helpers: Vec<std::rc::Rc<std::cell::RefCell<dyn HelperInterface>>> = - vec![std::rc::Rc::new(std::cell::RefCell::new(QuestionHelper))]; let inner = ConsoleIO::new( std::rc::Rc::new(std::cell::RefCell::new(input_obj)) as std::rc::Rc<std::cell::RefCell<dyn InputInterface>>, output, - HelperSet::new(helpers), + helper_set, ); Ok(Self { inner }) diff --git a/crates/shirabe/src/io/console_io.rs b/crates/shirabe/src/io/console_io.rs index 18287e3..2371dfa 100644 --- a/crates/shirabe/src/io/console_io.rs +++ b/crates/shirabe/src/io/console_io.rs @@ -10,6 +10,7 @@ use shirabe_external_packages::symfony::console::helper::ProgressBar; use shirabe_external_packages::symfony::console::helper::QuestionHelper; use shirabe_external_packages::symfony::console::helper::Table; use shirabe_external_packages::symfony::console::input::InputInterface; +use shirabe_external_packages::symfony::console::output::ConsoleOutput; use shirabe_external_packages::symfony::console::output::ConsoleOutputInterface; use shirabe_external_packages::symfony::console::output::output_interface::{ self as output_interface, OutputInterface, @@ -17,9 +18,8 @@ use shirabe_external_packages::symfony::console::output::output_interface::{ use shirabe_external_packages::symfony::console::question::ChoiceQuestion; use shirabe_external_packages::symfony::console::question::Question; use shirabe_php_shim::{ - PhpMixed, array_filter, array_keys, array_search, count, function_exists, implode, in_array, - is_array, is_string, mb_check_encoding, mb_convert_encoding, microtime, sprintf, str_repeat, - strip_tags, strlen, + AsAny, PhpMixed, array_search, function_exists, implode, in_array, is_array, is_string, + mb_check_encoding, mb_convert_encoding, microtime, sprintf, str_repeat, strip_tags, strlen, }; use std::cell::RefCell; @@ -129,9 +129,18 @@ impl ConsoleIO { messages }; - if stderr && let Some(console_output) = self.output.borrow().as_console_output_interface() { - console_output.get_error_output().borrow().write( - &Self::to_string_list(&messages).join(if newline { "\n" } else { "" }), + let error_output = if stderr { + let output = self.output.borrow(); + (*output) + .as_any() + .downcast_ref::<ConsoleOutput>() + .map(|console_output| console_output.get_error_output()) + } else { + None + }; + if let Some(error_output) = error_output { + error_output.borrow().write( + &[Self::to_string_list(&messages).join(if newline { "\n" } else { "" })], newline, sf_verbosity, ); @@ -145,7 +154,7 @@ impl ConsoleIO { } self.output.borrow().write( - &Self::to_string_list(&messages).join(if newline { "\n" } else { "" }), + &[Self::to_string_list(&messages).join(if newline { "\n" } else { "" })], newline, sf_verbosity, ); @@ -239,7 +248,8 @@ impl ConsoleIO { } pub fn get_progress_bar(&self, max: i64) -> ProgressBar { - ProgressBar::new(self.get_error_output(), max) + // PHP ProgressBar::__construct default $minSecondsBetweenRedraws = 1 / 25. + ProgressBar::new(self.get_error_output(), max, 1.0 / 25.0) } pub fn get_table(&self) -> Table { @@ -247,8 +257,14 @@ impl ConsoleIO { } fn get_error_output(&self) -> std::rc::Rc<std::cell::RefCell<dyn OutputInterface>> { - if let Some(console_output) = self.output.borrow().as_console_output_interface() { - return console_output.get_error_output(); + let output = self.output.borrow(); + let error_output = (*output) + .as_any() + .downcast_ref::<ConsoleOutput>() + .map(|console_output| console_output.get_error_output()); + drop(output); + if let Some(error_output) = error_output { + return error_output; } self.output.clone() @@ -358,6 +374,31 @@ impl ConsoleIO { _ => vec![], } } + + /// Resolves the "question" helper and delegates to `QuestionHelper::ask`. + /// + /// PHP: `$this->helperSet->get('question')->ask($this->input, $this->getErrorOutput(), $question)`. + /// `HelperSet::get` and `QuestionHelper::ask` both surface PHP exceptions; ConsoleIO does not + /// catch them, so an unrecoverable error here is a PHP fatal. The double `Result` is collapsed: + /// the outer `anyhow::Result` (fatal) and the inner `MissingInputException` (unhandled, hence + /// also fatal in PHP) both abort. + fn ask_question(&self, question: &Question) -> PhpMixed { + let helper_rc = self + .helper_set + .get("question") + .expect("the \"question\" helper is always registered"); + let error_output = self.get_error_output(); + let mut helper = helper_rc.borrow_mut(); + let question_helper = (*helper) + .as_any_mut() + .downcast_mut::<QuestionHelper>() + .expect("the \"question\" helper is a QuestionHelper"); + let mut input = self.input.borrow_mut(); + question_helper + .ask(&mut *input, error_output, question) + .expect("QuestionHelper::ask raised a fatal error") + .expect("QuestionHelper::ask returned no input") + } } impl IOInterfaceImmutable for ConsoleIO { @@ -434,7 +475,6 @@ impl IOInterfaceImmutable for ConsoleIO { } fn ask(&self, question: String, default: PhpMixed) -> PhpMixed { - let helper = self.helper_set.get::<QuestionHelper>("question"); let sanitized_question = Self::sanitize(PhpMixed::String(question), true) .as_string() .unwrap_or("") @@ -444,15 +484,12 @@ impl IOInterfaceImmutable for ConsoleIO { } else { Some(default) }; - let question = Question::new(&sanitized_question, sanitized_default); + let question = Question::new(sanitized_question, sanitized_default); - helper - .borrow() - .ask(self.input.clone(), self.get_error_output(), &question) + self.ask_question(&question) } fn ask_confirmation(&self, question: String, default: bool) -> bool { - let helper = self.helper_set.get::<QuestionHelper>("question"); let sanitized = Self::sanitize(PhpMixed::String(question), true) .as_string() .unwrap_or("") @@ -464,11 +501,7 @@ impl IOInterfaceImmutable for ConsoleIO { "/^no?$/i".to_string(), ); - let result = helper.borrow().ask( - self.input.clone(), - self.get_error_output(), - question.inner(), - ); + let result = self.ask_question(question.inner()); result.as_bool().unwrap_or(false) } @@ -488,18 +521,33 @@ impl IOInterfaceImmutable for ConsoleIO { } else { Some(default) }; - let mut question = Question::new(&sanitized_question, sanitized_default); - // Question::set_validator takes Fn(Option<PhpMixed>) -> Result<PhpMixed>; adapt the - // None answer to PHP's null and forward the validator's Result unchanged. - let adapted: Box<dyn Fn(Option<PhpMixed>) -> anyhow::Result<PhpMixed>> = - Box::new(move |answer: Option<PhpMixed>| validator(answer.unwrap_or(PhpMixed::Null))); + let mut question = Question::new(sanitized_question, sanitized_default); + // Question::set_validator takes Fn(Option<PhpMixed>) -> Result<PhpMixed, InvalidArgumentException>; + // adapt the None answer to PHP's null and translate the validator's anyhow error into the + // InvalidArgumentException the Question validator type expects (QuestionHelper retries on it). + let adapted: Box< + dyn Fn( + Option<PhpMixed>, + ) -> Result< + PhpMixed, + shirabe_external_packages::symfony::console::exception::InvalidArgumentException, + >, + > = Box::new(move |answer: Option<PhpMixed>| { + validator(answer.unwrap_or(PhpMixed::Null)).map_err(|e| { + shirabe_external_packages::symfony::console::exception::InvalidArgumentException( + shirabe_php_shim::InvalidArgumentException { + message: e.to_string(), + code: 0, + }, + ) + }) + }); question.set_validator(Some(adapted)); - question.set_max_attempts(attempts); + question + .set_max_attempts(attempts) + .map_err(|e| anyhow::anyhow!(e.0.message))?; - let helper = self.helper_set.get::<QuestionHelper>("question"); - Ok(helper - .borrow() - .ask(self.input.clone(), self.get_error_output(), &question)) + Ok(self.ask_question(&question)) } fn ask_and_hide_answer(&self, question: String) -> Option<String> { @@ -507,13 +555,13 @@ impl IOInterfaceImmutable for ConsoleIO { .as_string() .unwrap_or("") .to_string(); - let mut question = Question::new(&sanitized_question, Some(PhpMixed::Null)); - question.set_hidden(true); + let mut question = Question::new(sanitized_question, Some(PhpMixed::Null)); + // setHidden only throws when an autocompleter is set, which is not the case here. + question + .set_hidden(true) + .expect("a freshly constructed question has no autocompleter"); - let helper = self.helper_set.get::<QuestionHelper>("question"); - let result = helper - .borrow() - .ask(self.input.clone(), self.get_error_output(), &question); + let result = self.ask_question(&question); result.as_string().map(|s| s.to_string()) } @@ -533,35 +581,45 @@ impl IOInterfaceImmutable for ConsoleIO { .map(|s| Box::new(PhpMixed::String(s))) .collect(), ); - let _helper = self.helper_set.get::<QuestionHelper>("question"); + let _helper = self + .helper_set + .get("question") + .expect("the \"question\" helper is always registered"); let sanitized_question = Self::sanitize(PhpMixed::String(question), true) .as_string() .unwrap_or("") .to_string(); - // TODO(phase-b): ChoiceQuestion::new expects Vec<PhpMixed>; collect from the - // sanitized PhpMixed::List. + // ChoiceQuestion::new expects an IndexMap<String, Box<PhpMixed>>; project the + // sanitized choice list/map into the keyed form, preserving keys. let sanitized_choices_mixed = Self::sanitize(choices.clone(), true); - let sanitized_choices: Vec<PhpMixed> = match sanitized_choices_mixed { - PhpMixed::List(l) => l.into_iter().map(|b| *b).collect(), - PhpMixed::Array(a) => a.into_values().map(|b| *b).collect(), - other => vec![other], + let sanitized_choices: IndexMap<String, Box<PhpMixed>> = match sanitized_choices_mixed { + PhpMixed::List(l) => l + .into_iter() + .enumerate() + .map(|(i, b)| (i.to_string(), b)) + .collect(), + PhpMixed::Array(a) => a, + other => indexmap! { "0".to_string() => Box::new(other) }, }; let sanitized_default = if is_string(&default) { Some(Self::sanitize(default, true)) } else { Some(default) }; + // Empty choices raise a LogicException (programmer error in PHP). let mut question = - ChoiceQuestion::new(&sanitized_question, sanitized_choices, sanitized_default); + ChoiceQuestion::new(sanitized_question, sanitized_choices, sanitized_default) + .expect("select() always provides at least one choice"); // PHP: IOInterface requires false, and Question requires null or int - let max_attempts = match attempts { + let _max_attempts = match attempts { PhpMixed::Bool(false) => None, PhpMixed::Int(i) => Some(i), _ => None, }; - // ChoiceQuestion delegates set_max_attempts to its inner Question. - question.0.set_max_attempts(max_attempts); - question.set_error_message(&error_message); + // TODO(phase-c): PHP calls $question->setMaxAttempts($attempts), inherited from Question. + // ChoiceQuestion (shirabe-external-packages) keeps its inner Question private and exposes + // no setMaxAttempts passthrough, so the attempts cannot be propagated here yet. See report. + question.set_error_message(error_message); question.set_multiselect(multiselect); // TODO(phase-c): QuestionHelper::ask takes a concrete `&Question`, but PHP passes a diff --git a/crates/shirabe/src/question/strict_confirmation_question.rs b/crates/shirabe/src/question/strict_confirmation_question.rs index 5ce32f2..725005a 100644 --- a/crates/shirabe/src/question/strict_confirmation_question.rs +++ b/crates/shirabe/src/question/strict_confirmation_question.rs @@ -1,6 +1,5 @@ //! ref: composer/src/Composer/Question/StrictConfirmationQuestion.php -use anyhow::Result; use shirabe_external_packages::composer::pcre::Preg; use shirabe_external_packages::symfony::console::exception::InvalidArgumentException; use shirabe_external_packages::symfony::console::question::Question; @@ -19,7 +18,7 @@ impl StrictConfirmationQuestion { true_answer_regex: String, false_answer_regex: String, ) -> Self { - let inner = Question::new(&question, Some(PhpMixed::Bool(default))); + let inner = Question::new(question, Some(PhpMixed::Bool(default))); let mut this = Self { inner, true_answer_regex, @@ -36,19 +35,19 @@ impl StrictConfirmationQuestion { &self.inner } - fn get_default_normalizer(&self) -> Box<dyn Fn(&PhpMixed) -> PhpMixed> { + fn get_default_normalizer(&self) -> Box<dyn Fn(PhpMixed) -> PhpMixed> { let default = self.inner.get_default(); let true_regex = self.true_answer_regex.clone(); let false_regex = self.false_answer_regex.clone(); - Box::new(move |answer: &PhpMixed| { - if is_bool(answer) { - return answer.clone(); + Box::new(move |answer: PhpMixed| { + if is_bool(&answer) { + return answer; } - if empty(answer) && default.as_ref().is_some_and(|d| !empty(d)) { - return default.clone().unwrap_or(PhpMixed::Null); + if empty(&answer) && !empty(&default) { + return default.clone(); } - if let PhpMixed::String(s) = answer { + if let PhpMixed::String(s) = &answer { if Preg::is_match(&true_regex, s).unwrap_or(false) { return PhpMixed::Bool(true); } @@ -60,15 +59,18 @@ impl StrictConfirmationQuestion { }) } - fn get_default_validator(&self) -> Box<dyn Fn(Option<PhpMixed>) -> Result<PhpMixed>> { + fn get_default_validator( + &self, + ) -> Box<dyn Fn(Option<PhpMixed>) -> Result<PhpMixed, InvalidArgumentException>> { Box::new(|answer: Option<PhpMixed>| { let answer = answer.unwrap_or(PhpMixed::Null); if !is_bool(&answer) { - return Err(InvalidArgumentException { - message: "Please answer yes, y, no, or n.".to_string(), - code: 0, - } - .into()); + return Err(InvalidArgumentException( + shirabe_php_shim::InvalidArgumentException { + message: "Please answer yes, y, no, or n.".to_string(), + code: 0, + }, + )); } Ok(answer) }) |
