diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index ffa48296..487fd027 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -76,6 +76,8 @@ jobs: - uses: dart-lang/setup-dart@v1 # crystal_format - uses: crystal-lang/install-crystal@v1 + # roc_format + - uses: hasnep/setup-roc@v0.1.1 - run: rustup toolchain install stable --profile minimal - run: rustup component add rustfmt clippy diff --git a/README.md b/README.md index efe9f0db..48a1d41f 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,7 @@ mdsf init | Objective C | `clang-format` | | Protobuf | `clang-format` | | Python | `ruff`, `black`, `blue`, `yapf`, `autopep8` | +| Roc | `roc_format` | | Ruby | `rubocop` | | Rust | `rustfmt` | | SQL | `sqlfluff`, `sql-formatter` | diff --git a/schemas/v0.0.0/mdsf.schema.json b/schemas/v0.0.0/mdsf.schema.json index 7ae20c81..e650e020 100644 --- a/schemas/v0.0.0/mdsf.schema.json +++ b/schemas/v0.0.0/mdsf.schema.json @@ -212,6 +212,17 @@ } ] }, + "roc": { + "default": { + "enabled": true, + "formatter": "roc_format" + }, + "allOf": [ + { + "$ref": "#/definitions/Roc" + } + ] + }, "ruby": { "default": { "enabled": true, @@ -712,6 +723,27 @@ "type": "string", "enum": ["ruff", "black", "yapf", "blue", "autopep8"] }, + "Roc": { + "type": "object", + "properties": { + "enabled": { + "default": true, + "type": "boolean" + }, + "formatter": { + "default": "roc_format", + "allOf": [ + { + "$ref": "#/definitions/RocFormatter" + } + ] + } + } + }, + "RocFormatter": { + "type": "string", + "enum": ["roc_format"] + }, "Ruby": { "type": "object", "properties": { diff --git a/src/config.rs b/src/config.rs index ee35c43e..e71511a3 100644 --- a/src/config.rs +++ b/src/config.rs @@ -4,8 +4,8 @@ use crate::languages::{ c::C, cpp::Cpp, crystal::Crystal, csharp::CSharp, css::Css, dart::Dart, elixir::Elixir, gleam::Gleam, go::Go, html::Html, java::Java, javascript::JavaScript, json::Json, lua::Lua, markdown::Markdown, nim::Nim, objective_c::ObjectiveC, protobuf::Protobuf, python::Python, - ruby::Ruby, rust::Rust, shell::Shell, sql::Sql, toml::Toml, typescript::TypeScript, vue::Vue, - yaml::Yaml, zig::Zig, + roc::Roc, ruby::Ruby, rust::Rust, shell::Shell, sql::Sql, toml::Toml, typescript::TypeScript, + vue::Vue, yaml::Yaml, zig::Zig, }; #[derive(Debug, serde::Serialize, serde::Deserialize, JsonSchema)] @@ -72,6 +72,9 @@ pub struct MdsfConfig { #[serde(default)] pub python: Python, + #[serde(default)] + pub roc: Roc, + #[serde(default)] pub ruby: Ruby, @@ -125,6 +128,7 @@ impl Default for MdsfConfig { objective_c: ObjectiveC::default(), protobuf: Protobuf::default(), python: Python::default(), + roc: Roc::default(), ruby: Ruby::default(), rust: Rust::default(), shell: Shell::default(), diff --git a/src/formatters/mod.rs b/src/formatters/mod.rs index ba1da18b..f5a7e7f2 100644 --- a/src/formatters/mod.rs +++ b/src/formatters/mod.rs @@ -20,6 +20,7 @@ pub mod gofumpt; pub mod mix_format; pub mod nimpretty; pub mod prettier; +pub mod roc_format; pub mod rubocop; pub mod ruff; pub mod rustfmt; @@ -111,6 +112,7 @@ pub fn format_snippet(config: &MdsfConfig, language: &Language, code: &str) -> S Language::ObjectiveC => config.objective_c.format(snippet_path), Language::Protobuf => config.protobuf.format(snippet_path), Language::Python => config.python.format(snippet_path), + Language::Roc => config.roc.format(snippet_path), Language::Ruby => config.ruby.format(snippet_path), Language::Rust => config.rust.format(snippet_path), Language::Shell => config.shell.format(snippet_path), diff --git a/src/formatters/roc_format.rs b/src/formatters/roc_format.rs new file mode 100644 index 00000000..8cdd49de --- /dev/null +++ b/src/formatters/roc_format.rs @@ -0,0 +1,58 @@ +use super::execute_command; + +#[inline] +pub fn format_using_roc_format( + snippet_path: &std::path::Path, +) -> std::io::Result<(bool, Option)> { + let mut cmd = std::process::Command::new("roc"); + + cmd.arg("format").arg(snippet_path); + + execute_command(&mut cmd, snippet_path) +} + +#[cfg(test)] +mod test_roc_format { + use crate::{formatters::setup_snippet, languages::Language}; + + use super::format_using_roc_format; + + #[test] + fn it_should_format_roc() { + let input = r#"app "helloWorld" + packages { pf: "https://github.com/roc-lang/" } + imports [pf.Stdout] + provides [main] to pf + + + + + + +main = + Stdout.line "Hello, World!" + + + "#; + + let expected_output = r#"app "helloWorld" + packages { pf: "https://github.com/roc-lang/" } + imports [pf.Stdout] + provides [main] to pf + +main = + Stdout.line "Hello, World!" + +"#; + + let snippet = + setup_snippet(input, Language::Roc.to_file_ext()).expect("it to create a snippet file"); + + let output = format_using_roc_format(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 26628bd9..55bae647 100644 --- a/src/languages/mod.rs +++ b/src/languages/mod.rs @@ -18,6 +18,7 @@ pub enum Language { ObjectiveC, Protobuf, Python, + Roc, Ruby, Rust, Shell, @@ -64,6 +65,7 @@ pub mod nim; pub mod objective_c; pub mod protobuf; pub mod python; +pub mod roc; pub mod ruby; pub mod rust; pub mod shell; @@ -101,6 +103,7 @@ impl Language { "objectivec" | "objective-c" | "objc" => Some(Self::ObjectiveC), "profobuf" | "profo" => Some(Self::Protobuf), "python" => Some(Self::Python), + "roc" => Some(Self::Roc), "ruby" => Some(Self::Ruby), "rust" | "rb" => Some(Self::Rust), "shell" | "sh" | "bash" | "zsh" => Some(Self::Shell), @@ -139,6 +142,7 @@ impl Language { Self::ObjectiveC => ".m", Self::Protobuf => ".proto", Self::Python => ".py", + Self::Roc => ".roc", Self::Ruby => ".rb", Self::Rust => ".rs", Self::Shell => ".sh", diff --git a/src/languages/roc.rs b/src/languages/roc.rs new file mode 100644 index 00000000..8e7564ad --- /dev/null +++ b/src/languages/roc.rs @@ -0,0 +1,117 @@ +use schemars::JsonSchema; + +use crate::{config::default_enabled, formatters::roc_format::format_using_roc_format}; + +use super::LanguageFormatter; + +#[derive(Debug, Default, serde::Serialize, serde::Deserialize, JsonSchema)] +#[cfg_attr(test, derive(PartialEq, Eq))] +pub enum RocFormatter { + #[default] + #[serde(rename = "roc_format")] + RocFormat, +} + +#[derive(Debug, serde::Serialize, serde::Deserialize, JsonSchema)] +#[cfg_attr(test, derive(PartialEq, Eq))] +pub struct Roc { + #[serde(default = "default_enabled")] + pub enabled: bool, + #[serde(default)] + pub formatter: RocFormatter, +} + +impl Default for Roc { + #[inline] + fn default() -> Self { + Self { + enabled: true, + formatter: RocFormatter::default(), + } + } +} + +impl LanguageFormatter for Roc { + #[inline] + fn format(&self, snippet_path: &std::path::Path) -> std::io::Result> { + if !self.enabled { + return Ok(None); + } + + match self.formatter { + RocFormatter::RocFormat => format_using_roc_format(snippet_path).map(|res| res.1), + } + } +} + +#[cfg(test)] +mod test_roc { + use crate::{formatters::setup_snippet, languages::LanguageFormatter}; + + use super::{Roc, RocFormatter}; + + const INPUT: &str = r#"app "helloWorld" + packages { pf: "https://github.com/roc-lang/" } + imports [pf.Stdout] + provides [main] to pf + + + + + + +main = + Stdout.line "Hello, World!" + + + "#; + + const EXTENSION: &str = crate::languages::Language::Roc.to_file_ext(); + + #[test] + fn it_should_be_enabled_by_default() { + assert!(Roc::default().enabled); + } + + #[test] + fn it_should_not_format_when_enabled_is_false() { + let snippet = setup_snippet(INPUT, EXTENSION).expect("it to save the file"); + let snippet_path = snippet.path(); + + assert!(Roc { + enabled: false, + formatter: RocFormatter::default(), + } + .format(snippet_path) + .expect("it to not fail") + .is_none()); + } + + #[test] + fn test_roc_format() { + let l = Roc { + enabled: true, + formatter: RocFormatter::RocFormat, + }; + + 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#"app "helloWorld" + packages { pf: "https://github.com/roc-lang/" } + imports [pf.Stdout] + provides [main] to pf + +main = + Stdout.line "Hello, World!" + +"#; + + assert_eq!(output, expected_output); + } +} diff --git a/tests/roc.md b/tests/roc.md new file mode 100644 index 00000000..552be69e --- /dev/null +++ b/tests/roc.md @@ -0,0 +1,17 @@ +```roc + +app "helloWorld" + packages { pf: "https://github.com/roc-lang/" } + imports [pf.Stdout] + provides [main] to pf + + + + + + +main = + Stdout.line "Hello, World!" + + +```