aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe/src/command/config_command.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/shirabe/src/command/config_command.rs')
-rw-r--r--crates/shirabe/src/command/config_command.rs736
1 files changed, 366 insertions, 370 deletions
diff --git a/crates/shirabe/src/command/config_command.rs b/crates/shirabe/src/command/config_command.rs
index c4409f20..2727f36b 100644
--- a/crates/shirabe/src/command/config_command.rs
+++ b/crates/shirabe/src/command/config_command.rs
@@ -61,15 +61,7 @@ impl ConfigCommand {
"suggest",
"extra",
];
-}
-
-impl Default for ConfigCommand {
- fn default() -> Self {
- Self::new()
- }
-}
-impl ConfigCommand {
pub fn new() -> Self {
let command = ConfigCommand {
base_command_data: BaseCommandData::new(None),
@@ -84,6 +76,372 @@ impl ConfigCommand {
.expect("ConfigCommand::configure uses static, valid metadata");
command
}
+
+ pub(crate) fn handle_single_value(
+ &self,
+ key: &str,
+ callbacks: &(ValidatorFn, NormalizerFn),
+ values: &[String],
+ method: &str,
+ ) -> anyhow::Result<()> {
+ let (validator, normalizer) = callbacks;
+ if 1 != values.len() {
+ return Err(RuntimeException {
+ message: "You can only pass one value. Example: shirabe config process-timeout 300"
+ .to_string(),
+ code: 0,
+ }
+ .into());
+ }
+
+ let validation = validator(&PhpMixed::String(values[0].clone()));
+ if validation.as_bool() != Some(true) {
+ let suffix = if !validation.is_null() && validation.as_bool() != Some(false) {
+ format!(" ({})", validation.as_string().unwrap_or(""))
+ } else {
+ String::new()
+ };
+ return Err(RuntimeException {
+ message: format!("\"{}\" is an invalid value{}", values[0].clone(), suffix),
+ code: 0,
+ }
+ .into());
+ }
+
+ let normalized_value = normalizer(&PhpMixed::String(values[0].clone()));
+
+ if key == "disable-tls" {
+ let config = self.config.borrow().as_ref().unwrap().clone();
+ if !normalized_value.as_bool().unwrap_or(false)
+ && config
+ .borrow()
+ .get("disable-tls")
+ .as_bool()
+ .unwrap_or(false)
+ {
+ self.get_io().write_error(
+ "<info>You are now running Composer with SSL/TLS protection enabled.</info>",
+ );
+ } else if normalized_value.as_bool().unwrap_or(false)
+ && !config
+ .borrow()
+ .get("disable-tls")
+ .as_bool()
+ .unwrap_or(false)
+ {
+ self.get_io().write_error("<warning>You are now running Composer with SSL/TLS protection disabled.</warning>");
+ }
+ }
+
+ let mut config_source = self.config_source.borrow_mut();
+ let config_source = config_source.as_mut().unwrap();
+ match method {
+ "addConfigSetting" => config_source.add_config_setting(key, normalized_value)?,
+ "addProperty" => config_source.add_property(key, normalized_value)?,
+ _ => unreachable!(),
+ }
+ Ok(())
+ }
+
+ pub(crate) fn handle_multi_value(
+ &self,
+ key: &str,
+ callbacks: &(ValidatorFn, NormalizerFn),
+ values: &[String],
+ method: &str,
+ ) -> anyhow::Result<()> {
+ let (validator, normalizer) = callbacks;
+ let values_mixed =
+ PhpMixed::List(values.iter().map(|s| PhpMixed::String(s.clone())).collect());
+ let validation = validator(&values_mixed);
+ if validation.as_bool() != Some(true) {
+ let suffix = if !validation.is_null() && validation.as_bool() != Some(false) {
+ format!(" ({})", validation.as_string().unwrap_or(""))
+ } else {
+ String::new()
+ };
+ return Err(RuntimeException {
+ message: format!(
+ "{} is an invalid value{}",
+ PhpMixed::from(json_encode(&values_mixed).ok()),
+ suffix
+ ),
+ code: 0,
+ }
+ .into());
+ }
+
+ let mut config_source = self.config_source.borrow_mut();
+ let config_source = config_source.as_mut().unwrap();
+ match method {
+ "addConfigSetting" => {
+ config_source.add_config_setting(key, normalizer(&values_mixed))?
+ }
+ "addProperty" => config_source.add_property(key, normalizer(&values_mixed))?,
+ _ => unreachable!(),
+ }
+ Ok(())
+ }
+
+ /// Display the contents of the file in a pretty formatted way
+ pub(crate) fn list_configuration(
+ &self,
+ contents: PhpMixed,
+ raw_contents: PhpMixed,
+ output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>,
+ k: Option<String>,
+ show_source: bool,
+ ) {
+ let orig_k = k.clone();
+ let contents_arr = contents.as_array().cloned().unwrap_or_default();
+ let raw_contents_arr = raw_contents.as_array().cloned().unwrap_or_default();
+ let mut k = k;
+ for (key, value) in &contents_arr {
+ if k.is_none() && !matches!(key.as_str(), "config" | "repositories") {
+ continue;
+ }
+
+ let raw_val = raw_contents_arr.get(key).cloned().unwrap_or(PhpMixed::Null);
+
+ let value_inner = value.clone();
+
+ if is_array(&value_inner)
+ && (!is_numeric(&key_first_key(&value_inner).unwrap_or_default().into())
+ || (key == "repositories" && k.is_none()))
+ {
+ let mut new_k = k.clone().unwrap_or_default();
+ new_k.push_str(&Preg::replace(
+ php_regex!("{^config\\.}"),
+ "",
+ &format!("{}.", key),
+ ));
+ k = Some(new_k);
+ self.list_configuration(
+ value_inner,
+ raw_val,
+ output.clone(),
+ k.clone(),
+ show_source,
+ );
+ k = orig_k.clone();
+
+ continue;
+ }
+
+ let value_display: String = if is_array(&value_inner) {
+ let arr_strs: Vec<String> = value_inner
+ .as_list()
+ .map(|l| {
+ l.iter()
+ .map(|val| {
+ if is_array(val) {
+ json_encode(val).unwrap_or_default()
+ } else {
+ val.as_string().unwrap_or("").to_string()
+ }
+ })
+ .collect::<Vec<_>>()
+ })
+ .unwrap_or_default();
+ format!("[{}]", implode(", ", &arr_strs))
+ } else if is_bool(&value_inner) {
+ var_export(&value_inner, true)
+ } else {
+ value_inner.as_string().unwrap_or("").to_string()
+ };
+
+ let source = if show_source {
+ format!(
+ " ({})",
+ self.config
+ .borrow()
+ .as_ref()
+ .unwrap()
+ .borrow_mut()
+ .get_source_of_value(&format!("{}{}", k.clone().unwrap_or_default(), key))
+ )
+ } else {
+ String::new()
+ };
+
+ let link: String =
+ if k.is_some() && strpos(k.as_ref().unwrap(), "repositories") == Some(0) {
+ "https://getcomposer.org/doc/05-repositories.md".to_string()
+ } else {
+ let id_source = if k.as_deref() == Some("") || k.is_none() {
+ key.clone()
+ } else {
+ k.clone().unwrap()
+ };
+ let id = Preg::replace(php_regex!("{\\..*$}"), "", &id_source);
+ let id = Preg::replace(
+ php_regex!("{[^a-z0-9]}i"),
+ "-",
+ &strtolower(&shirabe_php_shim::trim(&id, Some(" \t\n\r\0\u{0B}"))),
+ );
+ let id = Preg::replace(php_regex!("{-+}"), "-", &id);
+ format!("https://getcomposer.org/doc/06-config.md#{}", id)
+ };
+ if is_string(&raw_val)
+ && raw_val
+ .as_string()
+ .map(|s| s.to_string())
+ .unwrap_or_default()
+ != value_display
+ {
+ self.get_io().write3(
+ &format!(
+ "[<fg=yellow;href={}>{}{}</>] <info>{} ({})</info>{}",
+ link,
+ k.clone().unwrap_or_default(),
+ key,
+ raw_val.as_string().unwrap_or(""),
+ value_display,
+ source
+ ),
+ true,
+ io_interface::QUIET,
+ );
+ } else {
+ self.get_io().write3(
+ &format!(
+ "[<fg=yellow;href={}>{}{}</>] <info>{}</info>{}",
+ link,
+ k.clone().unwrap_or_default(),
+ key,
+ value_display,
+ source
+ ),
+ true,
+ io_interface::QUIET,
+ );
+ }
+ }
+ }
+
+ /// Suggest setting-keys, while taking given options in account.
+ fn suggest_setting_keys(&self) -> crate::console::input::SuggestedValues {
+ crate::console::input::SuggestedValues::Closure(Box::new(|this, input, _suggestions| {
+ if input.get_option("list")?.to_bool()
+ || input.get_option("editor")?.to_bool()
+ || input.get_option("auth")?.to_bool()
+ {
+ return Ok(vec![]);
+ }
+
+ let this = this
+ .as_any()
+ .downcast_ref::<ConfigCommand>()
+ .expect("suggestSettingKeys is bound to ConfigCommand");
+ // PHP passes the CompletionInput itself; the accessors only read from it, so a
+ // clone behind a fresh handle is equivalent.
+ let input_handle: std::rc::Rc<
+ std::cell::RefCell<
+ dyn shirabe_external_packages::symfony::console::input::InputInterface,
+ >,
+ > = std::rc::Rc::new(std::cell::RefCell::new(input.clone()));
+
+ // initialize configuration
+ let mut config = Factory::create_config(None, None)?;
+
+ // load configuration
+ let config_file = JsonFile::new(
+ this.get_composer_config_file(input_handle.clone(), &config)?,
+ None,
+ None,
+ )?;
+ if config_file.exists() {
+ let path = config_file.get_path().to_string();
+ let data = config_file.read()?.as_array().cloned().unwrap_or_default();
+ config.merge(&data, &path);
+ }
+
+ // load auth-configuration
+ let auth_config_file = JsonFile::new(
+ this.get_auth_config_file(input_handle.clone(), &config)?,
+ None,
+ None,
+ )?;
+ if auth_config_file.exists() {
+ let path = auth_config_file.get_path().to_string();
+ let mut data = IndexMap::new();
+ data.insert("config".to_string(), auth_config_file.read()?);
+ config.merge(&data, &path);
+ }
+
+ // collect all configuration setting-keys
+ let raw_config = config.raw();
+ let mut keys = flatten_setting_keys(
+ raw_config.get("config").cloned().unwrap_or(PhpMixed::Null),
+ "",
+ );
+ keys.extend(flatten_setting_keys(
+ raw_config
+ .get("repositories")
+ .cloned()
+ .unwrap_or(PhpMixed::Null),
+ "repositories.",
+ ));
+
+ // if unsetting …
+ if input.get_option("unset")?.to_bool() {
+ // … keep only the currently customized setting-keys …
+ let sources = [
+ config_file.get_path().to_string(),
+ auth_config_file.get_path().to_string(),
+ ];
+ keys.retain(|key| sources.contains(&config.get_source_of_value(key)));
+
+ // … else if showing or setting a value …
+ } else {
+ // … add all configurable package-properties, no matter if it exist
+ keys.extend(
+ Self::CONFIGURABLE_PACKAGE_PROPERTIES
+ .iter()
+ .map(|property| property.to_string()),
+ );
+
+ // it would be nice to distinguish between showing and setting
+ // a value, but that makes the implementation much more complex
+ // and partially impossible because symfony's implementation
+ // does not complete arguments followed by other arguments
+ }
+
+ // add all existing configurable package-properties
+ if config_file.exists() {
+ let properties: IndexMap<String, PhpMixed> = config_file
+ .read()?
+ .as_array()
+ .cloned()
+ .unwrap_or_default()
+ .into_iter()
+ .filter(|(key, _)| {
+ Self::CONFIGURABLE_PACKAGE_PROPERTIES.contains(&key.as_str())
+ })
+ .collect();
+
+ keys.extend(flatten_setting_keys(PhpMixed::Array(properties), ""));
+ }
+
+ // filter settings-keys by completion value
+ let completion_value = input.get_completion_value();
+
+ if !completion_value.is_empty() {
+ keys.retain(|key| key.starts_with(&completion_value));
+ }
+
+ keys.sort();
+
+ keys.dedup();
+ Ok(keys)
+ }))
+ }
+}
+
+impl Default for ConfigCommand {
+ fn default() -> Self {
+ Self::new()
+ }
}
impl Command for ConfigCommand {
@@ -1275,368 +1633,6 @@ impl BaseCommand for ConfigCommand {
crate::delegate_base_command_trait_impls_to_inner!(base_command_data);
}
-impl ConfigCommand {
- pub(crate) fn handle_single_value(
- &self,
- key: &str,
- callbacks: &(ValidatorFn, NormalizerFn),
- values: &[String],
- method: &str,
- ) -> anyhow::Result<()> {
- let (validator, normalizer) = callbacks;
- if 1 != values.len() {
- return Err(RuntimeException {
- message: "You can only pass one value. Example: shirabe config process-timeout 300"
- .to_string(),
- code: 0,
- }
- .into());
- }
-
- let validation = validator(&PhpMixed::String(values[0].clone()));
- if validation.as_bool() != Some(true) {
- let suffix = if !validation.is_null() && validation.as_bool() != Some(false) {
- format!(" ({})", validation.as_string().unwrap_or(""))
- } else {
- String::new()
- };
- return Err(RuntimeException {
- message: format!("\"{}\" is an invalid value{}", values[0].clone(), suffix),
- code: 0,
- }
- .into());
- }
-
- let normalized_value = normalizer(&PhpMixed::String(values[0].clone()));
-
- if key == "disable-tls" {
- let config = self.config.borrow().as_ref().unwrap().clone();
- if !normalized_value.as_bool().unwrap_or(false)
- && config
- .borrow()
- .get("disable-tls")
- .as_bool()
- .unwrap_or(false)
- {
- self.get_io().write_error(
- "<info>You are now running Composer with SSL/TLS protection enabled.</info>",
- );
- } else if normalized_value.as_bool().unwrap_or(false)
- && !config
- .borrow()
- .get("disable-tls")
- .as_bool()
- .unwrap_or(false)
- {
- self.get_io().write_error("<warning>You are now running Composer with SSL/TLS protection disabled.</warning>");
- }
- }
-
- let mut config_source = self.config_source.borrow_mut();
- let config_source = config_source.as_mut().unwrap();
- match method {
- "addConfigSetting" => config_source.add_config_setting(key, normalized_value)?,
- "addProperty" => config_source.add_property(key, normalized_value)?,
- _ => unreachable!(),
- }
- Ok(())
- }
-
- pub(crate) fn handle_multi_value(
- &self,
- key: &str,
- callbacks: &(ValidatorFn, NormalizerFn),
- values: &[String],
- method: &str,
- ) -> anyhow::Result<()> {
- let (validator, normalizer) = callbacks;
- let values_mixed =
- PhpMixed::List(values.iter().map(|s| PhpMixed::String(s.clone())).collect());
- let validation = validator(&values_mixed);
- if validation.as_bool() != Some(true) {
- let suffix = if !validation.is_null() && validation.as_bool() != Some(false) {
- format!(" ({})", validation.as_string().unwrap_or(""))
- } else {
- String::new()
- };
- return Err(RuntimeException {
- message: format!(
- "{} is an invalid value{}",
- PhpMixed::from(json_encode(&values_mixed).ok()),
- suffix
- ),
- code: 0,
- }
- .into());
- }
-
- let mut config_source = self.config_source.borrow_mut();
- let config_source = config_source.as_mut().unwrap();
- match method {
- "addConfigSetting" => {
- config_source.add_config_setting(key, normalizer(&values_mixed))?
- }
- "addProperty" => config_source.add_property(key, normalizer(&values_mixed))?,
- _ => unreachable!(),
- }
- Ok(())
- }
-
- /// Display the contents of the file in a pretty formatted way
- pub(crate) fn list_configuration(
- &self,
- contents: PhpMixed,
- raw_contents: PhpMixed,
- output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>,
- k: Option<String>,
- show_source: bool,
- ) {
- let orig_k = k.clone();
- let contents_arr = contents.as_array().cloned().unwrap_or_default();
- let raw_contents_arr = raw_contents.as_array().cloned().unwrap_or_default();
- let mut k = k;
- for (key, value) in &contents_arr {
- if k.is_none() && !matches!(key.as_str(), "config" | "repositories") {
- continue;
- }
-
- let raw_val = raw_contents_arr.get(key).cloned().unwrap_or(PhpMixed::Null);
-
- let value_inner = value.clone();
-
- if is_array(&value_inner)
- && (!is_numeric(&key_first_key(&value_inner).unwrap_or_default().into())
- || (key == "repositories" && k.is_none()))
- {
- let mut new_k = k.clone().unwrap_or_default();
- new_k.push_str(&Preg::replace(
- php_regex!("{^config\\.}"),
- "",
- &format!("{}.", key),
- ));
- k = Some(new_k);
- self.list_configuration(
- value_inner,
- raw_val,
- output.clone(),
- k.clone(),
- show_source,
- );
- k = orig_k.clone();
-
- continue;
- }
-
- let value_display: String = if is_array(&value_inner) {
- let arr_strs: Vec<String> = value_inner
- .as_list()
- .map(|l| {
- l.iter()
- .map(|val| {
- if is_array(val) {
- json_encode(val).unwrap_or_default()
- } else {
- val.as_string().unwrap_or("").to_string()
- }
- })
- .collect::<Vec<_>>()
- })
- .unwrap_or_default();
- format!("[{}]", implode(", ", &arr_strs))
- } else if is_bool(&value_inner) {
- var_export(&value_inner, true)
- } else {
- value_inner.as_string().unwrap_or("").to_string()
- };
-
- let source = if show_source {
- format!(
- " ({})",
- self.config
- .borrow()
- .as_ref()
- .unwrap()
- .borrow_mut()
- .get_source_of_value(&format!("{}{}", k.clone().unwrap_or_default(), key))
- )
- } else {
- String::new()
- };
-
- let link: String =
- if k.is_some() && strpos(k.as_ref().unwrap(), "repositories") == Some(0) {
- "https://getcomposer.org/doc/05-repositories.md".to_string()
- } else {
- let id_source = if k.as_deref() == Some("") || k.is_none() {
- key.clone()
- } else {
- k.clone().unwrap()
- };
- let id = Preg::replace(php_regex!("{\\..*$}"), "", &id_source);
- let id = Preg::replace(
- php_regex!("{[^a-z0-9]}i"),
- "-",
- &strtolower(&shirabe_php_shim::trim(&id, Some(" \t\n\r\0\u{0B}"))),
- );
- let id = Preg::replace(php_regex!("{-+}"), "-", &id);
- format!("https://getcomposer.org/doc/06-config.md#{}", id)
- };
- if is_string(&raw_val)
- && raw_val
- .as_string()
- .map(|s| s.to_string())
- .unwrap_or_default()
- != value_display
- {
- self.get_io().write3(
- &format!(
- "[<fg=yellow;href={}>{}{}</>] <info>{} ({})</info>{}",
- link,
- k.clone().unwrap_or_default(),
- key,
- raw_val.as_string().unwrap_or(""),
- value_display,
- source
- ),
- true,
- io_interface::QUIET,
- );
- } else {
- self.get_io().write3(
- &format!(
- "[<fg=yellow;href={}>{}{}</>] <info>{}</info>{}",
- link,
- k.clone().unwrap_or_default(),
- key,
- value_display,
- source
- ),
- true,
- io_interface::QUIET,
- );
- }
- }
- }
-
- /// Suggest setting-keys, while taking given options in account.
- fn suggest_setting_keys(&self) -> crate::console::input::SuggestedValues {
- crate::console::input::SuggestedValues::Closure(Box::new(|this, input, _suggestions| {
- if input.get_option("list")?.to_bool()
- || input.get_option("editor")?.to_bool()
- || input.get_option("auth")?.to_bool()
- {
- return Ok(vec![]);
- }
-
- let this = this
- .as_any()
- .downcast_ref::<ConfigCommand>()
- .expect("suggestSettingKeys is bound to ConfigCommand");
- // PHP passes the CompletionInput itself; the accessors only read from it, so a
- // clone behind a fresh handle is equivalent.
- let input_handle: std::rc::Rc<
- std::cell::RefCell<
- dyn shirabe_external_packages::symfony::console::input::InputInterface,
- >,
- > = std::rc::Rc::new(std::cell::RefCell::new(input.clone()));
-
- // initialize configuration
- let mut config = Factory::create_config(None, None)?;
-
- // load configuration
- let config_file = JsonFile::new(
- this.get_composer_config_file(input_handle.clone(), &config)?,
- None,
- None,
- )?;
- if config_file.exists() {
- let path = config_file.get_path().to_string();
- let data = config_file.read()?.as_array().cloned().unwrap_or_default();
- config.merge(&data, &path);
- }
-
- // load auth-configuration
- let auth_config_file = JsonFile::new(
- this.get_auth_config_file(input_handle.clone(), &config)?,
- None,
- None,
- )?;
- if auth_config_file.exists() {
- let path = auth_config_file.get_path().to_string();
- let mut data = IndexMap::new();
- data.insert("config".to_string(), auth_config_file.read()?);
- config.merge(&data, &path);
- }
-
- // collect all configuration setting-keys
- let raw_config = config.raw();
- let mut keys = flatten_setting_keys(
- raw_config.get("config").cloned().unwrap_or(PhpMixed::Null),
- "",
- );
- keys.extend(flatten_setting_keys(
- raw_config
- .get("repositories")
- .cloned()
- .unwrap_or(PhpMixed::Null),
- "repositories.",
- ));
-
- // if unsetting …
- if input.get_option("unset")?.to_bool() {
- // … keep only the currently customized setting-keys …
- let sources = [
- config_file.get_path().to_string(),
- auth_config_file.get_path().to_string(),
- ];
- keys.retain(|key| sources.contains(&config.get_source_of_value(key)));
-
- // … else if showing or setting a value …
- } else {
- // … add all configurable package-properties, no matter if it exist
- keys.extend(
- Self::CONFIGURABLE_PACKAGE_PROPERTIES
- .iter()
- .map(|property| property.to_string()),
- );
-
- // it would be nice to distinguish between showing and setting
- // a value, but that makes the implementation much more complex
- // and partially impossible because symfony's implementation
- // does not complete arguments followed by other arguments
- }
-
- // add all existing configurable package-properties
- if config_file.exists() {
- let properties: IndexMap<String, PhpMixed> = config_file
- .read()?
- .as_array()
- .cloned()
- .unwrap_or_default()
- .into_iter()
- .filter(|(key, _)| {
- Self::CONFIGURABLE_PACKAGE_PROPERTIES.contains(&key.as_str())
- })
- .collect();
-
- keys.extend(flatten_setting_keys(PhpMixed::Array(properties), ""));
- }
-
- // filter settings-keys by completion value
- let completion_value = input.get_completion_value();
-
- if !completion_value.is_empty() {
- keys.retain(|key| key.starts_with(&completion_value));
- }
-
- keys.sort();
-
- keys.dedup();
- Ok(keys)
- }))
- }
-}
-
// PHP signature: function ($val): bool / ($val) -> bool/string
pub type ValidatorFn = Box<dyn Fn(&PhpMixed) -> PhpMixed>;
pub type NormalizerFn = Box<dyn Fn(&PhpMixed) -> PhpMixed>;