192 lines
No EOL
4.6 KiB
Lua
192 lines
No EOL
4.6 KiB
Lua
-- YAML header parsing module
|
|
local M = {}
|
|
|
|
local lyaml = require('lyaml')
|
|
local utils = require('notex.utils')
|
|
|
|
-- Extract YAML header from markdown content
|
|
function M.extract_yaml_header(content)
|
|
if not content or content == "" then
|
|
return nil, "Empty content provided"
|
|
end
|
|
|
|
-- Check for YAML header delimiters
|
|
local start_pos = content:find("^%s*%-%-%-%s*\n")
|
|
if not start_pos then
|
|
return nil, "No YAML header found"
|
|
end
|
|
|
|
local end_pos = content:find("\n%s*%-%-%-%s*\n", start_pos + 4)
|
|
if not end_pos then
|
|
return nil, "Unclosed YAML header"
|
|
end
|
|
|
|
-- Extract YAML content
|
|
local yaml_content = content:sub(start_pos + 4, end_pos - 1)
|
|
|
|
return yaml_content, nil
|
|
end
|
|
|
|
-- Parse YAML header content
|
|
function M.parse_yaml(yaml_content)
|
|
if not yaml_content or yaml_content == "" then
|
|
return {}, nil
|
|
end
|
|
|
|
local ok, data = pcall(lyaml.load, yaml_content)
|
|
|
|
if not ok then
|
|
return nil, "YAML parsing failed: " .. tostring(data)
|
|
end
|
|
|
|
if type(data) ~= "table" then
|
|
return {}, nil
|
|
end
|
|
|
|
return data, nil
|
|
end
|
|
|
|
-- Parse markdown file and extract YAML header
|
|
function M.parse_markdown_file(file_path)
|
|
-- Validate file exists
|
|
if not utils.file_exists(file_path) then
|
|
return nil, "File not found: " .. file_path
|
|
end
|
|
|
|
-- Validate UTF-8 encoding
|
|
if not utils.is_utf8(file_path) then
|
|
return nil, "File is not valid UTF-8: " .. file_path
|
|
end
|
|
|
|
-- Read file content
|
|
local content, err = utils.read_file(file_path)
|
|
if not content then
|
|
return nil, err
|
|
end
|
|
|
|
-- Extract YAML header
|
|
local yaml_content, extract_err = M.extract_yaml_header(content)
|
|
if not yaml_content then
|
|
return nil, extract_err
|
|
end
|
|
|
|
-- Parse YAML
|
|
local yaml_data, parse_err = M.parse_yaml(yaml_content)
|
|
if not yaml_data then
|
|
return nil, parse_err
|
|
end
|
|
|
|
return yaml_data, nil
|
|
end
|
|
|
|
-- Flatten YAML data into key-value pairs
|
|
function M.flatten_yaml(data, prefix)
|
|
local flattened = {}
|
|
prefix = prefix or ""
|
|
|
|
for key, value in pairs(data) do
|
|
local full_key = prefix .. (prefix ~= "" and "." or "") .. key
|
|
|
|
if type(value) == "table" then
|
|
-- Recursively flatten nested tables
|
|
local nested = M.flatten_yaml(value, full_key)
|
|
for nested_key, nested_value in pairs(nested) do
|
|
flattened[nested_key] = nested_value
|
|
end
|
|
else
|
|
flattened[full_key] = value
|
|
end
|
|
end
|
|
|
|
return flattened
|
|
end
|
|
|
|
-- Validate YAML structure
|
|
function M.validate_yaml(yaml_data)
|
|
local errors = {}
|
|
|
|
if type(yaml_data) ~= "table" then
|
|
table.insert(errors, "YAML data must be a table")
|
|
return errors
|
|
end
|
|
|
|
-- Check for required fields (if any)
|
|
local required_fields = {} -- Add required fields as needed
|
|
|
|
for _, field in ipairs(required_fields) do
|
|
if yaml_data[field] == nil then
|
|
table.insert(errors, string.format("Required field '%s' is missing", field))
|
|
end
|
|
end
|
|
|
|
-- Validate field types
|
|
local field_types = {
|
|
-- Define expected types for specific fields
|
|
}
|
|
|
|
for field, expected_type in pairs(field_types) do
|
|
if yaml_data[field] ~= nil and type(yaml_data[field]) ~= expected_type then
|
|
table.insert(errors, string.format("Field '%s' should be %s, got %s",
|
|
field, expected_type, type(yaml_data[field])))
|
|
end
|
|
end
|
|
|
|
return errors
|
|
end
|
|
|
|
-- Detect and convert property types
|
|
function M.detect_property_type(value)
|
|
local value_type = type(value)
|
|
|
|
if value_type == "boolean" then
|
|
return "boolean", value
|
|
elseif value_type == "number" then
|
|
return "number", value
|
|
elseif value_type == "string" then
|
|
-- Check for ISO 8601 date format
|
|
if value:match("^%d%d%d%d%-%d%d%-%d%d$") or
|
|
value:match("^%d%d%d%d%-%d%d%-%d%dT%d%d:%d%d:%d%dZ?$") then
|
|
return "date", value
|
|
end
|
|
|
|
-- Check for numeric strings
|
|
local num = tonumber(value)
|
|
if num and value:match("^%-?%d+%.?%d*$") then
|
|
return "number", num
|
|
end
|
|
|
|
-- Check for boolean strings
|
|
local lower = value:lower()
|
|
if lower == "true" then
|
|
return "boolean", true
|
|
elseif lower == "false" then
|
|
return "boolean", false
|
|
end
|
|
|
|
return "string", value
|
|
elseif value_type == "table" then
|
|
return "array", vim.json.encode(value)
|
|
else
|
|
return "string", tostring(value)
|
|
end
|
|
end
|
|
|
|
-- Process YAML data into property format
|
|
function M.process_properties(yaml_data)
|
|
local flattened = M.flatten_yaml(yaml_data)
|
|
local properties = {}
|
|
|
|
for key, value in pairs(flattened) do
|
|
local prop_type, processed_value = M.detect_property_type(value)
|
|
|
|
table.insert(properties, {
|
|
key = key,
|
|
value = processed_value,
|
|
value_type = prop_type
|
|
})
|
|
end
|
|
|
|
return properties
|
|
end
|
|
|
|
return M |