diff --git a/topiary-cli/Cargo.toml b/topiary-cli/Cargo.toml index 7eae00c9..0e78ab03 100644 --- a/topiary-cli/Cargo.toml +++ b/topiary-cli/Cargo.toml @@ -3,12 +3,12 @@ name = "topiary-cli" description = "CLI app for Topiary, the universal code formatter." categories = ["command-line-utilities", "development-tools", "text-processing"] keywords = [ - "cli", - "code-formatter", - "formatter", - "text", - "tree-sitter", - "utility", + "cli", + "code-formatter", + "formatter", + "text", + "tree-sitter", + "utility", ] version.workspace = true edition.workspace = true @@ -35,7 +35,12 @@ itertools = { workspace = true } log = { workspace = true } serde = { workspace = true, features = ["derive"] } tempfile = { workspace = true } -tokio = { workspace = true, features = ["fs", "rt-multi-thread", "sync", "macros"] } +tokio = { workspace = true, features = [ + "fs", + "rt-multi-thread", + "sync", + "macros", +] } toml = { workspace = true } topiary-core = { path = "../topiary-core" } topiary-config = { path = "../topiary-config" } @@ -58,34 +63,37 @@ predicates = { workspace = true } [features] default = [ - "contributed", - "json", - "nickel", - "ocaml", - "ocaml_interface", - "ocamllex", - "toml", - "tree_sitter_query" + "contributed", + "json", + "nickel", + "nix", + "ocaml", + "ocaml_interface", + "ocamllex", + "toml", + "tree_sitter_query", ] # Included by default -contributed = [ - "css" -] +contributed = ["css"] # Excluded by default -experimental = [ - "bash", - "rust", -] +experimental = ["bash", "rust"] +nix = ["topiary-config/nix", "topiary-queries/nix"] bash = ["topiary-config/bash", "topiary-queries/bash"] css = ["topiary-config/css", "topiary-queries/css"] json = ["topiary-config/json", "topiary-queries/json"] nickel = ["topiary-config/nickel", "topiary-queries/nickel"] ocaml = ["topiary-config/ocaml", "topiary-queries/ocaml"] -ocaml_interface = ["topiary-config/ocaml_interface", "topiary-queries/ocaml_interface"] +ocaml_interface = [ + "topiary-config/ocaml_interface", + "topiary-queries/ocaml_interface", +] ocamllex = ["topiary-config/ocamllex", "topiary-queries/ocamllex"] rust = ["topiary-config/rust", "topiary-queries/rust"] toml = ["topiary-config/toml", "topiary-queries/toml"] -tree_sitter_query = ["topiary-config/tree_sitter_query", "topiary-queries/tree_sitter_query"] +tree_sitter_query = [ + "topiary-config/tree_sitter_query", + "topiary-queries/tree_sitter_query", +] diff --git a/topiary-cli/src/io.rs b/topiary-cli/src/io.rs index 0c3a7753..648fd59f 100644 --- a/topiary-cli/src/io.rs +++ b/topiary-cli/src/io.rs @@ -350,6 +350,9 @@ where #[cfg(feature = "toml")] "toml" => Ok(topiary_queries::toml().into()), + #[cfg(feature = "nix")] + "nix" => Ok(topiary_queries::nix().into()), + #[cfg(feature = "tree_sitter_query")] "tree_sitter_query" => Ok(topiary_queries::tree_sitter_query().into()), diff --git a/topiary-config/Cargo.toml b/topiary-config/Cargo.toml index 36103a3d..db094589 100644 --- a/topiary-config/Cargo.toml +++ b/topiary-config/Cargo.toml @@ -33,11 +33,11 @@ git2.workspace = true libloading.workspace = true [features] -default = [ "parallel" ] +default = ["parallel"] # Enabling the `parallel` feature enables parallel computation where possible. # At the moment, this is only in grammar prefetching -parallel = [ "dep:rayon" ] +parallel = ["dep:rayon"] bash = [] css = [] @@ -46,6 +46,7 @@ nickel = [] ocaml = [] ocaml_interface = [] ocamllex = [] +nix = [] rust = [] toml = [] tree_sitter_query = [] @@ -53,14 +54,15 @@ tree_sitter_query = [] # This a convenience for the sake of downstream applications which don't # wish to cherry-pick grammars (e.g., the playground) all = [ - "bash", - "css", - "json", - "nickel", - "ocaml", - "ocaml_interface", - "ocamllex", - "rust", - "toml", - "tree_sitter_query" + "bash", + "css", + "json", + "nickel", + "ocaml", + "ocaml_interface", + "ocamllex", + "rust", + "nix", + "toml", + "tree_sitter_query", ] diff --git a/topiary-config/languages.ncl b/topiary-config/languages.ncl index 2d44d9a8..7ec0aea7 100644 --- a/topiary-config/languages.ncl +++ b/topiary-config/languages.ncl @@ -16,6 +16,14 @@ }, }, + nix = { + extensions = ["nix"], + grammar = { + git = "https://github.com/nix-community/tree-sitter-nix", + rev = "456b14a2fa6315abc7e02fcffaf4a1f35d4955d3", + }, + }, + json = { extensions = [ "json", diff --git a/topiary-queries/Cargo.toml b/topiary-queries/Cargo.toml index 45969a10..7db41c93 100644 --- a/topiary-queries/Cargo.toml +++ b/topiary-queries/Cargo.toml @@ -24,4 +24,5 @@ ocaml_interface = [] ocamllex = [] rust = [] toml = [] +nix = [] tree_sitter_query = [] diff --git a/topiary-queries/queries/nix.scm b/topiary-queries/queries/nix.scm new file mode 100644 index 00000000..3977c76b --- /dev/null +++ b/topiary-queries/queries/nix.scm @@ -0,0 +1,211 @@ +;; Nix Formatter based on RFC 166 + +; Leaf nodes +[ + (string_expression) + (integer_expression) + (float_expression) + (uri_expression) + (path_expression) + (hpath_expression) + (spath_expression) +] @leaf + +; Comments +(comment) @comment + +; Convert single-line /* */ comments to # +(comment) @convert_to_hash_comment + +; Preserve multiline comments +(comment) @preserve_multiline_comment + +; Append space after specific tokens +":" @append_space +"=" @prepend_space @append_space +"," @append_spaced_softline + +; Handle indentation and line breaks +; Define softline breaks and indentation starts/ends +"{" @append_spaced_softline @append_indent_start +"}" @prepend_spaced_softline @prepend_indent_end +"[" @append_spaced_softline @append_indent_start +"]" @prepend_spaced_softline @prepend_indent_end + +; Functions + +(function_expression + formals: (formals + "{" @append_spaced_softline @append_indent_start + (formal) @formal_param + "}" @prepend_spaced_softline @prepend_indent_end + ) + body: (_) @function_body +) @function_declaration + +; Ensure each parameter is on a new line with proper indentation +(formal) @prepend_spaced_softline + +; Attribute Sets + +(attrset_expression + "{" @append_spaced_softline @append_indent_start + (binding_set + (binding) @attr_binding + ) + "}" @prepend_spaced_softline @prepend_indent_end +) @attribute_set + +; Ensure each key-value pair is on its own line +(binding + attrpath: (_) @attr_key + "=" @append_space + (_) @attr_value + ";" @append_spaced_softline +) + +; Lists + +(list_expression + "[" @append_spaced_softline @append_indent_start + (_) @list_item + "]" @prepend_spaced_softline @prepend_indent_end +) @list + +; If-Then-Else Expressions + +(if_expression + "if" @keyword_if @append_space @prepend_spaced_softline + condition: (_) @if_condition + "then" @keyword_then @append_space @prepend_spaced_softline @append_indent_start + ;body: (_) @if_body @append_indent_end + "else" @keyword_else @append_space @prepend_spaced_softline @append_indent_start + alternative: (_) @else_body @append_indent_end +) + +; Let-In Expressions + +(let_expression + "let" @keyword_let @append_spaced_softline @append_indent_start + (binding_set + (binding + attrpath: (_) @let_binding_name + "=" @append_space + (_) @let_binding_value + ";" @append_spaced_softline + )+ + ) + "in" @keyword_in @prepend_spaced_softline @prepend_indent_end @append_spaced_softline + body: (_) @let_body +) + +; With Expressions + +(with_expression + "with" @keyword_with @append_space + (_) @with_expression + ";" @append_spaced_softline + body: (_) @with_body +) + +; Assert Expressions + +(assert_expression + "assert" @keyword_assert @append_space + (_) @assert_condition + ";" @append_spaced_softline + body: (_) @assert_body +) + +; Operator Formatting + +(binary_expression + left: (_) @op_left + right: (_) @op_right +) + +; Ensure operators start on new lines if they don't fit on a single line +(binary_expression + (_) @prepend_spaced_softline +) + +; Inherit Statements + +(inherit + "inherit" @keyword_inherit @append_space + (inherited_attrs + (identifier) @inherit_item + )+ + ";" @inherit_semicolon +) + +; Handle inherit with source +(inherit_from + "inherit" @keyword_inherit @append_space + "(" @inherit_parentheses_start + (_) @inherit_source + ")" @inherit_parentheses_end + (inherited_attrs + (identifier) @inherit_item + )+ + ";" @inherit_semicolon +) + +; Empty Objects and Arrays + +(attrset_expression + "{" @object_open + "}" @object_close +) @empty_attribute_set + +(list_expression + "[" @array_open + "]" @array_close +) @empty_array + +; String Interpolation + +(interpolation + "${" @interp_start + (_) @interp_content + "}" @interp_end +) + +; Additional Rules + +; Handle let bindings indentation +(let_expression + (binding_set + (binding) @let_binding_indent + ) +) + +; Handle assertions +(assert_expression + "assert" @keyword_assert @append_space + (_) @assert_condition + ";" @assert_semicolon +) + +; Handle comments within expressions +(comment) @comment_within_expr + +; Leaf Nodes Preservation +(_) @leaf + +; Handle semicolon placement in bindings +(binding + ";" @semicolon_end +) + +; Handle indentation for nested attribute sets +(attrset_expression + (binding_set + (binding + (attrset_expression) @nested_attribute_set + ) + ) +) + +; Handle newline preservation rules +(ERROR) @collapse_empty_lines diff --git a/topiary-queries/src/lib.rs b/topiary-queries/src/lib.rs index 20c886e6..463c7f48 100644 --- a/topiary-queries/src/lib.rs +++ b/topiary-queries/src/lib.rs @@ -52,6 +52,12 @@ pub fn toml() -> &'static str { include_str!("../queries/toml.scm") } +/// Returns the Topiary-compatible query file for Nix. +#[cfg(feature = "nix")] +pub fn nix() -> &'static str { + include_str!("../queries/nix.scm") +} + /// Returns the Topiary-compatible query file for the /// Tree-sitter query language. #[cfg(feature = "tree_sitter_query")]