diff --git a/README.md b/README.md index 6b514355..5f913499 100644 --- a/README.md +++ b/README.md @@ -92,5 +92,6 @@ mdsf init | TOML | `taplo` | | TypeScript | `prettier`, `biome`, `deno_fmt` | | Vue | `prettier` | +| Xml | `xmllint` | | YAML | `prettier` | | Zig | `zigfmt` | diff --git a/schemas/v0.0.1/mdsf.schema.json b/schemas/v0.0.1/mdsf.schema.json index 9680f125..e77fbade 100644 --- a/schemas/v0.0.1/mdsf.schema.json +++ b/schemas/v0.0.1/mdsf.schema.json @@ -369,6 +369,17 @@ } ] }, + "xml": { + "default": { + "enabled": true, + "formatter": "xmllint" + }, + "allOf": [ + { + "$ref": "#/definitions/Lang_for_Xml" + } + ] + }, "yaml": { "default": { "enabled": true, @@ -857,6 +868,18 @@ } } }, + "Lang_for_Xml": { + "type": "object", + "required": ["enabled", "formatter"], + "properties": { + "enabled": { + "type": "boolean" + }, + "formatter": { + "$ref": "#/definitions/MdsfFormatter_for_Xml" + } + } + }, "Lang_for_Yaml": { "type": "object", "required": ["enabled", "formatter"], @@ -1318,6 +1341,19 @@ } ] }, + "MdsfFormatter_for_Xml": { + "anyOf": [ + { + "$ref": "#/definitions/Xml" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/MdsfFormatter_for_Xml" + } + } + ] + }, "MdsfFormatter_for_Yaml": { "anyOf": [ { @@ -1400,6 +1436,10 @@ "type": "string", "enum": ["prettier"] }, + "Xml": { + "type": "string", + "enum": ["xmllint"] + }, "Yaml": { "type": "string", "enum": ["prettier"] diff --git a/src/config.rs b/src/config.rs index 5d592a2a..63207377 100644 --- a/src/config.rs +++ b/src/config.rs @@ -6,7 +6,7 @@ use crate::languages::{ javascript::JavaScript, json::Json, just::Just, lua::Lua, markdown::Markdown, nim::Nim, objective_c::ObjectiveC, ocaml::OCaml, protobuf::Protobuf, python::Python, rescript::ReScript, roc::Roc, ruby::Ruby, rust::Rust, shell::Shell, sql::Sql, toml::Toml, typescript::TypeScript, - vue::Vue, yaml::Yaml, zig::Zig, Lang, + vue::Vue, xml::Xml, yaml::Yaml, zig::Zig, Lang, }; #[derive(Debug, serde::Serialize, serde::Deserialize, JsonSchema)] @@ -115,6 +115,9 @@ pub struct MdsfConfig { #[serde(default)] pub vue: Lang, + #[serde(default)] + pub xml: Lang, + #[serde(default)] pub yaml: Lang, @@ -161,6 +164,7 @@ impl Default for MdsfConfig { toml: Lang::::default(), typescript: Lang::::default(), vue: Lang::::default(), + xml: Lang::::default(), yaml: Lang::::default(), zig: Lang::::default(), } diff --git a/src/formatters/mod.rs b/src/formatters/mod.rs index 20150afc..9ec0e013 100644 --- a/src/formatters/mod.rs +++ b/src/formatters/mod.rs @@ -35,6 +35,7 @@ pub mod sqlfluff; pub mod stylua; pub mod taplo; pub mod usort; +pub mod xmllint; pub mod yapf; pub mod zigfmt; @@ -134,6 +135,7 @@ pub fn format_snippet(config: &MdsfConfig, language: &Language, code: &str) -> S Language::Yaml => config.yaml.format(snippet_path), Language::Zig => config.zig.format(snippet_path), Language::ReScript => config.rescript.format(snippet_path), + Language::Xml => config.xml.format(snippet_path), } { let mut f = formatted_code.trim().to_owned(); diff --git a/src/formatters/xmllint.rs b/src/formatters/xmllint.rs new file mode 100644 index 00000000..c346e7f6 --- /dev/null +++ b/src/formatters/xmllint.rs @@ -0,0 +1,53 @@ +use super::execute_command; + +#[inline] +pub fn format_using_xmllint( + snippet_path: &std::path::Path, +) -> std::io::Result<(bool, Option)> { + let mut cmd = std::process::Command::new("xmllint"); + + cmd.arg("--format") + .arg(snippet_path) + .arg("--output") + .arg(snippet_path); + + execute_command(&mut cmd, snippet_path) +} + +#[cfg(test)] +mod test_isort { + use crate::{formatters::setup_snippet, languages::Language}; + + use super::format_using_xmllint; + + #[test_with::executable(xmllint)] + #[test] + fn it_should_format_xml() { + let input = " + + Tove + Jani + Reminder + Don't forget me this weekend! + "; + + let expected_output = r#" + + Tove + Jani + Reminder + Don't forget me this weekend! + +"#; + + let snippet = setup_snippet(input, Language::Python.to_file_ext()) + .expect("it to create a snippet file"); + + let output = format_using_xmllint(snippet.path()) + .expect("it to be successful") + .1 + .expect("it to be some"); + + assert_eq!(expected_output, output); + } +} diff --git a/src/languages/mod.rs b/src/languages/mod.rs index b715cab0..4d58531d 100644 --- a/src/languages/mod.rs +++ b/src/languages/mod.rs @@ -36,6 +36,7 @@ pub enum Language { Toml, TypeScript, Vue, + Xml, Yaml, Zig, // TODO: Haskell, @@ -86,6 +87,7 @@ pub mod sql; pub mod toml; pub mod typescript; pub mod vue; +pub mod xml; pub mod yaml; pub mod zig; @@ -137,6 +139,7 @@ impl Language { "graphql" | "gql" => Some(Self::GraphQL), "elm" => Some(Self::Elm), "rescript" => Some(Self::ReScript), + "xml" => Some(Self::Xml), _ => None, } } @@ -181,6 +184,7 @@ impl Language { Self::Blade => ".blade.php", Self::OCaml => ".ml", Self::ReScript => ".res", + Self::Xml => ".xml", } } } diff --git a/src/languages/xml.rs b/src/languages/xml.rs new file mode 100644 index 00000000..751ae7f8 --- /dev/null +++ b/src/languages/xml.rs @@ -0,0 +1,108 @@ +use schemars::JsonSchema; + +use crate::formatters::{xmllint::format_using_xmllint, MdsfFormatter}; + +use super::{Lang, LanguageFormatter}; + +#[derive(Debug, Default, serde::Serialize, serde::Deserialize, JsonSchema)] +#[cfg_attr(test, derive(PartialEq, Eq))] +pub enum Xml { + #[default] + #[serde(rename = "xmllint")] + Xmllint, +} + +impl Default for Lang { + #[inline] + fn default() -> Self { + Self { + enabled: true, + formatter: MdsfFormatter::::default(), + } + } +} + +impl Default for MdsfFormatter { + #[inline] + fn default() -> Self { + Self::Single(Xml::Xmllint) + } +} + +impl LanguageFormatter for Xml { + #[inline] + fn format_snippet( + &self, + snippet_path: &std::path::Path, + ) -> std::io::Result<(bool, Option)> { + match self { + Self::Xmllint => format_using_xmllint(snippet_path), + } + } +} + +#[cfg(test)] +mod test_xml { + use crate::{ + formatters::{setup_snippet, MdsfFormatter}, + languages::Lang, + }; + + use super::Xml; + + const INPUT: &str = " + + Tove + Jani + Reminder + Don't forget me this weekend! + "; + + const EXTENSION: &str = crate::languages::Language::Xml.to_file_ext(); + + #[test] + fn it_should_be_enabled_by_default() { + assert!(Lang::::default().enabled); + } + + #[test] + fn it_should_not_format_when_enabled_is_false() { + let l = Lang:: { + enabled: false, + formatter: MdsfFormatter::Single(Xml::Xmllint), + }; + + let snippet = setup_snippet(INPUT, EXTENSION).expect("it to save the file"); + let snippet_path = snippet.path(); + + assert!(l.format(snippet_path).expect("it to not fail").is_none()); + } + + #[test_with::executable(xmllint)] + #[test] + fn test_xmllint() { + let l = Lang:: { + enabled: true, + formatter: MdsfFormatter::Single(Xml::Xmllint), + }; + + let snippet = setup_snippet(INPUT, EXTENSION).expect("it to save the file"); + let snippet_path = snippet.path(); + + let output = l + .format(snippet_path) + .expect("it to not fail") + .expect("it to be a snippet"); + + let expected_output = r#" + + Tove + Jani + Reminder + Don't forget me this weekend! + +"#; + + assert_eq!(output, expected_output); + } +}