aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe-symfony-console/src/descriptor/application_description.rs
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-08-09 11:19:03 +0900
committernsfisis <nsfisis@gmail.com>2026-08-09 11:19:03 +0900
commit6051927c8fa32cfffa102d2a170c5a6cf747a1b9 (patch)
tree3fe6769c90cb03ca8f1b9b825e38ad459182361e /crates/shirabe-symfony-console/src/descriptor/application_description.rs
parente3e8806aec771e482899ed3470e920f7b291fa95 (diff)
downloadphp-shirabe-6051927c8fa32cfffa102d2a170c5a6cf747a1b9.tar.gz
php-shirabe-6051927c8fa32cfffa102d2a170c5a6cf747a1b9.tar.zst
php-shirabe-6051927c8fa32cfffa102d2a170c5a6cf747a1b9.zip
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) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe-symfony-console/src/descriptor/application_description.rs')
-rw-r--r--crates/shirabe-symfony-console/src/descriptor/application_description.rs189
1 files changed, 189 insertions, 0 deletions
diff --git a/crates/shirabe-symfony-console/src/descriptor/application_description.rs b/crates/shirabe-symfony-console/src/descriptor/application_description.rs
new file mode 100644
index 00000000..d63f0be2
--- /dev/null
+++ b/crates/shirabe-symfony-console/src/descriptor/application_description.rs
@@ -0,0 +1,189 @@
+//! ref: composer/vendor/symfony/console/Descriptor/ApplicationDescription.php
+
+use crate::application::Application;
+use crate::command::command::Command;
+use crate::exception::command_not_found_exception::CommandNotFoundException;
+use indexmap::IndexMap;
+use shirabe_php_shim::PhpMixed;
+
+/// @internal
+#[derive(Debug)]
+pub struct ApplicationDescription {
+ application: std::rc::Rc<std::cell::RefCell<dyn Application>>,
+ namespace: Option<String>,
+ show_hidden: bool,
+
+ /// @var array
+ /// Each namespace entry is `['id' => string, 'commands' => string[]]`.
+ namespaces: Option<IndexMap<String, IndexMap<String, PhpMixed>>>,
+
+ /// @var array<string, Command>
+ commands: Option<IndexMap<String, std::rc::Rc<std::cell::RefCell<dyn Command>>>>,
+
+ /// @var array<string, Command>
+ aliases: Option<IndexMap<String, std::rc::Rc<std::cell::RefCell<dyn Command>>>>,
+}
+
+impl ApplicationDescription {
+ pub const GLOBAL_NAMESPACE: &'static str = "_global";
+
+ pub fn new(
+ application: std::rc::Rc<std::cell::RefCell<dyn Application>>,
+ namespace: Option<String>,
+ show_hidden: bool,
+ ) -> Self {
+ ApplicationDescription {
+ application,
+ namespace,
+ show_hidden,
+ namespaces: None,
+ commands: None,
+ aliases: None,
+ }
+ }
+
+ pub fn get_namespaces(&mut self) -> IndexMap<String, IndexMap<String, PhpMixed>> {
+ if self.namespaces.is_none() {
+ self.inspect_application();
+ }
+
+ self.namespaces.clone().unwrap()
+ }
+
+ pub fn get_commands(
+ &mut self,
+ ) -> &IndexMap<String, std::rc::Rc<std::cell::RefCell<dyn Command>>> {
+ if self.commands.is_none() {
+ self.inspect_application();
+ }
+
+ self.commands.as_ref().unwrap()
+ }
+
+ /// @throws CommandNotFoundException
+ pub fn get_command(
+ &self,
+ name: &str,
+ ) -> anyhow::Result<std::rc::Rc<std::cell::RefCell<dyn Command>>> {
+ 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<String> = 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<String, PhpMixed> = 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<String, std::rc::Rc<std::cell::RefCell<dyn Command>>>,
+ ) -> IndexMap<String, IndexMap<String, std::rc::Rc<std::cell::RefCell<dyn Command>>>> {
+ let mut namespaced_commands: IndexMap<
+ String,
+ IndexMap<String, std::rc::Rc<std::cell::RefCell<dyn Command>>>,
+ > = IndexMap::new();
+ let mut global_commands: IndexMap<String, std::rc::Rc<std::cell::RefCell<dyn Command>>> =
+ IndexMap::new();
+ let mut sorted_commands: IndexMap<
+ String,
+ IndexMap<String, std::rc::Rc<std::cell::RefCell<dyn Command>>>,
+ > = 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
+ }
+}