aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe-symfony-console/src/descriptor/xml_descriptor.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/shirabe-symfony-console/src/descriptor/xml_descriptor.rs')
-rw-r--r--crates/shirabe-symfony-console/src/descriptor/xml_descriptor.rs403
1 files changed, 403 insertions, 0 deletions
diff --git a/crates/shirabe-symfony-console/src/descriptor/xml_descriptor.rs b/crates/shirabe-symfony-console/src/descriptor/xml_descriptor.rs
new file mode 100644
index 00000000..11c26b46
--- /dev/null
+++ b/crates/shirabe-symfony-console/src/descriptor/xml_descriptor.rs
@@ -0,0 +1,403 @@
+//! ref: composer/vendor/symfony/console/Descriptor/XmlDescriptor.php
+
+use crate::application::Application;
+use crate::command::command::Command;
+use crate::descriptor::application_description::ApplicationDescription;
+use crate::descriptor::descriptor::Descriptor;
+use crate::descriptor::descriptor_interface::{DescribableObject, DescriptorInterface};
+use crate::input::input_argument::InputArgument;
+use crate::input::input_definition::InputDefinition;
+use crate::input::input_option::InputOption;
+use crate::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<std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>>,
+}
+
+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 <definition> element");
+ self.append_document(&command_xml, &definition_node);
+ }
+
+ dom
+ }
+
+ pub fn get_application_document(
+ &self,
+ application: std::rc::Rc<std::cell::RefCell<dyn Application>>,
+ namespace: Option<String>,
+ 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<String, PhpMixed>,
+ ) -> anyhow::Result<()> {
+ self.write_document(self.get_input_argument_document(argument));
+ Ok(())
+ }
+
+ fn describe_input_option(
+ &mut self,
+ option: &InputOption,
+ _options: IndexMap<String, PhpMixed>,
+ ) -> anyhow::Result<()> {
+ self.write_document(self.get_input_option_document(option));
+ Ok(())
+ }
+
+ fn describe_input_definition(
+ &mut self,
+ definition: &InputDefinition,
+ _options: IndexMap<String, PhpMixed>,
+ ) -> anyhow::Result<()> {
+ self.write_document(self.get_input_definition_document(definition));
+ Ok(())
+ }
+
+ fn describe_command(
+ &mut self,
+ command: &dyn Command,
+ options: IndexMap<String, PhpMixed>,
+ ) -> 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<std::cell::RefCell<dyn Application>>,
+ options: IndexMap<String, PhpMixed>,
+ ) -> 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<String> = 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<String> = 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<String> {
+ 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<std::cell::RefCell<dyn OutputInterface>>,
+ object: DescribableObject,
+ options: IndexMap<String, PhpMixed>,
+ ) -> anyhow::Result<()> {
+ Descriptor::describe(self, output, object, options)
+ }
+}
+
+impl Descriptor for XmlDescriptor {
+ fn output(&self) -> std::rc::Rc<std::cell::RefCell<dyn OutputInterface>> {
+ self.output.clone().unwrap()
+ }
+
+ fn set_output(&mut self, output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>) {
+ self.output = Some(output);
+ }
+
+ fn describe_input_argument(
+ &mut self,
+ argument: &InputArgument,
+ options: IndexMap<String, PhpMixed>,
+ ) -> anyhow::Result<()> {
+ XmlDescriptor::describe_input_argument(self, argument, options)
+ }
+
+ fn describe_input_option(
+ &mut self,
+ option: &InputOption,
+ options: IndexMap<String, PhpMixed>,
+ ) -> anyhow::Result<()> {
+ XmlDescriptor::describe_input_option(self, option, options)
+ }
+
+ fn describe_input_definition(
+ &mut self,
+ definition: &InputDefinition,
+ options: IndexMap<String, PhpMixed>,
+ ) -> anyhow::Result<()> {
+ XmlDescriptor::describe_input_definition(self, definition, options)
+ }
+
+ fn describe_command(
+ &mut self,
+ command: &dyn Command,
+ options: IndexMap<String, PhpMixed>,
+ ) -> anyhow::Result<()> {
+ XmlDescriptor::describe_command(self, command, options)
+ }
+
+ fn describe_application(
+ &mut self,
+ application: std::rc::Rc<std::cell::RefCell<dyn Application>>,
+ options: IndexMap<String, PhpMixed>,
+ ) -> anyhow::Result<()> {
+ XmlDescriptor::describe_application(self, application, options)
+ }
+}