diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index bfcc6d91..7b56ef56 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -43,12 +43,12 @@ jobs: steps: - uses: actions/checkout@v4 - uses: Swatinem/rust-cache@v2 - # gleam_format, mix_format + # _format, mix_format # NOTE: should be first since it is sometimes crashes (?) - uses: erlef/setup-beam@v1 with: otp-version: "26" - gleam-version: "1.0.0" + -version: "1.0.0" elixir-version: "1.16.1" # ruff, sqlfluff, black, blue, yapf, autopep8 - uses: actions/setup-python@v5 @@ -84,65 +84,82 @@ jobs: deno-version: v1.x # just_fmt - uses: taiki-e/install-action@just + # ocamlformat + - uses: ocaml/setup-ocaml@v2 + with: + ocaml-compiler: "5.1" - run: rustup toolchain install stable --profile minimal - run: rustup component add rustfmt clippy - - name: Validate taplo + - name: taplo run: taplo --version - - name: Install ruff + - name: ruff run: pip install ruff && ruff --version - - name: Install sqlfluff + - name: sqlfluff run: pip install sqlfluff && sqlfluff format --version - - name: Validate biome + - name: biome run: npx --yes @biomejs/biome --version - - name: Validate prettier + - name: prettier run: npx --yes prettier --version - - name: Validate nimpretty + - name: nimpretty run: nimpretty --version - - name: Validate zig fmt + - name: zig fmt run: zig fmt --help - - name: Validate gleam format + - name: gleam format run: gleam format --help - - name: Validate mix format + - name: mix format run: mix help format - - name: Install rubocop + - name: rubocop run: gem install rubocop && rubocop --version - - name: Install stylua + - name: stylua run: cargo install stylua && stylua --version - - name: Install shfmt + - name: shfmt run: go install mvdan.cc/sh/v3/cmd/shfmt@latest && shfmt --version - - name: Install gofumpt + - name: gofumpt run: go install mvdan.cc/gofumpt@latest && gofumpt --version - - name: Install black + - name: black run: pip install black && black --version - - name: Install blue + - name: blue run: pip install blue && blue --version - - name: Install yapf + - name: yapf run: pip install yapf && yapf --version - - name: Install autopep + - name: autopep run: pip install autopep8 && autopep8 --version - - name: Install clang-format + - name: clang-format run: pip install clang-format && clang-format --version - - run: pip install isort - - run: pip install usort + - name: isort + run: pip install isort + + - name: usort + run: pip install usort + + - name: ocamlformat + run: eval $(opam env) && opam install ocamlformat && eval $(opam env) && ocamlformat --version + + - name: blade-formatter + run: npx --yes blade-formatter --version + + - name: crystal_format + run: crystal tool format --help - - run: cargo test + - name: run tests + run: cargo test diff --git a/README.md b/README.md index 93f99e25..f64b63f3 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,7 @@ mdsf init | Lua | `stylua` | | Nim | `nimpretty` | | Objective C | `clang-format` | +| OCaml | `ocamlformat` | | Protobuf | `clang-format` | | Python | `ruff`, `black`, `blue`, `yapf`, `autopep8`, `isort`, `usort` | | Roc | `roc_format` | diff --git a/schemas/v0.0.1/mdsf.schema.json b/schemas/v0.0.1/mdsf.schema.json index 607a6ec9..8d40d02c 100644 --- a/schemas/v0.0.1/mdsf.schema.json +++ b/schemas/v0.0.1/mdsf.schema.json @@ -234,6 +234,17 @@ } ] }, + "ocaml": { + "default": { + "enabled": true, + "formatter": "ocamlformat" + }, + "allOf": [ + { + "$ref": "#/definitions/Lang_for_OCaml" + } + ] + }, "protobuf": { "default": { "enabled": true, @@ -679,6 +690,18 @@ } } }, + "Lang_for_OCaml": { + "type": "object", + "required": ["enabled", "formatter"], + "properties": { + "enabled": { + "type": "boolean" + }, + "formatter": { + "$ref": "#/definitions/MdsfFormatter_for_OCaml" + } + } + }, "Lang_for_ObjectiveC": { "type": "object", "required": ["enabled", "formatter"], @@ -1103,6 +1126,19 @@ } ] }, + "MdsfFormatter_for_OCaml": { + "anyOf": [ + { + "$ref": "#/definitions/OCaml" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/MdsfFormatter_for_OCaml" + } + } + ] + }, "MdsfFormatter_for_ObjectiveC": { "anyOf": [ { @@ -1276,6 +1312,10 @@ "type": "string", "enum": ["nimpretty"] }, + "OCaml": { + "type": "string", + "enum": ["ocamlformat"] + }, "ObjectiveC": { "type": "string", "enum": ["clang-format"] diff --git a/src/config.rs b/src/config.rs index 9bddc702..f0dea42f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -4,9 +4,9 @@ use crate::languages::{ blade::Blade, c::C, cpp::Cpp, crystal::Crystal, csharp::CSharp, css::Css, dart::Dart, elixir::Elixir, elm::Elm, gleam::Gleam, go::Go, graphql::GraphQL, html::Html, java::Java, javascript::JavaScript, json::Json, just::Just, lua::Lua, markdown::Markdown, nim::Nim, - objective_c::ObjectiveC, protobuf::Protobuf, python::Python, roc::Roc, ruby::Ruby, rust::Rust, - shell::Shell, sql::Sql, toml::Toml, typescript::TypeScript, vue::Vue, yaml::Yaml, zig::Zig, - Lang, + objective_c::ObjectiveC, ocaml::OCaml, protobuf::Protobuf, python::Python, roc::Roc, + ruby::Ruby, rust::Rust, shell::Shell, sql::Sql, toml::Toml, typescript::TypeScript, vue::Vue, + yaml::Yaml, zig::Zig, Lang, }; #[derive(Debug, serde::Serialize, serde::Deserialize, JsonSchema)] @@ -79,6 +79,9 @@ pub struct MdsfConfig { #[serde(default)] pub objective_c: Lang, + #[serde(default)] + pub ocaml: Lang, + #[serde(default)] pub protobuf: Lang, @@ -143,6 +146,7 @@ impl Default for MdsfConfig { markdown: Lang::::default(), nim: Lang::::default(), objective_c: Lang::::default(), + ocaml: Lang::::default(), protobuf: Lang::::default(), python: Lang::::default(), roc: Lang::::default(), diff --git a/src/formatters/mod.rs b/src/formatters/mod.rs index 1c04ed93..41092ef5 100644 --- a/src/formatters/mod.rs +++ b/src/formatters/mod.rs @@ -22,6 +22,7 @@ pub mod isort; pub mod just_fmt; pub mod mix_format; pub mod nimpretty; +pub mod ocamlformat; pub mod prettier; pub mod roc_format; pub mod rubocop; @@ -118,6 +119,7 @@ pub fn format_snippet(config: &MdsfConfig, language: &Language, code: &str) -> S Language::Markdown => config.markdown.format(snippet_path), Language::Nim => config.nim.format(snippet_path), Language::ObjectiveC => config.objective_c.format(snippet_path), + Language::OCaml => config.ocaml.format(snippet_path), Language::Protobuf => config.protobuf.format(snippet_path), Language::Python => config.python.format(snippet_path), Language::Roc => config.roc.format(snippet_path), diff --git a/src/formatters/ocamlformat.rs b/src/formatters/ocamlformat.rs new file mode 100644 index 00000000..5dc1d334 --- /dev/null +++ b/src/formatters/ocamlformat.rs @@ -0,0 +1,43 @@ +use super::execute_command; + +#[inline] +pub fn format_using_ocamlformat( + snippet_path: &std::path::Path, +) -> std::io::Result<(bool, Option)> { + let mut cmd = std::process::Command::new("ocamlformat"); + + cmd.arg("--ignore-invalid-option") + .arg("--inplace") + .arg("--quiet") + .arg("--enable-outside-detected-project") + .arg(snippet_path); + + execute_command(&mut cmd, snippet_path) +} + +#[cfg(test)] +mod test_ocamlformat { + use crate::{ + formatters::{ocamlformat::format_using_ocamlformat, setup_snippet}, + languages::Language, + }; + + #[test] + fn it_should_format_ocaml() { + let input = " +let add a b = a + b + "; + let expected_output = "let add a b = a + b +"; + + let snippet = setup_snippet(input, Language::OCaml.to_file_ext()) + .expect("it to create a snippet file"); + + let output = format_using_ocamlformat(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 cf3122d0..8b3b618d 100644 --- a/src/languages/mod.rs +++ b/src/languages/mod.rs @@ -24,6 +24,7 @@ pub enum Language { Markdown, Nim, ObjectiveC, + OCaml, Protobuf, Python, Roc, @@ -37,7 +38,6 @@ pub enum Language { Yaml, Zig, // TODO: Haskell, - // TODO: OCaml, // TODO: PHP, // TODO: Kotlin, // TODO: FSharp, @@ -73,6 +73,7 @@ pub mod lua; pub mod markdown; pub mod nim; pub mod objective_c; +pub mod ocaml; pub mod protobuf; pub mod python; pub mod roc; @@ -116,6 +117,7 @@ impl Language { "markdown" | "md" => Some(Self::Markdown), "nim" => Some(Self::Nim), "objectivec" | "objective-c" | "objc" => Some(Self::ObjectiveC), + "ocaml" => Some(Self::ObjectiveC), "profobuf" | "profo" => Some(Self::Protobuf), "python" => Some(Self::Python), "roc" => Some(Self::Roc), @@ -174,6 +176,7 @@ impl Language { Self::GraphQL => ".gql", Self::Elm => ".elm", Self::Blade => ".blade.php", + Self::OCaml => ".ml", } } } diff --git a/src/languages/ocaml.rs b/src/languages/ocaml.rs new file mode 100644 index 00000000..77567dc3 --- /dev/null +++ b/src/languages/ocaml.rs @@ -0,0 +1,98 @@ +use schemars::JsonSchema; + +use crate::formatters::{ocamlformat::format_using_ocamlformat, MdsfFormatter}; + +use super::{Lang, LanguageFormatter}; + +#[derive(Debug, Default, serde::Serialize, serde::Deserialize, JsonSchema)] +#[cfg_attr(test, derive(PartialEq, Eq))] +pub enum OCaml { + #[default] + #[serde(rename = "ocamlformat")] + OCamlFormat, +} + +impl Default for Lang { + #[inline] + fn default() -> Self { + Self { + enabled: true, + formatter: MdsfFormatter::::default(), + } + } +} + +impl Default for MdsfFormatter { + #[inline] + fn default() -> Self { + Self::Single(OCaml::OCamlFormat) + } +} + +impl LanguageFormatter for OCaml { + #[inline] + fn format_snippet( + &self, + snippet_path: &std::path::Path, + ) -> std::io::Result<(bool, Option)> { + match self { + Self::OCamlFormat => format_using_ocamlformat(snippet_path), + } + } +} + +#[cfg(test)] +mod test_ocaml { + use crate::{ + formatters::{setup_snippet, MdsfFormatter}, + languages::Lang, + }; + + use super::OCaml; + + const INPUT: &str = " +let add a b = a + b + "; + + const EXTENSION: &str = crate::languages::Language::OCaml.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 snippet = setup_snippet(INPUT, EXTENSION).expect("it to save the file"); + let snippet_path = snippet.path(); + + assert!(Lang:: { + enabled: false, + formatter: MdsfFormatter::Single(OCaml::default()) + } + .format(snippet_path) + .expect("it to not fail") + .is_none()); + } + + #[test] + fn test_ocamlformat() { + let l = Lang:: { + enabled: true, + formatter: MdsfFormatter::Single(OCaml::OCamlFormat), + }; + + 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 = "let add a b = a + b +"; + + assert_eq!(output, expected_output); + } +}