From 8251c37a96eea1f85d0d9960908109318e0bb1fb Mon Sep 17 00:00:00 2001 From: Matthias Eck Date: Thu, 18 Sep 2025 21:08:49 +0200 Subject: [PATCH 1/3] refactor: replace lyaml with a built-in lightweight YAML parser --- README.md | 3 - docs/project-description.md | 4 +- lua/codecompanion/filewise/frontmatter.lua | 4 +- lua/codecompanion/filewise/yaml_parser.lua | 123 +++++++++++++++++++++ 4 files changed, 127 insertions(+), 7 deletions(-) create mode 100644 lua/codecompanion/filewise/yaml_parser.lua diff --git a/README.md b/README.md index 7f68591..76e764a 100644 --- a/README.md +++ b/README.md @@ -37,9 +37,6 @@ ### Installation -> [!IMPORTANT] -> This plugin requires the Lua [lyaml](https://luarocks.org/modules/gvvaughan/lyaml) library for YAML parsing. - Use your favorite Neovim plugin manager.
diff --git a/docs/project-description.md b/docs/project-description.md index 54166d2..90d7f67 100644 --- a/docs/project-description.md +++ b/docs/project-description.md @@ -24,7 +24,7 @@ Each extension is independently configurable and can be enabled or disabled as n - The extension can be enabled or disabled via the `enabled` config field. - **Frontmatter Parsing:** - - The extension parses YAML frontmatter from markdown files using a Lua YAML parser (`lyaml` or `yaml.nvim`). + - The extension parses YAML frontmatter from markdown files using a built-in lightweight YAML parser. - **Slash Command Patching:** - The `/buffer` slash command is patched so that after a buffer is added, the extension injects the appropriate instruction files. @@ -36,7 +36,7 @@ Each extension is independently configurable and can be enabled or disabled as n ## Technical Notes - The extension caches the mapping between globs and custom instruction files in memory for performance. -- If no YAML parser is available, the extension notifies the user and skips conditional custom instructions. +- The extension includes a lightweight YAML parser optimized for frontmatter parsing. - The extension is designed to be robust and extensible, following CodeCompanion's extension guidelines. ## Goals diff --git a/lua/codecompanion/filewise/frontmatter.lua b/lua/codecompanion/filewise/frontmatter.lua index 7f15323..d9c63bb 100644 --- a/lua/codecompanion/filewise/frontmatter.lua +++ b/lua/codecompanion/filewise/frontmatter.lua @@ -2,7 +2,7 @@ -- filewise/frontmatter – Utilities for parsing YAML frontmatter from markdown --============================================================================= -local lyaml = require 'lyaml' +local yaml_parser = require 'codecompanion.filewise.yaml_parser' local M = {} @@ -31,7 +31,7 @@ function M.parse(path, rest) if rest then table.insert(body, l) else break end end end - local ok, fm = pcall(lyaml.load, table.concat(frontmatter, '\n')) + local ok, fm = pcall(yaml_parser.parse, table.concat(frontmatter, '\n')) if ok and type(fm) == 'table' then return fm, body end diff --git a/lua/codecompanion/filewise/yaml_parser.lua b/lua/codecompanion/filewise/yaml_parser.lua new file mode 100644 index 0000000..31e3141 --- /dev/null +++ b/lua/codecompanion/filewise/yaml_parser.lua @@ -0,0 +1,123 @@ +--============================================================================= +-- filewise/yaml_parser – Simple YAML parser for frontmatter +--============================================================================= + +local M = {} + +local function trim(s) + if not s then return s end + return s:match('^%s*(.-)%s*$') +end + +local function unquote(s) + if not s then return s end + return s:match('^"(.-)"$') or s:match("^'(.-)'$") or s +end + +--- Simple YAML parser for basic key-value frontmatter +--- Supports: +--- - simple key: value pairs +--- - inline lists: key: [a, b, c] +--- - dash lists: +--- key: +--- - a +--- - b +--- - block scalars using | or > +--- @param yaml_text string The YAML content to parse +--- @return table|nil Parsed YAML as a table, or nil if invalid +function M.parse(yaml_text) + if not yaml_text or yaml_text == "" then + return nil + end + + local result = {} + local lines = {} + for l in yaml_text:gmatch('[^\r\n]+') do + table.insert(lines, l) + end + + local i = 1 + while i <= #lines do + local raw = lines[i] + local line = trim(raw) + -- skip empty and comment lines + if line == '' or line:match('^#') then + i = i + 1 + else + local key, value = line:match('^([%w_%-]+)%s*:%s*(.*)$') + if key then + -- empty value -> might be a dash list or block following + if value == '' then + -- check for dash list on following lines + local items = {} + local j = i + 1 + while j <= #lines do + local nxt = lines[j] + if nxt:match('^%s*%-+%s+') then + local item = nxt:match('^%s*%-+%s+(.*)$') + item = trim(item) + item = unquote(item) + table.insert(items, item) + j = j + 1 + elseif nxt:match('^%s*$') then + j = j + 1 + else + break + end + end + if #items > 0 then + result[key] = items + i = j + else + -- nothing special; store empty string + result[key] = '' + i = i + 1 + end + else + -- inline list? e.g. [a, b, c] + local inline = value:match('^%[(.*)%]$') + if inline then + local items = {} + for part in inline:gmatch('[^,]+') do + local v = trim(part) + v = unquote(v) + table.insert(items, v) + end + result[key] = items + i = i + 1 + elseif value == '|' or value == '>' then + -- block scalar: collect following indented lines (start with whitespace) + local j = i + 1 + local buf = {} + while j <= #lines do + local nxt = lines[j] + if nxt:match('^%s+') then + table.insert(buf, trim(nxt)) + j = j + 1 + else + break + end + end + result[key] = table.concat(buf, '\n') + i = j + else + local v = trim(value) + v = unquote(v) + result[key] = v + i = i + 1 + end + end + else + -- not a key line; ignore + i = i + 1 + end + end + end + + if next(result) == nil then + return nil + end + return result +end + +return M \ No newline at end of file From 3af96252572221814e7ca9dfa6c60954cfbbfac0 Mon Sep 17 00:00:00 2001 From: Matthias Eck Date: Thu, 18 Sep 2025 21:20:14 +0200 Subject: [PATCH 2/3] refactor(yaml_parser): enhance string manipulation functions and improve documentation --- lua/codecompanion/filewise/yaml_parser.lua | 30 +++++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/lua/codecompanion/filewise/yaml_parser.lua b/lua/codecompanion/filewise/yaml_parser.lua index 31e3141..2859c25 100644 --- a/lua/codecompanion/filewise/yaml_parser.lua +++ b/lua/codecompanion/filewise/yaml_parser.lua @@ -4,16 +4,29 @@ local M = {} +--- Trim leading and trailing whitespace from a string. +---@param s string|nil +---@return string|nil local function trim(s) if not s then return s end return s:match('^%s*(.-)%s*$') end +--- Remove a single layer of surrounding quotes from a string if present. +---@param s string|nil +---@return string|nil local function unquote(s) if not s then return s end return s:match('^"(.-)"$') or s:match("^'(.-)'$") or s end +--- Strip only the leading indentation from a string (keep trailing spaces intact). +---@param s string +---@return string +local function ltrim(s) + return (s:gsub('^%s+', '')) +end + --- Simple YAML parser for basic key-value frontmatter --- Supports: --- - simple key: value pairs @@ -23,8 +36,12 @@ end --- - a --- - b --- - block scalars using | or > ---- @param yaml_text string The YAML content to parse ---- @return table|nil Parsed YAML as a table, or nil if invalid +--- Notes/limitations (by design for frontmatter use-cases): +--- - keys are limited to [A-Za-z0-9_-] +--- - inline lists don't support commas inside quoted strings +--- - scalar values are returned as strings (no auto-typing) +---@param yaml_text string|nil The YAML content to parse +---@return table|nil Parsed YAML as a table, or nil if invalid/empty function M.parse(yaml_text) if not yaml_text or yaml_text == "" then return nil @@ -91,8 +108,13 @@ function M.parse(yaml_text) local buf = {} while j <= #lines do local nxt = lines[j] - if nxt:match('^%s+') then - table.insert(buf, trim(nxt)) + if nxt:match('^%s+$') then + -- preserve blank line inside block scalar + table.insert(buf, '') + j = j + 1 + elseif nxt:match('^%s+') then + -- strip only the leading indentation for readability + table.insert(buf, ltrim(nxt)) j = j + 1 else break From 17b086d07438e08cae0eea91da92b6999dd0acf8 Mon Sep 17 00:00:00 2001 From: Eckii24 <35373554+Eckii24@users.noreply.github.com> Date: Thu, 18 Sep 2025 22:10:14 +0200 Subject: [PATCH 3/3] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- lua/codecompanion/filewise/yaml_parser.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lua/codecompanion/filewise/yaml_parser.lua b/lua/codecompanion/filewise/yaml_parser.lua index 2859c25..7ef0795 100644 --- a/lua/codecompanion/filewise/yaml_parser.lua +++ b/lua/codecompanion/filewise/yaml_parser.lua @@ -70,8 +70,8 @@ function M.parse(yaml_text) local j = i + 1 while j <= #lines do local nxt = lines[j] - if nxt:match('^%s*%-+%s+') then - local item = nxt:match('^%s*%-+%s+(.*)$') + if nxt:match('^%s*%-%s+') then + local item = nxt:match('^%s*%-%s+(.*)$') item = trim(item) item = unquote(item) table.insert(items, item)