diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 22e3e09d..9f587a4f 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -457,7 +457,7 @@ jobs:
path: dist/plugins
retention-days: "7"
build-plugins-maple:
- name: "Plugins (maple): caddy, cmake, dockerfile, dot, graphql, hcl, ini, jq, meson, nginx, ninja, nix, query, rego, ron, sql, ssh-config, styx, toml, yaml"
+ name: "Plugins (maple): caddy, cmake, dockerfile, dot, gitattributes, graphql, hcl, ini, jq, meson, nginx, ninja, nix, query, rego, ron, sql, ssh-config, styx, toml, yaml"
runs-on: depot-ubuntu-24.04-32
container: "ghcr.io/bearcove/arborium-plugin-builder:latest"
needs:
@@ -475,10 +475,10 @@ jobs:
set -e
tar -xf generate-output.tar && rm generate-output.tar
shell: bash
- - name: Build caddy, cmake, dockerfile, dot, graphql, hcl, ini, jq, meson, nginx, ninja, nix, query, rego, ron, sql, ssh-config, styx, toml, yaml
+ - name: Build caddy, cmake, dockerfile, dot, gitattributes, graphql, hcl, ini, jq, meson, nginx, ninja, nix, query, rego, ron, sql, ssh-config, styx, toml, yaml
run: |-
set -e
- ./xtask/target/release/xtask build caddy cmake dockerfile dot graphql hcl ini jq meson nginx ninja nix query rego ron sql ssh-config styx toml yaml -o dist/plugins
+ ./xtask/target/release/xtask build caddy cmake dockerfile dot gitattributes graphql hcl ini jq meson nginx ninja nix query rego ron sql ssh-config styx toml yaml -o dist/plugins
shell: bash
- name: Upload plugins artifact
uses: actions/upload-artifact@v4
diff --git a/langs/group-maple/gitattributes/def/arborium.yaml b/langs/group-maple/gitattributes/def/arborium.yaml
new file mode 100644
index 00000000..fcb5acb9
--- /dev/null
+++ b/langs/group-maple/gitattributes/def/arborium.yaml
@@ -0,0 +1,20 @@
+repo: https://github.com/tree-sitter-grammars/tree-sitter-gitattributes
+commit: 1b7af09d45b579f9f288453b95ad555f1f431645
+license: MIT
+
+grammars:
+ - id: gitattributes
+ name: .gitattributes
+ tag: config
+ tier: 2
+ has_scanner: false
+
+ inventor: Linus Torvalds
+ year: 2005
+ description: Defining attributes per path for Git.
+ link: https://git-scm.com/docs/gitattributes
+
+ samples:
+ - path: samples/.gitattributes
+ link: https://github.com/gitattributes/gitattributes/blob/1dcb1ff5890b3997642b295fee6934204b4f79ac/.gitattributes
+ license: MIT
diff --git a/langs/group-maple/gitattributes/def/grammar/grammar.js b/langs/group-maple/gitattributes/def/grammar/grammar.js
new file mode 100644
index 00000000..90391664
--- /dev/null
+++ b/langs/group-maple/gitattributes/def/grammar/grammar.js
@@ -0,0 +1,220 @@
+/**
+ * @file gitattributes grammar for tree-sitter
+ * @author ObserverOfTime
+ * @license MIT
+ */
+
+///
+// @ts-check
+
+/** Built-in attribute names */
+const BUILTIN_ATTRIBUTES = [
+ 'text',
+ 'eol',
+ 'crlf',
+ 'working-tree-encoding',
+ 'ident',
+ 'filter',
+ 'diff',
+ 'merge',
+ 'whitespace',
+ 'export-ignore',
+ 'export-subst',
+ 'delta',
+ 'encoding',
+ 'binary'
+];
+
+/** POSIX character classes */
+const POSIX_CLASSES = [
+ 'alnum',
+ 'alpha',
+ 'blank',
+ 'cntrl',
+ 'digit',
+ 'graph',
+ 'lower',
+ 'print',
+ 'punct',
+ 'space',
+ 'upper',
+ 'xdigit'
+];
+
+/**
+ * Returns a quoted or unquoted pattern sequence
+ * @param {GrammarSymbols} $ the symbols of the grammar
+ * @param {boolean} quoted `true` if the pattern is in quotes
+ */
+const __pattern = ($, quoted) => [
+ optional(alias('!', $.pattern_negation)), // error
+ optional(field('absolute', $.dir_sep)),
+ quoted ? $._quoted_pattern : $._pattern,
+ repeat(seq(field('relative', $.dir_sep), $._pattern)),
+ optional(alias($.dir_sep, $.trailing_slash)) // error
+]
+
+module.exports = grammar({
+ name: 'gitattributes',
+
+ extras: _ => [],
+
+ word: $ => $.attr_name,
+
+ rules: {
+ file: $ => repeat($._line),
+
+ _line: $ => seq(
+ optional($._space),
+ optional(choice(
+ $.comment,
+ $._attr_list,
+ $.macro_def
+ )),
+ choice($._eol, $._eof)
+ ),
+
+ _attr_list: $ => seq(
+ prec.left(choice($.pattern, $.quoted_pattern)),
+ repeat1(seq($._space, $.attribute)),
+ optional($._space)
+ ),
+
+ pattern: $ => seq(...__pattern($, false)),
+
+ _pattern: $ => repeat1(choice(
+ $._pattern_char,
+ $.wildcard,
+ $.escaped_char,
+ $.range_notation,
+ alias('\\', $.redundant_escape) // error
+ )),
+
+ quoted_pattern: $ => seq(
+ '"', ...__pattern($, true), '"'
+ ),
+
+ _quoted_pattern: $ => repeat1(choice(
+ /[^\n/]/,
+ choice($.ansi_c_escape, $.escaped_char),
+ alias('\\', $.redundant_escape),
+ )),
+
+ _pattern_char: _ => /[^\s/?*]/,
+
+ escaped_char: _ => /\\[\\\[\]!?*]/,
+
+ ansi_c_escape: $ => prec.right(
+ 1, choice(
+ $._special_char, $._char_code
+ )
+ ),
+
+ _special_char: _ => /\\[abeEfnrtv\\'"?]/,
+
+ _char_code: $ => choice(
+ $._octal_code,
+ $._hex_code,
+ $._unicode_code,
+ $._control_code
+ ),
+
+ _octal_code: _ => /\\\d{1,3}/,
+
+ _hex_code: _ => /\\x[0-9A-Fa-f]{2}/,
+
+ _unicode_code: _ => choice(
+ /\\u[0-9A-Fa-f]{4}/,
+ /\\U[0-9A-Fa-f]{8}/
+ ),
+
+ _control_code: _ => token(choice(
+ /\\c[\x00-\x5B\x5D-\x7F]/, /\\c\\\\/
+ )),
+
+ range_notation: $ => prec.left(seq(
+ '[',
+ optional(alias(
+ token(choice('!', '^')),
+ $.range_negation
+ )),
+ repeat1(choice(
+ $.class_range,
+ $.character_class,
+ $._class_char,
+ $.ansi_c_escape,
+ '-'
+ )),
+ ']'
+ )),
+
+ class_range: $ => prec.right(
+ 2, seq(
+ choice($._class_char, $._char_code),
+ '-',
+ choice($._class_char, $._char_code)
+ )
+ ),
+
+ _class_char: _ => token(choice(
+ /[^-\\\]\n]/, /\\[-\\\[\]!^]/
+ )),
+
+ character_class: _ => token(seq(
+ '[:', choice(...POSIX_CLASSES), ':]'
+ )),
+
+ wildcard: _ => token(choice('?', '*', '**')),
+
+ dir_sep: _ => '/',
+
+ attribute: $ => choice(
+ seq(
+ choice($.attr_name, $.builtin_attr),
+ $._attr_value
+ ),
+ $._prefixed_attr
+ ),
+
+ _prefixed_attr: $ => seq(
+ optional(choice(
+ alias('!', $.attr_reset),
+ alias('-', $.attr_unset),
+ )),
+ choice($.attr_name, $.builtin_attr),
+ prec(-1, optional(
+ alias($._attr_value, $.ignored_value)
+ ))
+ ),
+
+ _attr_value: $ => seq(
+ alias('=', $.attr_set),
+ choice(
+ prec(2, alias(
+ token(choice('true', 'false')),
+ $.boolean_value
+ )),
+ prec(1, alias(/\S+/, $.string_value))
+ )
+ ),
+
+ attr_name: _ => /[A-Za-z0-9_.][-A-Za-z0-9_.]*/,
+
+ builtin_attr: _ => prec(1, choice(...BUILTIN_ATTRIBUTES)),
+
+ macro_def: $ => seq(
+ alias('[attr]', $.macro_tag),
+ field('macro_name', $.attr_name),
+ repeat1(seq($._space, $.attribute)),
+ optional($._space)
+ ),
+
+ comment: _ => seq('#', repeat(/[^\n]/)),
+
+ _space: _ => prec(-1, /[ \t]+/),
+
+ _eol: _ => /\r?\n/,
+
+ _eof: _ => '\0'
+ }
+});
diff --git a/langs/group-maple/gitattributes/def/queries/highlights.scm b/langs/group-maple/gitattributes/def/queries/highlights.scm
new file mode 100644
index 00000000..f9d2a5a9
--- /dev/null
+++ b/langs/group-maple/gitattributes/def/queries/highlights.scm
@@ -0,0 +1,52 @@
+(dir_sep) @punctuation.delimiter
+
+(quoted_pattern
+ "\"" @punctuation.special)
+
+(range_notation) @string.special
+
+(range_notation
+ [ "[" "]" ] @punctuation.bracket)
+
+(wildcard) @string.regexp
+
+(range_negation) @operator
+
+(character_class) @constant
+
+(class_range "-" @operator)
+
+[
+ (ansi_c_escape)
+ (escaped_char)
+] @escape
+
+(attribute
+ (attr_name) @variable.parameter)
+
+(attribute
+ (builtin_attr) @variable.builtin)
+
+[
+ (attr_reset)
+ (attr_unset)
+ (attr_set)
+] @operator
+
+(boolean_value) @boolean
+
+(string_value) @string
+
+(macro_tag) @keyword
+
+(macro_def
+ macro_name: (_) @property)
+
+[
+ (pattern_negation)
+ (redundant_escape)
+ (trailing_slash)
+ (ignored_value)
+] @error
+
+(comment) @comment
diff --git a/langs/group-maple/gitattributes/def/samples/.gitattributes b/langs/group-maple/gitattributes/def/samples/.gitattributes
new file mode 100644
index 00000000..605e2505
--- /dev/null
+++ b/langs/group-maple/gitattributes/def/samples/.gitattributes
@@ -0,0 +1,25 @@
+# Handle line endings automatically for files detected as text
+# and leave all files detected as binary untouched.
+* text=auto
+
+#
+# The above will handle all files NOT found below
+#
+# These files are text and should be normalized (Convert crlf => lf)
+*.gitattributes text
+.gitignore text
+*.md text diff=markdown
+
+#
+# Exclude files from exporting
+#
+
+.gitattributes export-ignore
+.gitignore export-ignore
+
+#
+# Enable syntax highlighting for files with `.gitattributes` extensions.
+#
+*.gitattributes linguist-language=gitattributes
+*.gitattributes linguist-detectable=true
+*.gitattributes linguist-documentation=false