From 6051927c8fa32cfffa102d2a170c5a6cf747a1b9 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sun, 9 Aug 2026 11:19:03 +0900 Subject: refactor(symfony-console): extract symfony/console into the shirabe-symfony-console crate Move `Symfony\Component\Console` out of shirabe-external-packages and into its own crate, so the path is `shirabe_symfony_console::application::Application` instead of `shirabe_external_packages::symfony::console::application::Application`. The `delegate_to_inner!` and `delegate_command_trait_impls_to_inner!` macros move with it. Co-Authored-By: Claude Opus 5 (1M context) --- .../console/descriptor/application_description.rs | 189 ------- .../src/symfony/console/descriptor/descriptor.rs | 99 ---- .../console/descriptor/descriptor_interface.rs | 29 - .../symfony/console/descriptor/json_descriptor.rs | 404 -------------- .../console/descriptor/markdown_descriptor.rs | 380 ------------- .../symfony/console/descriptor/text_descriptor.rs | 594 --------------------- .../symfony/console/descriptor/xml_descriptor.rs | 405 -------------- 7 files changed, 2100 deletions(-) delete mode 100644 crates/shirabe-external-packages/src/symfony/console/descriptor/application_description.rs delete mode 100644 crates/shirabe-external-packages/src/symfony/console/descriptor/descriptor.rs delete mode 100644 crates/shirabe-external-packages/src/symfony/console/descriptor/descriptor_interface.rs delete mode 100644 crates/shirabe-external-packages/src/symfony/console/descriptor/json_descriptor.rs delete mode 100644 crates/shirabe-external-packages/src/symfony/console/descriptor/markdown_descriptor.rs delete mode 100644 crates/shirabe-external-packages/src/symfony/console/descriptor/text_descriptor.rs delete mode 100644 crates/shirabe-external-packages/src/symfony/console/descriptor/xml_descriptor.rs (limited to 'crates/shirabe-external-packages/src/symfony/console/descriptor') diff --git a/crates/shirabe-external-packages/src/symfony/console/descriptor/application_description.rs b/crates/shirabe-external-packages/src/symfony/console/descriptor/application_description.rs deleted file mode 100644 index 62fc9933..00000000 --- a/crates/shirabe-external-packages/src/symfony/console/descriptor/application_description.rs +++ /dev/null @@ -1,189 +0,0 @@ -//! ref: composer/vendor/symfony/console/Descriptor/ApplicationDescription.php - -use crate::symfony::console::application::Application; -use crate::symfony::console::command::command::Command; -use crate::symfony::console::exception::command_not_found_exception::CommandNotFoundException; -use indexmap::IndexMap; -use shirabe_php_shim::PhpMixed; - -/// @internal -#[derive(Debug)] -pub struct ApplicationDescription { - application: std::rc::Rc>, - namespace: Option, - show_hidden: bool, - - /// @var array - /// Each namespace entry is `['id' => string, 'commands' => string[]]`. - namespaces: Option>>, - - /// @var array - commands: Option>>>, - - /// @var array - aliases: Option>>>, -} - -impl ApplicationDescription { - pub const GLOBAL_NAMESPACE: &'static str = "_global"; - - pub fn new( - application: std::rc::Rc>, - namespace: Option, - show_hidden: bool, - ) -> Self { - ApplicationDescription { - application, - namespace, - show_hidden, - namespaces: None, - commands: None, - aliases: None, - } - } - - pub fn get_namespaces(&mut self) -> IndexMap> { - if self.namespaces.is_none() { - self.inspect_application(); - } - - self.namespaces.clone().unwrap() - } - - pub fn get_commands( - &mut self, - ) -> &IndexMap>> { - if self.commands.is_none() { - self.inspect_application(); - } - - self.commands.as_ref().unwrap() - } - - /// @throws CommandNotFoundException - pub fn get_command( - &self, - name: &str, - ) -> anyhow::Result>> { - let in_commands = self - .commands - .as_ref() - .map(|c| c.contains_key(name)) - .unwrap_or(false); - let in_aliases = self - .aliases - .as_ref() - .map(|a| a.contains_key(name)) - .unwrap_or(false); - if !in_commands && !in_aliases { - return Err(CommandNotFoundException::new( - format!("Command \"{}\" does not exist.", name), - vec![], - 0, - ) - .into()); - } - - Ok(self - .commands - .as_ref() - .and_then(|c| c.get(name)) - .cloned() - .unwrap_or_else(|| self.aliases.as_ref().unwrap().get(name).unwrap().clone())) - } - - fn inspect_application(&mut self) { - self.commands = Some(IndexMap::new()); - self.namespaces = Some(IndexMap::new()); - - let namespace_filter = match &self.namespace { - Some(ns) if !ns.is_empty() => { - Some(self.application.borrow_mut().find_namespace(ns).unwrap()) - } - _ => None, - }; - let all = self - .application - .borrow_mut() - .all(namespace_filter.as_deref()) - .unwrap(); - for (namespace, commands) in self.sort_commands(all) { - let mut names: Vec = vec![]; - - for (name, command) in commands { - let command_name = command.borrow().get_name(); - let is_hidden = command.borrow().is_hidden(); - if command_name.is_none() - || command_name.as_deref() == Some("") - || (!self.show_hidden && is_hidden) - { - continue; - } - - if command_name.as_deref() == Some(name.as_str()) { - self.commands - .as_mut() - .unwrap() - .insert(name.clone(), command); - } else { - self.aliases - .get_or_insert_with(IndexMap::new) - .insert(name.clone(), command); - } - - names.push(name); - } - - let mut entry: IndexMap = IndexMap::new(); - entry.insert("id".to_string(), PhpMixed::String(namespace.clone())); - entry.insert( - "commands".to_string(), - PhpMixed::List(names.into_iter().map(PhpMixed::String).collect()), - ); - self.namespaces.as_mut().unwrap().insert(namespace, entry); - } - } - - fn sort_commands( - &self, - commands: IndexMap>>, - ) -> IndexMap>>> { - let mut namespaced_commands: IndexMap< - String, - IndexMap>>, - > = IndexMap::new(); - let mut global_commands: IndexMap>> = - IndexMap::new(); - let mut sorted_commands: IndexMap< - String, - IndexMap>>, - > = IndexMap::new(); - for (name, command) in commands { - let key = self.application.borrow().extract_namespace(&name, Some(1)); - if ["", Self::GLOBAL_NAMESPACE].contains(&key.as_str()) { - global_commands.insert(name, command); - } else { - namespaced_commands - .entry(key) - .or_default() - .insert(name, command); - } - } - - if !global_commands.is_empty() { - global_commands.sort_keys(); - sorted_commands.insert(Self::GLOBAL_NAMESPACE.to_string(), global_commands); - } - - if !namespaced_commands.is_empty() { - // ksort($namespacedCommands, \SORT_STRING) - namespaced_commands.sort_keys(); - for (key, mut commands_set) in namespaced_commands { - commands_set.sort_keys(); - sorted_commands.insert(key, commands_set); - } - } - - sorted_commands - } -} diff --git a/crates/shirabe-external-packages/src/symfony/console/descriptor/descriptor.rs b/crates/shirabe-external-packages/src/symfony/console/descriptor/descriptor.rs deleted file mode 100644 index 5055b49b..00000000 --- a/crates/shirabe-external-packages/src/symfony/console/descriptor/descriptor.rs +++ /dev/null @@ -1,99 +0,0 @@ -//! ref: composer/vendor/symfony/console/Descriptor/Descriptor.php - -use crate::symfony::console::application::Application; -use crate::symfony::console::command::command::Command; -use crate::symfony::console::descriptor::descriptor_interface::{ - DescribableObject, DescriptorInterface, -}; -use crate::symfony::console::input::input_argument::InputArgument; -use crate::symfony::console::input::input_definition::InputDefinition; -use crate::symfony::console::input::input_option::InputOption; -use crate::symfony::console::output::output_interface::OutputInterface; -use indexmap::IndexMap; -use shirabe_php_shim::PhpMixed; - -/// @internal -pub trait Descriptor: DescriptorInterface { - fn output(&self) -> std::rc::Rc>; - - fn set_output(&mut self, output: std::rc::Rc>); - - fn describe( - &mut self, - output: std::rc::Rc>, - object: DescribableObject, - options: IndexMap, - ) -> anyhow::Result<()> { - self.set_output(output); - - // PHP dispatches via `$object instanceof ...`; the explicit `DescribableObject` enum makes - // that dispatch a `match`. - match object { - DescribableObject::InputArgument(argument) => { - self.describe_input_argument(&argument, options)?; - } - DescribableObject::InputOption(option) => { - self.describe_input_option(&option, options)?; - } - DescribableObject::InputDefinition(definition) => { - self.describe_input_definition(&definition, options)?; - } - DescribableObject::Command(command) => { - self.describe_command(&*command.borrow(), options)?; - } - DescribableObject::Application(application) => { - self.describe_application(application, options)?; - } - } - - Ok(()) - } - - /// Writes content to output. - fn write(&self, content: &str, decorated: bool) { - self.output().borrow().write( - &[content.to_string()], - false, - if decorated { - crate::symfony::console::output::output_interface::OUTPUT_NORMAL - } else { - crate::symfony::console::output::output_interface::OUTPUT_RAW - }, - ); - } - - /// Describes an InputArgument instance. - fn describe_input_argument( - &mut self, - argument: &InputArgument, - options: IndexMap, - ) -> anyhow::Result<()>; - - /// Describes an InputOption instance. - fn describe_input_option( - &mut self, - option: &InputOption, - options: IndexMap, - ) -> anyhow::Result<()>; - - /// Describes an InputDefinition instance. - fn describe_input_definition( - &mut self, - definition: &InputDefinition, - options: IndexMap, - ) -> anyhow::Result<()>; - - /// Describes a Command instance. - fn describe_command( - &mut self, - command: &dyn Command, - options: IndexMap, - ) -> anyhow::Result<()>; - - /// Describes an Application instance. - fn describe_application( - &mut self, - application: std::rc::Rc>, - options: IndexMap, - ) -> anyhow::Result<()>; -} diff --git a/crates/shirabe-external-packages/src/symfony/console/descriptor/descriptor_interface.rs b/crates/shirabe-external-packages/src/symfony/console/descriptor/descriptor_interface.rs deleted file mode 100644 index be0e9ff7..00000000 --- a/crates/shirabe-external-packages/src/symfony/console/descriptor/descriptor_interface.rs +++ /dev/null @@ -1,29 +0,0 @@ -//! ref: composer/vendor/symfony/console/Descriptor/DescriptorInterface.php - -use crate::symfony::console::application::Application; -use crate::symfony::console::command::command::Command; -use crate::symfony::console::input::input_argument::InputArgument; -use crate::symfony::console::input::input_definition::InputDefinition; -use crate::symfony::console::input::input_option::InputOption; -use crate::symfony::console::output::output_interface::OutputInterface; -use indexmap::IndexMap; -use shirabe_php_shim::PhpMixed; - -/// The set of objects the descriptors know how to describe. -pub enum DescribableObject { - InputArgument(InputArgument), - InputOption(InputOption), - InputDefinition(InputDefinition), - Command(std::rc::Rc>), - Application(std::rc::Rc>), -} - -/// Descriptor interface. -pub trait DescriptorInterface { - fn describe( - &mut self, - output: std::rc::Rc>, - object: DescribableObject, - options: IndexMap, - ) -> anyhow::Result<()>; -} diff --git a/crates/shirabe-external-packages/src/symfony/console/descriptor/json_descriptor.rs b/crates/shirabe-external-packages/src/symfony/console/descriptor/json_descriptor.rs deleted file mode 100644 index 3232e6fa..00000000 --- a/crates/shirabe-external-packages/src/symfony/console/descriptor/json_descriptor.rs +++ /dev/null @@ -1,404 +0,0 @@ -//! ref: composer/vendor/symfony/console/Descriptor/JsonDescriptor.php - -use crate::symfony::console::application::Application; -use crate::symfony::console::command::command::Command; -use crate::symfony::console::descriptor::application_description::ApplicationDescription; -use crate::symfony::console::descriptor::descriptor::Descriptor; -use crate::symfony::console::descriptor::descriptor_interface::{ - DescribableObject, DescriptorInterface, -}; -use crate::symfony::console::input::input_argument::InputArgument; -use crate::symfony::console::input::input_definition::InputDefinition; -use crate::symfony::console::input::input_option::InputOption; -use crate::symfony::console::output::output_interface::OutputInterface; -use indexmap::IndexMap; -use shirabe_pcre::preg::Preg; -use shirabe_php_shim::{PhpMixed, php_regex}; - -/// JSON descriptor. -/// -/// @internal -#[derive(Debug, Default)] -pub struct JsonDescriptor { - output: Option>>, -} - -impl JsonDescriptor { - fn describe_input_argument( - &mut self, - argument: &InputArgument, - options: IndexMap, - ) -> anyhow::Result<()> { - self.write_data(self.get_input_argument_data(argument)?, &options)?; - Ok(()) - } - - fn describe_input_option( - &mut self, - option: &InputOption, - options: IndexMap, - ) -> anyhow::Result<()> { - self.write_data(self.get_input_option_data(option, false)?, &options)?; - if option.is_negatable() { - self.write_data(self.get_input_option_data(option, true)?, &options)?; - } - Ok(()) - } - - fn describe_input_definition( - &mut self, - definition: &InputDefinition, - options: IndexMap, - ) -> anyhow::Result<()> { - self.write_data(self.get_input_definition_data(definition)?, &options)?; - Ok(()) - } - - fn describe_command( - &mut self, - command: &dyn Command, - options: IndexMap, - ) -> anyhow::Result<()> { - let short = matches!(options.get("short"), Some(PhpMixed::Bool(true))); - self.write_data(self.get_command_data(command, short)?, &options)?; - Ok(()) - } - - fn describe_application( - &mut self, - application: std::rc::Rc>, - options: IndexMap, - ) -> anyhow::Result<()> { - let described_namespace = match options.get("namespace") { - Some(PhpMixed::String(s)) => Some(s.clone()), - _ => None, - }; - let mut description = - ApplicationDescription::new(application.clone(), described_namespace.clone(), true); - let mut commands: Vec = vec![]; - - let short = matches!(options.get("short"), Some(PhpMixed::Bool(true))); - for command in description.get_commands().values() { - let command = command.borrow(); - commands.push(PhpMixed::Array( - self.get_command_data(&*command, short)? - .into_iter() - .collect(), - )); - } - - let mut data: IndexMap = IndexMap::new(); - if "UNKNOWN" != application.borrow().get_name() { - let mut application_data: IndexMap = IndexMap::new(); - application_data.insert( - "name".to_string(), - PhpMixed::String(application.borrow().get_name()), - ); - if "UNKNOWN" != application.borrow().get_version() { - application_data.insert( - "version".to_string(), - PhpMixed::String(application.borrow().get_version()), - ); - } - data.insert("application".to_string(), PhpMixed::Array(application_data)); - } - - data.insert("commands".to_string(), PhpMixed::List(commands)); - - if let Some(described_namespace) = described_namespace { - data.insert( - "namespace".to_string(), - PhpMixed::String(described_namespace), - ); - } else { - data.insert( - "namespaces".to_string(), - PhpMixed::List( - description - .get_namespaces() - .into_values() - .map(|ns| PhpMixed::Array(ns.into_iter().collect())) - .collect(), - ), - ); - } - - self.write_data(data, &options)?; - Ok(()) - } - - /// Writes data as json. - fn write_data( - &self, - data: IndexMap, - options: &IndexMap, - ) -> anyhow::Result<()> { - let flags = match options.get("json_encoding") { - Some(PhpMixed::Int(f)) => *f, - _ => 0, - }; - - self.write( - &shirabe_php_shim::json_encode_ex(&PhpMixed::Array(data.into_iter().collect()), flags) - .unwrap_or_default(), - false, - ); - Ok(()) - } - - fn get_input_argument_data( - &self, - argument: &InputArgument, - ) -> anyhow::Result> { - let mut data: IndexMap = IndexMap::new(); - data.insert( - "name".to_string(), - PhpMixed::String(argument.get_name().to_string()), - ); - data.insert( - "is_required".to_string(), - PhpMixed::Bool(argument.is_required()), - ); - data.insert("is_array".to_string(), PhpMixed::Bool(argument.is_array())); - data.insert( - "description".to_string(), - PhpMixed::String(Preg::replace( - php_regex!("/\\s*[\\r\\n]\\s*/"), - " ", - argument.get_description(), - )), - ); - data.insert( - "default".to_string(), - if matches!(argument.get_default(), PhpMixed::Float(f) if f.is_infinite() && *f > 0.0) { - PhpMixed::String("INF".to_string()) - } else { - argument.get_default().clone() - }, - ); - Ok(data) - } - - fn get_input_option_data( - &self, - option: &InputOption, - negated: bool, - ) -> anyhow::Result> { - let mut data: IndexMap = IndexMap::new(); - if negated { - data.insert( - "name".to_string(), - PhpMixed::String(format!("--no-{}", option.get_name())), - ); - data.insert("shortcut".to_string(), PhpMixed::String(String::new())); - data.insert("accept_value".to_string(), PhpMixed::Bool(false)); - data.insert("is_value_required".to_string(), PhpMixed::Bool(false)); - data.insert("is_multiple".to_string(), PhpMixed::Bool(false)); - data.insert( - "description".to_string(), - PhpMixed::String(format!("Negate the \"--{}\" option", option.get_name())), - ); - data.insert("default".to_string(), PhpMixed::Bool(false)); - } else { - data.insert( - "name".to_string(), - PhpMixed::String(format!("--{}", option.get_name())), - ); - data.insert( - "shortcut".to_string(), - PhpMixed::String(if let Some(shortcut) = option.get_shortcut() { - format!("-{}", shirabe_php_shim::str_replace("|", "|-", shortcut)) - } else { - String::new() - }), - ); - data.insert( - "accept_value".to_string(), - PhpMixed::Bool(option.accept_value()), - ); - data.insert( - "is_value_required".to_string(), - PhpMixed::Bool(option.is_value_required()), - ); - data.insert("is_multiple".to_string(), PhpMixed::Bool(option.is_array())); - data.insert( - "description".to_string(), - PhpMixed::String(Preg::replace( - php_regex!("/\\s*[\\r\\n]\\s*/"), - " ", - option.get_description(), - )), - ); - data.insert( - "default".to_string(), - if matches!(option.get_default(), PhpMixed::Float(f) if f.is_infinite() && *f > 0.0) - { - PhpMixed::String("INF".to_string()) - } else { - option.get_default().clone() - }, - ); - } - Ok(data) - } - - fn get_input_definition_data( - &self, - definition: &InputDefinition, - ) -> anyhow::Result> { - let mut input_arguments: IndexMap = IndexMap::new(); - for (name, argument) in definition.get_arguments() { - input_arguments.insert( - name.clone(), - PhpMixed::Array( - self.get_input_argument_data(argument)? - .into_iter() - .collect(), - ), - ); - } - - let mut input_options: IndexMap = IndexMap::new(); - for (name, option) in definition.get_options() { - input_options.insert( - name.clone(), - PhpMixed::Array( - self.get_input_option_data(option, false)? - .into_iter() - .collect(), - ), - ); - if option.is_negatable() { - input_options.insert( - format!("no-{}", name), - PhpMixed::Array( - self.get_input_option_data(option, true)? - .into_iter() - .collect(), - ), - ); - } - } - - let mut data: IndexMap = IndexMap::new(); - data.insert("arguments".to_string(), PhpMixed::Array(input_arguments)); - data.insert("options".to_string(), PhpMixed::Array(input_options)); - Ok(data) - } - - fn get_command_data( - &self, - command: &dyn Command, - short: bool, - ) -> anyhow::Result> { - let mut data: IndexMap = IndexMap::new(); - data.insert( - "name".to_string(), - match command.get_name() { - Some(name) => PhpMixed::String(name), - None => PhpMixed::Null, - }, - ); - data.insert( - "description".to_string(), - PhpMixed::String(command.get_description()), - ); - - if short { - data.insert( - "usage".to_string(), - PhpMixed::List( - command - .get_aliases() - .into_iter() - .map(PhpMixed::String) - .collect(), - ), - ); - } else { - command.merge_application_definition(false); - - let mut usage = vec![PhpMixed::String(command.get_synopsis(false))]; - usage.extend(command.get_usages().into_iter().map(PhpMixed::String)); - usage.extend(command.get_aliases().into_iter().map(PhpMixed::String)); - data.insert("usage".to_string(), PhpMixed::List(usage)); - data.insert( - "help".to_string(), - PhpMixed::String(command.get_processed_help()), - ); - data.insert( - "definition".to_string(), - PhpMixed::Array( - self.get_input_definition_data(&command.get_definition())? - .into_iter() - .collect(), - ), - ); - } - - data.insert("hidden".to_string(), PhpMixed::Bool(command.is_hidden())); - - Ok(data) - } -} - -impl DescriptorInterface for JsonDescriptor { - fn describe( - &mut self, - output: std::rc::Rc>, - object: DescribableObject, - options: IndexMap, - ) -> anyhow::Result<()> { - Descriptor::describe(self, output, object, options) - } -} - -impl Descriptor for JsonDescriptor { - fn output(&self) -> std::rc::Rc> { - self.output.clone().unwrap() - } - - fn set_output(&mut self, output: std::rc::Rc>) { - self.output = Some(output); - } - - fn describe_input_argument( - &mut self, - argument: &InputArgument, - options: IndexMap, - ) -> anyhow::Result<()> { - JsonDescriptor::describe_input_argument(self, argument, options) - } - - fn describe_input_option( - &mut self, - option: &InputOption, - options: IndexMap, - ) -> anyhow::Result<()> { - JsonDescriptor::describe_input_option(self, option, options) - } - - fn describe_input_definition( - &mut self, - definition: &InputDefinition, - options: IndexMap, - ) -> anyhow::Result<()> { - JsonDescriptor::describe_input_definition(self, definition, options) - } - - fn describe_command( - &mut self, - command: &dyn Command, - options: IndexMap, - ) -> anyhow::Result<()> { - JsonDescriptor::describe_command(self, command, options) - } - - fn describe_application( - &mut self, - application: std::rc::Rc>, - options: IndexMap, - ) -> anyhow::Result<()> { - JsonDescriptor::describe_application(self, application, options) - } -} diff --git a/crates/shirabe-external-packages/src/symfony/console/descriptor/markdown_descriptor.rs b/crates/shirabe-external-packages/src/symfony/console/descriptor/markdown_descriptor.rs deleted file mode 100644 index 48514cfe..00000000 --- a/crates/shirabe-external-packages/src/symfony/console/descriptor/markdown_descriptor.rs +++ /dev/null @@ -1,380 +0,0 @@ -//! ref: composer/vendor/symfony/console/Descriptor/MarkdownDescriptor.php - -use crate::symfony::console::application::Application; -use crate::symfony::console::command::command::Command; -use crate::symfony::console::descriptor::application_description::ApplicationDescription; -use crate::symfony::console::descriptor::descriptor::Descriptor; -use crate::symfony::console::descriptor::descriptor_interface::{ - DescribableObject, DescriptorInterface, -}; -use crate::symfony::console::helper::helper::Helper; -use crate::symfony::console::input::input_argument::InputArgument; -use crate::symfony::console::input::input_definition::InputDefinition; -use crate::symfony::console::input::input_option::InputOption; -use crate::symfony::console::output::output_interface::OutputInterface; -use indexmap::IndexMap; -use shirabe_pcre::preg::Preg; -use shirabe_php_shim::PhpMixed; - -/// Markdown descriptor. -/// -/// @internal -#[derive(Debug, Default)] -pub struct MarkdownDescriptor { - output: Option>>, -} - -impl MarkdownDescriptor { - pub fn describe( - &mut self, - output: std::rc::Rc>, - object: DescribableObject, - options: IndexMap, - ) -> anyhow::Result<()> { - let decorated = output.borrow().is_decorated(); - output.borrow().set_decorated(false); - - Descriptor::describe(self, output.clone(), object, options)?; - - output.borrow().set_decorated(decorated); - Ok(()) - } - - fn describe_input_argument( - &mut self, - argument: &InputArgument, - _options: IndexMap, - ) -> anyhow::Result<()> { - let name = if !argument.get_name().is_empty() { - argument.get_name().to_string() - } else { - "".to_string() - }; - self.write( - &format!( - "#### `{}`\n\n{}* Is required: {}\n* Is array: {}\n* Default: `{}`", - name, - if !argument.get_description().is_empty() { - format!( - "{}\n\n", - Preg::replace("/\\s*[\\r\\n]\\s*/", "\n", argument.get_description()) - ) - } else { - String::new() - }, - if argument.is_required() { "yes" } else { "no" }, - if argument.is_array() { "yes" } else { "no" }, - shirabe_php_shim::str_replace( - "\n", - "", - &shirabe_php_shim::var_export(argument.get_default(), true), - ), - ), - true, - ); - Ok(()) - } - - fn describe_input_option( - &mut self, - option: &InputOption, - _options: IndexMap, - ) -> anyhow::Result<()> { - let mut name = format!("--{}", option.get_name()); - if option.is_negatable() { - name += &format!("|--no-{}", option.get_name()); - } - if let Some(shortcut) = option.get_shortcut() { - name += &format!("|-{}", shirabe_php_shim::str_replace("|", "|-", shortcut)); - } - - self.write( - &format!( - "#### `{}`\n\n{}* Accept value: {}\n* Is value required: {}\n* Is multiple: {}\n* Is negatable: {}\n* Default: `{}`", - name, - if !option.get_description().is_empty() { - format!( - "{}\n\n", - Preg::replace("/\\s*[\\r\\n]\\s*/", "\n", option.get_description()) - ) - } else { - String::new() - }, - if option.accept_value() { "yes" } else { "no" }, - if option.is_value_required() { "yes" } else { "no" }, - if option.is_array() { "yes" } else { "no" }, - if option.is_negatable() { "yes" } else { "no" }, - shirabe_php_shim::str_replace( - "\n", - "", - &shirabe_php_shim::var_export(option.get_default(), true), - ), - ), - true, - ); - Ok(()) - } - - fn describe_input_definition( - &mut self, - definition: &InputDefinition, - _options: IndexMap, - ) -> anyhow::Result<()> { - let show_arguments = !definition.get_arguments().is_empty(); - if show_arguments { - self.write("### Arguments", true); - for argument in definition.get_arguments().values() { - self.write("\n\n", true); - // describeInputArgument returns null; the guarded write never runs. - self.describe_input_argument(argument, IndexMap::new())?; - } - } - - if !definition.get_options().is_empty() { - if show_arguments { - self.write("\n\n", true); - } - - self.write("### Options", true); - for option in definition.get_options().values() { - self.write("\n\n", true); - // describeInputOption returns null; the guarded write never runs. - self.describe_input_option(option, IndexMap::new())?; - } - } - Ok(()) - } - - fn describe_command( - &mut self, - command: &dyn Command, - options: IndexMap, - ) -> anyhow::Result<()> { - if matches!(options.get("short"), Some(PhpMixed::Bool(true))) { - self.write( - &format!( - "`{}`\n{}\n\n{}### Usage\n\n{}", - command.get_name().unwrap_or_default(), - shirabe_php_shim::str_repeat( - "-", - (Helper::width(command.get_name().as_deref().unwrap_or("")) + 2) as usize - ), - if !command.get_description().is_empty() { - format!("{}\n\n", command.get_description()) - } else { - String::new() - }, - command - .get_aliases() - .iter() - .fold(String::new(), |carry, usage| { - format!("{}* `{}`\n", carry, usage) - }), - ), - true, - ); - - return Ok(()); - } - - command.merge_application_definition(false); - - let mut usages = vec![command.get_synopsis(false)]; - usages.extend(command.get_aliases()); - usages.extend(command.get_usages()); - self.write( - &format!( - "`{}`\n{}\n\n{}### Usage\n\n{}", - command.get_name().unwrap_or_default(), - shirabe_php_shim::str_repeat( - "-", - (Helper::width(command.get_name().as_deref().unwrap_or("")) + 2) as usize - ), - if !command.get_description().is_empty() { - format!("{}\n\n", command.get_description()) - } else { - String::new() - }, - usages.iter().fold(String::new(), |carry, usage| { - format!("{}* `{}`\n", carry, usage) - }), - ), - true, - ); - - let help = command.get_processed_help(); - if !help.is_empty() { - self.write("\n", true); - self.write(&help, true); - } - - let definition = command.get_definition().clone(); - if !definition.get_options().is_empty() || !definition.get_arguments().is_empty() { - self.write("\n\n", true); - self.describe_input_definition(&definition, IndexMap::new())?; - } - Ok(()) - } - - fn describe_application( - &mut self, - application: std::rc::Rc>, - options: IndexMap, - ) -> anyhow::Result<()> { - let described_namespace = match options.get("namespace") { - Some(PhpMixed::String(s)) => Some(s.clone()), - _ => None, - }; - let mut description = - ApplicationDescription::new(application.clone(), described_namespace, false); - let title = self.get_application_title(&*application.borrow()); - - self.write( - &format!( - "{}\n{}", - title, - shirabe_php_shim::str_repeat("=", Helper::width(&title) as usize) - ), - true, - ); - - for namespace in description.get_namespaces().values() { - let namespace_id = match namespace.get("id") { - Some(PhpMixed::String(s)) => s.clone(), - _ => String::new(), - }; - if ApplicationDescription::GLOBAL_NAMESPACE != namespace_id { - self.write("\n\n", true); - self.write(&format!("**{}:**", namespace_id), true); - } - - self.write("\n\n", true); - let command_names: Vec = match namespace.get("commands") { - Some(PhpMixed::List(names)) => names - .iter() - .filter_map(|n| n.as_string().map(|s| s.to_string())) - .collect(), - _ => vec![], - }; - self.write( - &command_names - .iter() - .map(|command_name| { - Ok(format!( - "* [`{}`](#{})", - command_name.clone(), - shirabe_php_shim::str_replace( - ":", - "", - &description - .get_command(command_name)? - .borrow() - .get_name() - .unwrap_or_default(), - ), - )) - }) - .collect::>>()? - .join("\n"), - true, - ); - } - - for command in description.get_commands().values() { - let command = command.borrow(); - self.write("\n\n", true); - // describeCommand returns null; the guarded write never runs. - self.describe_command(&*command, options.clone())?; - } - Ok(()) - } - - fn get_application_title(&self, application: &dyn Application) -> String { - if "UNKNOWN" != application.get_name() { - if "UNKNOWN" != application.get_version() { - return format!("{} {}", application.get_name(), application.get_version()); - } - - return application.get_name(); - } - - "Console Tool".to_string() - } -} - -impl DescriptorInterface for MarkdownDescriptor { - fn describe( - &mut self, - output: std::rc::Rc>, - object: DescribableObject, - options: IndexMap, - ) -> anyhow::Result<()> { - MarkdownDescriptor::describe(self, output, object, options) - } -} - -impl Descriptor for MarkdownDescriptor { - fn output(&self) -> std::rc::Rc> { - self.output.clone().unwrap() - } - - fn set_output(&mut self, output: std::rc::Rc>) { - self.output = Some(output); - } - - /// {@inheritdoc} - fn write(&self, content: &str, decorated: bool) { - // PHP overrides write() only to flip the default of $decorated to true; - // it still delegates to parent::write. - let _ = decorated; - self.output().borrow().write( - &[content.to_string()], - false, - if decorated { - crate::symfony::console::output::output_interface::OUTPUT_NORMAL - } else { - crate::symfony::console::output::output_interface::OUTPUT_RAW - }, - ); - } - - fn describe_input_argument( - &mut self, - argument: &InputArgument, - options: IndexMap, - ) -> anyhow::Result<()> { - MarkdownDescriptor::describe_input_argument(self, argument, options) - } - - fn describe_input_option( - &mut self, - option: &InputOption, - options: IndexMap, - ) -> anyhow::Result<()> { - MarkdownDescriptor::describe_input_option(self, option, options) - } - - fn describe_input_definition( - &mut self, - definition: &InputDefinition, - options: IndexMap, - ) -> anyhow::Result<()> { - MarkdownDescriptor::describe_input_definition(self, definition, options) - } - - fn describe_command( - &mut self, - command: &dyn Command, - options: IndexMap, - ) -> anyhow::Result<()> { - MarkdownDescriptor::describe_command(self, command, options) - } - - fn describe_application( - &mut self, - application: std::rc::Rc>, - options: IndexMap, - ) -> anyhow::Result<()> { - MarkdownDescriptor::describe_application(self, application, options) - } -} diff --git a/crates/shirabe-external-packages/src/symfony/console/descriptor/text_descriptor.rs b/crates/shirabe-external-packages/src/symfony/console/descriptor/text_descriptor.rs deleted file mode 100644 index 147fe73f..00000000 --- a/crates/shirabe-external-packages/src/symfony/console/descriptor/text_descriptor.rs +++ /dev/null @@ -1,594 +0,0 @@ -//! ref: composer/vendor/symfony/console/Descriptor/TextDescriptor.php - -use crate::symfony::console::application::Application; -use crate::symfony::console::command::command::Command; -use crate::symfony::console::descriptor::application_description::ApplicationDescription; -use crate::symfony::console::descriptor::descriptor::Descriptor; -use crate::symfony::console::descriptor::descriptor_interface::{ - DescribableObject, DescriptorInterface, -}; -use crate::symfony::console::formatter::output_formatter::OutputFormatter; -use crate::symfony::console::helper::helper::Helper; -use crate::symfony::console::input::input_argument::InputArgument; -use crate::symfony::console::input::input_definition::InputDefinition; -use crate::symfony::console::input::input_option::InputOption; -use crate::symfony::console::output::output_interface::OutputInterface; -use indexmap::IndexMap; -use shirabe_pcre::preg::Preg; -use shirabe_php_shim::PhpMixed; - -/// Text descriptor. -/// -/// @internal -#[derive(Debug, Default)] -pub struct TextDescriptor { - output: Option>>, -} - -/// Models PHP's `array` passed to `getColumnWidth`. -/// `PhpMixed` cannot hold console types, so a dedicated enum is used. -#[derive(Debug)] -enum CommandOrString { - Command(std::rc::Rc>), - String(String), -} - -impl TextDescriptor { - fn describe_input_argument( - &mut self, - argument: &InputArgument, - options: IndexMap, - ) -> anyhow::Result<()> { - let default = if !argument.get_default().is_null() - && (!matches!( - argument.get_default(), - PhpMixed::List(_) | PhpMixed::Array(_) - ) || shirabe_php_shim::count(argument.get_default()) != 0) - { - format!( - " [default: {}]", - self.format_default_value(argument.get_default())? - ) - } else { - String::new() - }; - - let total_width = match options.get("total_width") { - Some(PhpMixed::Int(w)) => *w, - _ => Helper::width(argument.get_name()), - }; - let spacing_width = total_width - shirabe_php_shim::strlen(argument.get_name()); - - self.write_text( - &format!( - " {} {}{}{}", - argument.get_name(), - shirabe_php_shim::str_repeat(" ", spacing_width as usize), - // + 4 = 2 spaces before , 2 spaces after - Preg::replace( - "/\\s*[\\r\\n]\\s*/", - &format!( - "\n{}", - shirabe_php_shim::str_repeat(" ", (total_width + 4) as usize) - ), - argument.get_description(), - ), - default, - ), - &options, - ); - Ok(()) - } - - fn describe_input_option( - &mut self, - option: &InputOption, - options: IndexMap, - ) -> anyhow::Result<()> { - let default = if option.accept_value() - && !option.get_default().is_null() - && (!matches!(option.get_default(), PhpMixed::List(_) | PhpMixed::Array(_)) - || shirabe_php_shim::count(option.get_default()) != 0) - { - format!( - " [default: {}]", - self.format_default_value(option.get_default())? - ) - } else { - String::new() - }; - - let mut value = String::new(); - if option.accept_value() { - value = format!("={}", shirabe_php_shim::strtoupper(option.get_name())); - - if option.is_value_optional() { - value = format!("[{}]", value); - } - } - - let total_width = match options.get("total_width") { - Some(PhpMixed::Int(w)) => *w, - _ => self.calculate_total_width_for_options(&[option]), - }; - let synopsis = format!( - "{}{}", - if option.get_shortcut().is_some() { - format!("-{}, ", option.get_shortcut().unwrap()) - } else { - " ".to_string() - }, - if option.is_negatable() { - format!("--{0}|--no-{0}", option.get_name().to_string()) - } else { - format!("--{0}{1}", option.get_name(), value) - } - ); - - let spacing_width = total_width - Helper::width(&synopsis); - - self.write_text( - &format!( - " {} {}{}{}{}", - synopsis, - shirabe_php_shim::str_repeat(" ", spacing_width as usize), - // + 4 = 2 spaces before , 2 spaces after - Preg::replace( - "/\\s*[\\r\\n]\\s*/", - &format!( - "\n{}", - shirabe_php_shim::str_repeat(" ", (total_width + 4) as usize) - ), - option.get_description(), - ), - default, - if option.is_array() { - " (multiple values allowed)".to_string() - } else { - String::new() - }, - ), - &options, - ); - Ok(()) - } - - fn describe_input_definition( - &mut self, - definition: &InputDefinition, - options: IndexMap, - ) -> anyhow::Result<()> { - let mut total_width = self.calculate_total_width_for_options( - &definition - .get_options() - .values() - .map(|o| o.as_ref()) - .collect::>(), - ); - for argument in definition.get_arguments().values() { - total_width = std::cmp::max(total_width, Helper::width(argument.get_name())); - } - - if !definition.get_arguments().is_empty() { - self.write_text("Arguments:", &options); - self.write_text("\n", &IndexMap::new()); - for argument in definition.get_arguments().values() { - let mut merged = options.clone(); - merged.insert("total_width".to_string(), PhpMixed::Int(total_width)); - self.describe_input_argument(argument, merged)?; - self.write_text("\n", &IndexMap::new()); - } - } - - if !definition.get_arguments().is_empty() && !definition.get_options().is_empty() { - self.write_text("\n", &IndexMap::new()); - } - - if !definition.get_options().is_empty() { - let mut later_options: Vec<&InputOption> = vec![]; - - self.write_text("Options:", &options); - for option in definition.get_options().values() { - if shirabe_php_shim::strlen(option.get_shortcut().unwrap_or("")) > 1 { - later_options.push(option.as_ref()); - continue; - } - self.write_text("\n", &IndexMap::new()); - let mut merged = options.clone(); - merged.insert("total_width".to_string(), PhpMixed::Int(total_width)); - self.describe_input_option(option, merged)?; - } - for option in later_options { - self.write_text("\n", &IndexMap::new()); - let mut merged = options.clone(); - merged.insert("total_width".to_string(), PhpMixed::Int(total_width)); - self.describe_input_option(option, merged)?; - } - } - Ok(()) - } - - fn describe_command( - &mut self, - command: &dyn Command, - options: IndexMap, - ) -> anyhow::Result<()> { - command.merge_application_definition(false); - - let description = command.get_description(); - if !description.is_empty() { - self.write_text("Description:", &options); - self.write_text("\n", &IndexMap::new()); - self.write_text(&format!(" {}", description), &IndexMap::new()); - self.write_text("\n\n", &IndexMap::new()); - } - - self.write_text("Usage:", &options); - let mut usages = vec![command.get_synopsis(true)]; - usages.extend(command.get_aliases()); - usages.extend(command.get_usages()); - for usage in usages { - self.write_text("\n", &IndexMap::new()); - self.write_text(&format!(" {}", OutputFormatter::escape(&usage)?), &options); - } - self.write_text("\n", &IndexMap::new()); - - let definition = command.get_definition().clone(); - if !definition.get_options().is_empty() || !definition.get_arguments().is_empty() { - self.write_text("\n", &IndexMap::new()); - self.describe_input_definition(&definition, options.clone())?; - self.write_text("\n", &IndexMap::new()); - } - - let help = command.get_processed_help(); - if !help.is_empty() && help != description { - self.write_text("\n", &IndexMap::new()); - self.write_text("Help:", &options); - self.write_text("\n", &IndexMap::new()); - self.write_text( - &format!(" {}", shirabe_php_shim::str_replace("\n", "\n ", &help)), - &options, - ); - self.write_text("\n", &IndexMap::new()); - } - Ok(()) - } - - fn describe_application( - &mut self, - application: std::rc::Rc>, - options: IndexMap, - ) -> anyhow::Result<()> { - let described_namespace = match options.get("namespace") { - Some(PhpMixed::String(s)) => Some(s.clone()), - _ => None, - }; - let mut description = - ApplicationDescription::new(application.clone(), described_namespace.clone(), false); - - if matches!(options.get("raw_text"), Some(v) if shirabe_php_shim::php_truthy(v)) { - let width = self.get_column_width( - &description - .get_commands() - .values() - .map(|c| CommandOrString::Command(c.clone())) - .collect::>(), - ); - - let command_list: Vec<_> = description.get_commands().values().cloned().collect(); - for command in &command_list { - let command = command.borrow(); - self.write_text( - &format!( - "{:Usage:\n", &options); - self.write_text(" command [options] [arguments]\n\n", &options); - - let app_definition = application.borrow_mut().get_definition(); - let options_only: Vec> = app_definition - .borrow() - .get_options() - .values() - .cloned() - .collect(); - let definition = InputDefinition::from_options(options_only)?; - self.describe_input_definition(&definition, options.clone())?; - - self.write_text("\n", &IndexMap::new()); - self.write_text("\n", &IndexMap::new()); - - let mut commands = description.get_commands().clone(); - let namespaces = description.get_namespaces(); - if described_namespace.is_some() && !namespaces.is_empty() { - // make sure all alias commands are included when describing a specific namespace - let described_namespace_info = namespaces.values().next().unwrap(); - if let Some(PhpMixed::List(names)) = described_namespace_info.get("commands") { - let names: Vec = names - .iter() - .filter_map(|n| n.as_string().map(|s| s.to_string())) - .collect(); - for name in names { - let command = description.get_command(&name)?; - commands.insert(name, command); - } - } - } - - // calculate max. width based on available commands per namespace - let width = self.get_column_width(&{ - let command_keys: Vec = commands.keys().cloned().collect(); - let mut merged: Vec = vec![]; - for namespace in namespaces.values() { - if let Some(PhpMixed::List(ns_commands)) = namespace.get("commands") { - for c in ns_commands { - if let PhpMixed::String(name) = c - && command_keys.contains(name) - { - merged.push(CommandOrString::String(name.clone())); - } - } - } - } - merged - }); - - if let Some(ref described_namespace) = described_namespace { - self.write_text( - &format!( - "Available commands for the \"{}\" namespace:", - described_namespace.clone(), - ), - &options, - ); - } else { - self.write_text("Available commands:", &options); - } - - for namespace in namespaces.values() { - let ns_commands: Vec = match namespace.get("commands") { - Some(PhpMixed::List(names)) => names - .iter() - .filter_map(|n| match n { - PhpMixed::String(name) if commands.contains_key(name) => { - Some(name.clone()) - } - _ => None, - }) - .collect(), - _ => vec![], - }; - - if ns_commands.is_empty() { - continue; - } - - let namespace_id = match namespace.get("id") { - Some(PhpMixed::String(s)) => s.clone(), - _ => String::new(), - }; - - if described_namespace.is_none() - && ApplicationDescription::GLOBAL_NAMESPACE != namespace_id - { - self.write_text("\n", &IndexMap::new()); - self.write_text(&format!(" {}", namespace_id), &options); - } - - for name in ns_commands { - self.write_text("\n", &IndexMap::new()); - let spacing_width = width - Helper::width(&name); - let command = commands.get(&name).unwrap().clone(); - let command = command.borrow(); - let command_aliases = if command.get_name().as_deref() == Some(name.as_str()) { - self.get_command_aliases_text(&*command) - } else { - String::new() - }; - self.write_text( - &format!( - " {}{}{}{}", - name.clone(), - shirabe_php_shim::str_repeat(" ", spacing_width as usize), - command_aliases, - command.get_description(), - ), - &options, - ); - } - } - - self.write_text("\n", &IndexMap::new()); - } - Ok(()) - } - - fn write_text(&self, content: &str, options: &IndexMap) { - let raw_text = - matches!(options.get("raw_text"), Some(v) if shirabe_php_shim::php_truthy(v)); - let content = if raw_text { - shirabe_php_shim::strip_tags(content) - } else { - content.to_string() - }; - let decorated = match options.get("raw_output") { - Some(v) => !shirabe_php_shim::php_truthy(v), - None => true, - }; - self.write(&content, decorated); - } - - /// Formats command aliases to show them in the command description. - fn get_command_aliases_text(&self, command: &dyn Command) -> String { - let mut text = String::new(); - let aliases = command.get_aliases(); - - if !aliases.is_empty() { - text = format!("[{}] ", aliases.join("|")); - } - - text - } - - /// Formats input option/argument default value. - fn format_default_value(&self, default: &PhpMixed) -> anyhow::Result { - if matches!(default, PhpMixed::Float(f) if f.is_infinite() && *f > 0.0) { - return Ok("INF".to_string()); - } - - let default = match default { - PhpMixed::String(s) => PhpMixed::String(OutputFormatter::escape(s)?), - PhpMixed::Array(arr) => { - let mut arr = arr.clone(); - for (_key, value) in arr.iter_mut() { - if let PhpMixed::String(s) = &*value { - *value = PhpMixed::String(OutputFormatter::escape(s)?); - } - } - PhpMixed::Array(arr) - } - PhpMixed::List(list) => { - let mut list = list.clone(); - for value in list.iter_mut() { - if let PhpMixed::String(s) = &*value { - *value = PhpMixed::String(OutputFormatter::escape(s)?); - } - } - PhpMixed::List(list) - } - other => other.clone(), - }; - - Ok(shirabe_php_shim::str_replace( - "\\\\", - "\\", - &shirabe_php_shim::json_encode_ex( - &default, - shirabe_php_shim::JSON_UNESCAPED_SLASHES | shirabe_php_shim::JSON_UNESCAPED_UNICODE, - ) - .unwrap_or_default(), - )) - } - - fn get_column_width(&self, commands: &[CommandOrString]) -> i64 { - let mut widths: Vec = vec![]; - - for command in commands { - // case $command instanceof Command - match command { - CommandOrString::Command(command) => { - let command = command.borrow(); - widths.push(Helper::width(command.get_name().as_deref().unwrap_or(""))); - for alias in command.get_aliases() { - widths.push(Helper::width(&alias)); - } - } - CommandOrString::String(s) => { - widths.push(Helper::width(s)); - } - } - } - - if !widths.is_empty() { - widths.into_iter().max().unwrap() + 2 - } else { - 0 - } - } - - fn calculate_total_width_for_options(&self, options: &[&InputOption]) -> i64 { - let mut total_width: i64 = 0; - for option in options { - // "-" + shortcut + ", --" + name - let mut name_length = 1 - + Helper::width(option.get_shortcut().unwrap_or("")).max(1) - + 4 - + Helper::width(option.get_name()); - if option.is_negatable() { - name_length += 6 + Helper::width(option.get_name()); // |--no- + name - } else if option.accept_value() { - let mut value_length = 1 + Helper::width(option.get_name()); // = + value - value_length += if option.is_value_optional() { 2 } else { 0 }; // [ + ] - - name_length += value_length; - } - total_width = std::cmp::max(total_width, name_length); - } - - total_width - } -} - -impl DescriptorInterface for TextDescriptor { - fn describe( - &mut self, - output: std::rc::Rc>, - object: DescribableObject, - options: IndexMap, - ) -> anyhow::Result<()> { - Descriptor::describe(self, output, object, options) - } -} - -impl Descriptor for TextDescriptor { - fn output(&self) -> std::rc::Rc> { - self.output.clone().unwrap() - } - - fn set_output(&mut self, output: std::rc::Rc>) { - self.output = Some(output); - } - - fn describe_input_argument( - &mut self, - argument: &InputArgument, - options: IndexMap, - ) -> anyhow::Result<()> { - TextDescriptor::describe_input_argument(self, argument, options) - } - - fn describe_input_option( - &mut self, - option: &InputOption, - options: IndexMap, - ) -> anyhow::Result<()> { - TextDescriptor::describe_input_option(self, option, options) - } - - fn describe_input_definition( - &mut self, - definition: &InputDefinition, - options: IndexMap, - ) -> anyhow::Result<()> { - TextDescriptor::describe_input_definition(self, definition, options) - } - - fn describe_command( - &mut self, - command: &dyn Command, - options: IndexMap, - ) -> anyhow::Result<()> { - TextDescriptor::describe_command(self, command, options) - } - - fn describe_application( - &mut self, - application: std::rc::Rc>, - options: IndexMap, - ) -> anyhow::Result<()> { - TextDescriptor::describe_application(self, application, options) - } -} diff --git a/crates/shirabe-external-packages/src/symfony/console/descriptor/xml_descriptor.rs b/crates/shirabe-external-packages/src/symfony/console/descriptor/xml_descriptor.rs deleted file mode 100644 index c2770a43..00000000 --- a/crates/shirabe-external-packages/src/symfony/console/descriptor/xml_descriptor.rs +++ /dev/null @@ -1,405 +0,0 @@ -//! ref: composer/vendor/symfony/console/Descriptor/XmlDescriptor.php - -use crate::symfony::console::application::Application; -use crate::symfony::console::command::command::Command; -use crate::symfony::console::descriptor::application_description::ApplicationDescription; -use crate::symfony::console::descriptor::descriptor::Descriptor; -use crate::symfony::console::descriptor::descriptor_interface::{ - DescribableObject, DescriptorInterface, -}; -use crate::symfony::console::input::input_argument::InputArgument; -use crate::symfony::console::input::input_definition::InputDefinition; -use crate::symfony::console::input::input_option::InputOption; -use crate::symfony::console::output::output_interface::OutputInterface; -use indexmap::IndexMap; -use shirabe_php_shim::{DOMDocument, DOMNode, PhpMixed}; - -/// XML descriptor. -/// -/// @internal -#[derive(Debug, Default)] -pub struct XmlDescriptor { - output: Option>>, -} - -impl XmlDescriptor { - pub fn get_input_definition_document(&self, definition: &InputDefinition) -> DOMDocument { - let dom = DOMDocument::new("1.0", "UTF-8"); - let definition_xml = dom.append_child(dom.create_element("definition")); - - let arguments_xml = definition_xml.append_child(dom.create_element("arguments")); - for argument in definition.get_arguments().values() { - let argument_xml = self.get_input_argument_document(argument); - self.append_document(&arguments_xml, &argument_xml.as_node()); - } - - let options_xml = definition_xml.append_child(dom.create_element("options")); - for option in definition.get_options().values() { - let option_xml = self.get_input_option_document(option); - self.append_document(&options_xml, &option_xml.as_node()); - } - - dom - } - - pub fn get_command_document(&self, command: &dyn Command, short: bool) -> DOMDocument { - let dom = DOMDocument::new("1.0", "UTF-8"); - let command_xml = dom.append_child(dom.create_element("command")); - - let name = command.get_name().unwrap_or_default(); - command_xml.set_attribute("id", &name); - command_xml.set_attribute("name", &name); - command_xml.set_attribute("hidden", if command.is_hidden() { "1" } else { "0" }); - - let usages_xml = command_xml.append_child(dom.create_element("usages")); - - let description_xml = command_xml.append_child(dom.create_element("description")); - description_xml.append_child(dom.create_text_node(&shirabe_php_shim::str_replace( - "\n", - "\n ", - &command.get_description(), - ))); - - if short { - for usage in command.get_aliases() { - usages_xml.append_child(dom.create_element_with_value("usage", &usage)); - } - } else { - command.merge_application_definition(false); - - let mut usages = vec![command.get_synopsis(false)]; - usages.extend(command.get_aliases()); - usages.extend(command.get_usages()); - for usage in usages { - usages_xml.append_child(dom.create_element_with_value("usage", &usage)); - } - - let help_xml = command_xml.append_child(dom.create_element("help")); - help_xml.append_child(dom.create_text_node(&shirabe_php_shim::str_replace( - "\n", - "\n ", - &command.get_processed_help(), - ))); - - let command_definition = command.get_definition().clone(); - let definition_xml = self.get_input_definition_document(&command_definition); - let definition_node = definition_xml - .get_elements_by_tag_name("definition") - .item(0) - .expect("input definition document always contains a element"); - self.append_document(&command_xml, &definition_node); - } - - dom - } - - pub fn get_application_document( - &self, - application: std::rc::Rc>, - namespace: Option, - short: bool, - ) -> DOMDocument { - let dom = DOMDocument::new("1.0", "UTF-8"); - let root_xml = dom.append_child(dom.create_element("symfony")); - - let app_name = application.borrow().get_name(); - if app_name != "UNKNOWN" { - root_xml.set_attribute("name", &app_name); - let app_version = application.borrow().get_version(); - if app_version != "UNKNOWN" { - root_xml.set_attribute("version", &app_version); - } - } - - let commands_xml = root_xml.append_child(dom.create_element("commands")); - - let mut description = - ApplicationDescription::new(application.clone(), namespace.clone(), true); - - if let Some(ref namespace) = namespace { - commands_xml.set_attribute("namespace", namespace); - } - - for command in description.get_commands().values() { - let command = command.borrow(); - let command_xml = self.get_command_document(&*command, short); - self.append_document(&commands_xml, &command_xml.as_node()); - } - - if namespace.is_none() { - let namespaces_xml = root_xml.append_child(dom.create_element("namespaces")); - - let namespaces = description.get_namespaces(); - for namespace_description in namespaces.values() { - let namespace_array_xml = - namespaces_xml.append_child(dom.create_element("namespace")); - let id = match namespace_description.get("id") { - Some(PhpMixed::String(s)) => s.as_str(), - _ => "", - }; - namespace_array_xml.set_attribute("id", id); - - if let Some(PhpMixed::List(names)) = namespace_description.get("commands") { - for name in names { - let command_xml = - namespace_array_xml.append_child(dom.create_element("command")); - command_xml - .append_child(dom.create_text_node(name.as_string().unwrap_or(""))); - } - } - } - } - - dom - } - - fn describe_input_argument( - &mut self, - argument: &InputArgument, - _options: IndexMap, - ) -> anyhow::Result<()> { - self.write_document(self.get_input_argument_document(argument)); - Ok(()) - } - - fn describe_input_option( - &mut self, - option: &InputOption, - _options: IndexMap, - ) -> anyhow::Result<()> { - self.write_document(self.get_input_option_document(option)); - Ok(()) - } - - fn describe_input_definition( - &mut self, - definition: &InputDefinition, - _options: IndexMap, - ) -> anyhow::Result<()> { - self.write_document(self.get_input_definition_document(definition)); - Ok(()) - } - - fn describe_command( - &mut self, - command: &dyn Command, - options: IndexMap, - ) -> anyhow::Result<()> { - let short = matches!(options.get("short"), Some(PhpMixed::Bool(true))); - self.write_document(self.get_command_document(command, short)); - Ok(()) - } - - fn describe_application( - &mut self, - application: std::rc::Rc>, - options: IndexMap, - ) -> anyhow::Result<()> { - let namespace = match options.get("namespace") { - Some(PhpMixed::String(s)) => Some(s.clone()), - _ => None, - }; - let short = matches!(options.get("short"), Some(PhpMixed::Bool(true))); - self.write_document(self.get_application_document(application, namespace, short)); - Ok(()) - } - - /// Appends document children to parent node. - fn append_document(&self, parent_node: &DOMNode, imported_parent: &DOMNode) { - for child_node in imported_parent.child_nodes() { - parent_node.append_child(parent_node.owner_document().import_node(&child_node, true)); - } - } - - /// Writes DOM document. - fn write_document(&self, dom: DOMDocument) { - dom.set_format_output(true); - let mut buf = Vec::new(); - dom.save_xml(&mut buf) - .expect("serializing XML to an in-memory buffer cannot fail"); - let xml = String::from_utf8(buf).expect("DOM serialization yields valid UTF-8"); - self.write(&xml, false); - } - - fn get_input_argument_document(&self, argument: &InputArgument) -> DOMDocument { - let dom = DOMDocument::new("1.0", "UTF-8"); - - let object_xml = dom.append_child(dom.create_element("argument")); - object_xml.set_attribute("name", argument.get_name()); - object_xml.set_attribute( - "is_required", - if argument.is_required() { "1" } else { "0" }, - ); - object_xml.set_attribute("is_array", if argument.is_array() { "1" } else { "0" }); - let description_xml = object_xml.append_child(dom.create_element("description")); - description_xml.append_child(dom.create_text_node(argument.get_description())); - - let defaults_xml = object_xml.append_child(dom.create_element("defaults")); - let defaults: Vec = match argument.get_default() { - PhpMixed::List(_) | PhpMixed::Array(_) => { - self.default_values_as_strings(argument.get_default()) - } - PhpMixed::Bool(_) => vec![shirabe_php_shim::var_export(argument.get_default(), true)], - d if shirabe_php_shim::php_truthy(d) => { - vec![shirabe_php_shim::php_to_string(argument.get_default())] - } - _ => vec![], - }; - for default in defaults { - let default_xml = defaults_xml.append_child(dom.create_element("default")); - default_xml.append_child(dom.create_text_node(&default)); - } - - dom - } - - fn get_input_option_document(&self, option: &InputOption) -> DOMDocument { - let dom = DOMDocument::new("1.0", "UTF-8"); - - let object_xml = dom.append_child(dom.create_element("option")); - object_xml.set_attribute("name", &format!("--{}", option.get_name())); - let pos = shirabe_php_shim::strpos(option.get_shortcut().unwrap_or(""), "|"); - if let Some(pos) = pos { - object_xml.set_attribute( - "shortcut", - &format!( - "-{}", - shirabe_php_shim::substr(option.get_shortcut().unwrap(), 0, Some(pos as i64)) - ), - ); - object_xml.set_attribute( - "shortcuts", - &format!( - "-{}", - shirabe_php_shim::str_replace("|", "|-", option.get_shortcut().unwrap()) - ), - ); - } else { - object_xml.set_attribute( - "shortcut", - &match option.get_shortcut() { - Some(s) => format!("-{}", s), - None => String::new(), - }, - ); - } - object_xml.set_attribute( - "accept_value", - if option.accept_value() { "1" } else { "0" }, - ); - object_xml.set_attribute( - "is_value_required", - if option.is_value_required() { "1" } else { "0" }, - ); - object_xml.set_attribute("is_multiple", if option.is_array() { "1" } else { "0" }); - let description_xml = object_xml.append_child(dom.create_element("description")); - description_xml.append_child(dom.create_text_node(option.get_description())); - - if option.accept_value() { - let defaults: Vec = match option.get_default() { - PhpMixed::List(_) | PhpMixed::Array(_) => { - self.default_values_as_strings(option.get_default()) - } - PhpMixed::Bool(_) => vec![shirabe_php_shim::var_export(option.get_default(), true)], - d if shirabe_php_shim::php_truthy(d) => { - vec![shirabe_php_shim::php_to_string(option.get_default())] - } - _ => vec![], - }; - let defaults_xml = object_xml.append_child(dom.create_element("defaults")); - - if !defaults.is_empty() { - for default in defaults { - let default_xml = defaults_xml.append_child(dom.create_element("default")); - default_xml.append_child(dom.create_text_node(&default)); - } - } - } - - if option.is_negatable() { - let object_xml = dom.append_child(dom.create_element("option")); - object_xml.set_attribute("name", &format!("--no-{}", option.get_name())); - object_xml.set_attribute("shortcut", ""); - object_xml.set_attribute("accept_value", "0"); - object_xml.set_attribute("is_value_required", "0"); - object_xml.set_attribute("is_multiple", "0"); - let description_xml = object_xml.append_child(dom.create_element("description")); - description_xml.append_child( - dom.create_text_node(&format!("Negate the \"--{}\" option", option.get_name())), - ); - } - - dom - } - - /// Helper used by the default-value branches of getInputArgumentDocument / - /// getInputOptionDocument when the default is an array (returns it verbatim). - fn default_values_as_strings(&self, default: &PhpMixed) -> Vec { - match default { - PhpMixed::List(list) => list.iter().map(shirabe_php_shim::php_to_string).collect(), - PhpMixed::Array(arr) => arr.values().map(shirabe_php_shim::php_to_string).collect(), - _ => vec![], - } - } -} - -impl DescriptorInterface for XmlDescriptor { - fn describe( - &mut self, - output: std::rc::Rc>, - object: DescribableObject, - options: IndexMap, - ) -> anyhow::Result<()> { - Descriptor::describe(self, output, object, options) - } -} - -impl Descriptor for XmlDescriptor { - fn output(&self) -> std::rc::Rc> { - self.output.clone().unwrap() - } - - fn set_output(&mut self, output: std::rc::Rc>) { - self.output = Some(output); - } - - fn describe_input_argument( - &mut self, - argument: &InputArgument, - options: IndexMap, - ) -> anyhow::Result<()> { - XmlDescriptor::describe_input_argument(self, argument, options) - } - - fn describe_input_option( - &mut self, - option: &InputOption, - options: IndexMap, - ) -> anyhow::Result<()> { - XmlDescriptor::describe_input_option(self, option, options) - } - - fn describe_input_definition( - &mut self, - definition: &InputDefinition, - options: IndexMap, - ) -> anyhow::Result<()> { - XmlDescriptor::describe_input_definition(self, definition, options) - } - - fn describe_command( - &mut self, - command: &dyn Command, - options: IndexMap, - ) -> anyhow::Result<()> { - XmlDescriptor::describe_command(self, command, options) - } - - fn describe_application( - &mut self, - application: std::rc::Rc>, - options: IndexMap, - ) -> anyhow::Result<()> { - XmlDescriptor::describe_application(self, application, options) - } -} -- cgit v1.3.1-4-g156e