function string:starts_with(s) return self:sub(1, #s) == s end function string:flag_name() if self:sub(1, 2) ~= "--" then return nil end return self:sub(3) end function string:split(sep) splits = {} for split in self:gmatch("[^" .. sep .. "]+") do splits[#splits + 1] = split end return splits end -- NOTE: This function is for certain not complete and not very efficient -- but for now it works good enough to proceed with development. -- We might need to rewrite this function in the future if it is a bottleneck. function string:split_into_template_blocks() local splits = { { kind = 'text', content = '' } } local stack = {} local current = self local matching = { ["{"] = "}", ["["] = "]", ["("] = ")", } while #current > 0 do local add = true if #stack == 0 and current:starts_with("@{") then current = current:sub(2, #current) stack[#stack + 1] = "}" splits[#splits + 1] = { kind = 'code', content = '' } add = false elseif #stack > 0 and ( current:starts_with('"') or current:starts_with("'") or current:starts_with('[[') ) then local string_end = current:starts_with('"') and '"' or (current:starts_with("'") and "'" or ']]') splits[#splits].content = splits[#splits].content .. current:sub(1, 1) current = current:sub(2) while #current > 0 do if current:starts_with('\\') then splits[#splits].content = splits[#splits].content .. current:sub(1, 1) current = current:sub(2) elseif current:starts_with(string_end) then break end splits[#splits].content = splits[#splits].content .. current:sub(1, 1) current = current:sub(2) end elseif #stack > 0 and current:starts_with("--") then local comment_end = current:starts_with('--[[') and ']]' or '\n' splits[#splits].content = splits[#splits].content .. current:sub(1, (current:find(comment_end) + #comment_end) or #current + 1) current = current:sub((current:find(comment_end) + #comment_end) or #current + 1) add = false elseif #stack > 0 then local start = current:sub(1, 1) local closing = matching[start] if start == stack[#stack] then stack[#stack] = nil if #stack == 0 then current = current:sub(2, #current) splits[#splits + 1] = { kind = 'text', content = '' } end elseif closing then stack[#stack + 1] = closing end end if add then splits[#splits].content = splits[#splits].content .. current:sub(1, 1) end current = current:sub(2) end return splits end return { tests = { function() local path = 'some/path/here' assert.equals({ 'some', 'path', 'here' }, path:split("/")) end, function() local f = Path:new("example/test.py"):open("r") local raw = f:read("*all") f:close() for _, value in ipairs(raw:split_into_template_blocks()) do print(require('inspect')(value)) end end } }