Skip to content

Commit

Permalink
feat(crystal): support crystal format
Browse files Browse the repository at this point in the history
  • Loading branch information
hougesen committed Mar 11, 2024
1 parent 35c6eec commit c5e84c1
Show file tree
Hide file tree
Showing 8 changed files with 196 additions and 13 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ jobs:
ruby-version: "3.3"
# dart_format
- uses: dart-lang/setup-dart@v1
# crystal_format
- uses: crystal-lang/install-crystal@v1

- run: rustup toolchain install stable --profile minimal
- run: rustup component add rustfmt clippy
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ mdsf init
| C | `clang-format` |
| CSS | `prettier` |
| Cpp | `clang-format` |
| Crystal | `crystal_format` |
| Dart | `dart_format` |
| Elixir | `mix_format` |
| Gleam | `gleam_format` |
Expand Down
32 changes: 32 additions & 0 deletions schemas/v0.0.0/mdsf.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,17 @@
}
]
},
"crystal": {
"default": {
"enabled": true,
"formatter": "crystal_format"
},
"allOf": [
{
"$ref": "#/definitions/Crystal"
}
]
},
"csharp": {
"default": {
"enabled": true,
Expand Down Expand Up @@ -365,6 +376,27 @@
"type": "string",
"enum": ["clang-format"]
},
"Crystal": {
"type": "object",
"properties": {
"enabled": {
"default": true,
"type": "boolean"
},
"formatter": {
"default": "crystal_format",
"allOf": [
{
"$ref": "#/definitions/CrystalFormatter"
}
]
}
}
},
"CrystalFormatter": {
"type": "string",
"enum": ["crystal_format"]
},
"Css": {
"type": "object",
"properties": {
Expand Down
13 changes: 9 additions & 4 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use schemars::JsonSchema;

use crate::languages::{
c::C, cpp::Cpp, 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,
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,
};

#[derive(Debug, serde::Serialize, serde::Deserialize, JsonSchema)]
Expand All @@ -20,6 +21,9 @@ pub struct MdsfConfig {
#[serde(default)]
pub cpp: Cpp,

#[serde(default)]
pub crystal: Crystal,

#[serde(default)]
pub csharp: CSharp,

Expand Down Expand Up @@ -104,6 +108,7 @@ impl Default for MdsfConfig {

c: C::default(),
cpp: Cpp::default(),
crystal: Crystal::default(),
csharp: CSharp::default(),
css: Css::default(),
dart: Dart::default(),
Expand Down
39 changes: 39 additions & 0 deletions src/formatters/crystal_format.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
use super::execute_command;

#[inline]
pub fn format_using_crystal_format(
snippet_path: &std::path::Path,
) -> std::io::Result<(bool, Option<String>)> {
let mut cmd = std::process::Command::new("crystal");

cmd.arg("tool").arg("format").arg(snippet_path);

execute_command(&mut cmd, snippet_path)
}

#[cfg(test)]
mod test_crystal_format {
use crate::{formatters::setup_snippet, languages::Language};

use super::format_using_crystal_format;

#[test]
fn it_should_format_crystal() {
let input = "def add(a, b) return a + b end";

let expected_output = "def add(a, b)
return a + b
end
";

let snippet = setup_snippet(input, Language::Crystal.to_file_ext())
.expect("it to create a snippet file");

let output = format_using_crystal_format(snippet.path())
.expect("it to be successful")
.1
.expect("it to be some");

assert_eq!(expected_output, output);
}
}
14 changes: 8 additions & 6 deletions src/formatters/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub mod biome;
pub mod black;
pub mod blue;
pub mod clang_format;
pub mod crystal_format;
pub mod dart_format;
pub mod gleam_format;
pub mod gofmt;
Expand Down Expand Up @@ -91,17 +92,24 @@ pub fn format_snippet(config: &MdsfConfig, language: &Language, code: &str) -> S
let snippet_path = snippet.path();

if let Ok(Some(formatted_code)) = match language {
Language::C => config.c.format(snippet_path),
Language::CSharp => config.csharp.format(snippet_path),
Language::Cpp => config.cpp.format(snippet_path),
Language::Crystal => config.crystal.format(snippet_path),
Language::Css => config.css.format(snippet_path),
Language::Dart => config.dart.format(snippet_path),
Language::Elixir => config.elixir.format(snippet_path),
Language::Gleam => config.gleam.format(snippet_path),
Language::Go => config.go.format(snippet_path),
Language::Html => config.html.format(snippet_path),
Language::Java => config.java.format(snippet_path),
Language::JavaScript => config.javascript.format(snippet_path),
Language::Json => config.json.format(snippet_path),
Language::Lua => config.lua.format(snippet_path),
Language::Markdown => config.markdown.format(snippet_path),
Language::Nim => config.nim.format(snippet_path),
Language::ObjectiveC => config.objective_c.format(snippet_path),
Language::Protobuf => config.protobuf.format(snippet_path),
Language::Python => config.python.format(snippet_path),
Language::Ruby => config.ruby.format(snippet_path),
Language::Rust => config.rust.format(snippet_path),
Expand All @@ -112,12 +120,6 @@ pub fn format_snippet(config: &MdsfConfig, language: &Language, code: &str) -> S
Language::Vue => config.vue.format(snippet_path),
Language::Yaml => config.yaml.format(snippet_path),
Language::Zig => config.zig.format(snippet_path),
Language::Protobuf => config.protobuf.format(snippet_path),
Language::CSharp => config.csharp.format(snippet_path),
Language::ObjectiveC => config.objective_c.format(snippet_path),
Language::Java => config.java.format(snippet_path),
Language::Cpp => config.cpp.format(snippet_path),
Language::C => config.c.format(snippet_path),
} {
let mut f = formatted_code.trim().to_owned();

Expand Down
99 changes: 99 additions & 0 deletions src/languages/crystal.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
use schemars::JsonSchema;

use crate::{config::default_enabled, formatters::crystal_format::format_using_crystal_format};

use super::LanguageFormatter;

#[derive(Debug, Default, serde::Serialize, serde::Deserialize, JsonSchema)]
#[cfg_attr(test, derive(PartialEq, Eq))]
pub enum CrystalFormatter {
#[default]
#[serde(rename = "crystal_format")]
CrystalFormat,
}

#[derive(Debug, serde::Serialize, serde::Deserialize, JsonSchema)]
#[cfg_attr(test, derive(PartialEq, Eq))]
pub struct Crystal {
#[serde(default = "default_enabled")]
pub enabled: bool,
#[serde(default)]
pub formatter: CrystalFormatter,
}

impl Default for Crystal {
#[inline]
fn default() -> Self {
Self {
enabled: true,
formatter: CrystalFormatter::default(),
}
}
}

impl LanguageFormatter for Crystal {
#[inline]
fn format(&self, snippet_path: &std::path::Path) -> std::io::Result<Option<String>> {
if !self.enabled {
return Ok(None);
}

match self.formatter {
CrystalFormatter::CrystalFormat => {
format_using_crystal_format(snippet_path).map(|res| res.1)
}
}
}
}

#[cfg(test)]
mod test_crystal {
use crate::{formatters::setup_snippet, languages::LanguageFormatter};

use super::{Crystal, CrystalFormatter};

const INPUT: &str = "def add(a, b) return a + b end";

const EXTENSION: &str = crate::languages::Language::Crystal.to_file_ext();

#[test]
fn it_should_be_enabled_by_default() {
assert!(Crystal::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!(Crystal {
enabled: false,
formatter: CrystalFormatter::CrystalFormat,
}
.format(snippet_path)
.expect("it to not fail")
.is_none());
}

#[test]
fn test_crystal_format() {
let expected_output = "def add(a, b)
return a + b
end
";
let l = Crystal {
enabled: true,
formatter: CrystalFormatter::CrystalFormat,
};

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");

assert_eq!(output, expected_output);
}
}
9 changes: 6 additions & 3 deletions src/languages/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
pub enum Language {
C,
CSharp,
Crystal,
Cpp,
CSharp,
Css,
Dart,
Elixir,
Expand All @@ -28,7 +29,6 @@ pub enum Language {
Zig,
// TODO: Haskell,
// TODO: OCaml,
// TODO: Crystal,
// TODO: PHP,
// TODO: Kotlin,
// TODO: FSharp,
Expand All @@ -47,6 +47,7 @@ pub enum Language {

pub mod c;
pub mod cpp;
pub mod crystal;
pub mod csharp;
pub mod css;
pub mod dart;
Expand Down Expand Up @@ -83,6 +84,7 @@ impl Language {
match input {
"c" | "clang" => Some(Self::C),
"cpp" | "c++" => Some(Self::Cpp),
"crystal" | "cr" => Some(Self::Crystal),
"csharp" | "c#" => Some(Self::CSharp),
"css" | "scss" => Some(Self::Css),
"dart" => Some(Self::Dart),
Expand Down Expand Up @@ -119,8 +121,9 @@ impl Language {
match self {
// NOTE: since scss is a superset of css we might as well support both at the same time
Self::C => ".c",
Self::Cpp => ".cpp",
Self::Crystal => ".cr",
Self::CSharp => ".cs",
Self::Cpp => "cpp",
Self::Css => ".scss",
Self::Dart => ".dart",
Self::Elixir => ".ex",
Expand Down

0 comments on commit c5e84c1

Please sign in to comment.