diff options
| author | Nathan Reiner <nathan@nathanreiner.xyz> | 2026-04-20 13:43:01 +0200 |
|---|---|---|
| committer | Nathan Reiner <nathan@nathanreiner.xyz> | 2026-04-20 13:43:01 +0200 |
| commit | 0686b40f979f4607b3fd8cca21c463e98f617666 (patch) | |
| tree | 355e8a7bfd4b37510d05370876a9d8cedfdcd5d7 /src/lib | |
| parent | ba561ebd063b391013b6c9c1fcc9b1838dd422e6 (diff) | |
implement builder and instance
Diffstat (limited to 'src/lib')
| -rw-r--r-- | src/lib/builder.lua | 111 | ||||
| -rw-r--r-- | src/lib/env.lua | 27 | ||||
| -rw-r--r-- | src/lib/init.lua | 4 | ||||
| -rw-r--r-- | src/lib/instance.lua | 54 | ||||
| -rw-r--r-- | src/lib/path.lua | 112 | ||||
| -rw-r--r-- | src/lib/template.lua | 51 |
6 files changed, 350 insertions, 9 deletions
diff --git a/src/lib/builder.lua b/src/lib/builder.lua new file mode 100644 index 0000000..7390257 --- /dev/null +++ b/src/lib/builder.lua @@ -0,0 +1,111 @@ +Builder = {} + +function Builder:from_template(path) + local builder = { + template = loadfile( + path, + nil, + Env.Sandbox { + Template = Template + } + )(), + source = Path:new(path), + } + + builder.source_directory = builder.source:parent() + + setmetatable(builder, self) + self.__index = self + + local argument_template = { + strict = true, + { name = "target" }, + { name = "template", description = "some description", kind = "property", required = true }, + }; + + for key, value in pairs(builder.template.options) do + argument_template[#argument_template + 1] = value + end + + local args = require('arg')(argument_template) + + for key, _ in pairs(builder.template.options) do + builder.template.arguments[key] = args[key] + end + + return builder +end + +function Builder:entries() + local entries = {} + + for _, value in ipairs(self.template.files) do + entries[#entries + 1] = { + path = Path:new(value.path), + env = value.env, + } + end + + for _, value in ipairs(self.template.directories) do + for entry in Path:new(value.path):children() do + entries[#entries + 1] = { + path = entry, + env = value.env, + } + end + end + + local i = 1 + return function() + local value = entries[i] + i = i + 1 + return value + end +end + +function Builder:build(args) + local instance = Instance:new() + + for entry in self:entries() do + instance:register( + entry.path, + entry.path.is_directory and nil or self:process_file(entry) + ) + end + + return instance +end + +function Builder:process_file(file) + local f = (self.source_directory / file.path):open("r") + local raw = f:read("*all") + f:close() + + local content = '' + + for _, split in ipairs(raw:split_into_template_blocks()) do + if split.kind == 'text' then + content = content .. split.content + else + local func, err = load( + 'return ' .. split.content, + 'macro ' .. tostring(file.path), + 't', + Env.Sandbox(file.env) + ) + + if err then + error(err) + end + + local value = func() + if type(value) == 'function' then + content = content .. tostring(value()) + else + content = content .. tostring(value) + end + end + end + + return content +end diff --git a/src/lib/env.lua b/src/lib/env.lua new file mode 100644 index 0000000..fd55444 --- /dev/null +++ b/src/lib/env.lua @@ -0,0 +1,27 @@ +Env = {} + +function Env.Sandbox(opts) + local env = { + string = string, + table = table, + type = type, + utf8 = utf8, + pairs = pairs, + ipairs = ipairs, + select = select, + tonumber = tonumber, + tostring = tostring, + next = next, + math = math, + error = error, + getmetatable = getmetatable, + } + + opts = opts or {} + + for key, value in pairs(opts) do + env[key] = value + end + + return env +end diff --git a/src/lib/init.lua b/src/lib/init.lua index 4f069d0..2db3367 100644 --- a/src/lib/init.lua +++ b/src/lib/init.lua @@ -1 +1,5 @@ require('lib.path') +require('lib.env') +require('lib.template') +require('lib.builder') +require('lib.instance') diff --git a/src/lib/instance.lua b/src/lib/instance.lua new file mode 100644 index 0000000..2de2c5f --- /dev/null +++ b/src/lib/instance.lua @@ -0,0 +1,54 @@ +Instance = {} + +function Instance:new() + local instance = { + entries = {}, + } + + setmetatable(instance, self) + self.__index = self + + return instance +end + +function Instance:register(path, content) + self.entries[#self.entries + 1] = { + path = path, + content = content, + } +end + +function Instance:save_to(target) + for _, entry in ipairs(self.entries) do + local target_entry = Path:new(target) / entry.path + + if entry.content == nil then + target_entry:make_directory { create_parents = true } + else + target_entry:parent():make_directory { create_parents = true } + local file = target_entry:open('w') + file:write(entry.content) + file:close() + end + end +end + +function Instance:__tostring() + local output = [[{ + entries = { +]] + + for _, entry in ipairs(self.entries) do + output = output .. [[ { + path = ]] .. tostring(entry.path) .. [[, + content = ]] .. (entry.content and ("[[\n " .. entry.content:gsub('\n', '\n ') .. "\n ]]") or "nil") .. [[, + }, +]] + end + + output = output .. [[ } +} +]] + + return output +end diff --git a/src/lib/path.lua b/src/lib/path.lua index b0f0fac..f99585b 100644 --- a/src/lib/path.lua +++ b/src/lib/path.lua @@ -1,7 +1,18 @@ Path = {} function Path:new(p) - local path = { segments = p:split('/') } + local is_absolute = p:sub(1, 1) == "/" + p = p:gsub("/+", "/") + p = p:gsub("(/%./", "/") + p = p:gsub("/%.$", "") + p = p:gsub("/$", "") + p = p:gsub("^%./", "") + p = p:gsub("/[^/]+/%.%.", "") + + local path = { + segments = p:split('/'), + is_absolute = is_absolute + } setmetatable(path, self) self.__index = self @@ -18,20 +29,87 @@ function Path:stem() end function Path:parent() - local path = { segments = { table.unpack(self.segments, 1, #self.segments - 1) } } - - setmetatable(path, self) - self.__index = self - - return path + return self / ".." end function Path:__tostring() - return table.concat(self.segments, "/") + return (self.is_absolute and "/" or "") .. table.concat(self.segments, "/") end function Path:__div(next) - return Path:new(self:__tostring() .. "/" .. next) + if next.is_absolute then + error("cannot concat with absolute path") + end + + return Path:new(self:__tostring() .. "/" .. tostring(next)) +end + +function Path:open(mode) + return io.open(tostring(self), mode) +end + +function Path:exists() + local f = self:open('r') + + if f ~= nil then + f:close() + return true + end + return false +end + +function Path:is_directory() + local f = self:open('r') + + if not f then + return false + end + + local value, err, code = f:read() + f:close() + + return code == 21 +end + +function Path:entries() + if not self:is_directory() then + return function() end + end + + local pipe = io.popen('ls' .. ' "' .. tostring(self) .. '"') + local lines = pipe:lines() + + return function() + local value = lines() + if value == nil then + pipe:close() + return nil + end + return self / value + end +end + +function Path:children() + if not self:is_directory() then + return function() end + end + + local pipe = io.popen('find' .. ' "' .. tostring(self) .. '"') + local lines = pipe:lines() + + return function() + local value = lines() + if value == nil then + pipe:close() + return nil + end + return Path:new(value) + end +end + +function Path:make_directory(opts) + local pipe = io.popen('mkdir ' .. (opts.create_parents and '-p ' or ' ') .. tostring(self)) + pipe:close() end return { @@ -56,6 +134,22 @@ return { local path = Path:new("some/path/here") path = path / 'next.txt' assert.equals("some/path/here/next.txt", path:__tostring()) + end, + function() + local path = Path:new("./src") + assert.equals(true, path:is_directory()) + end, + function() + local path = Path:new("./src/blueprint") + assert.equals(false, path:is_directory()) + end, + function() + local path = Path:new("./src/blueprint") + assert.equals(true, path:exists()) + end, + function() + local path = Path:new("./src/blueprint__") + assert.equals(false, path:exists()) end } } diff --git a/src/lib/template.lua b/src/lib/template.lua new file mode 100644 index 0000000..d1f3a94 --- /dev/null +++ b/src/lib/template.lua @@ -0,0 +1,51 @@ +Template = {} + +function Template:new() + local template = { + directories = {}, + files = {}, + options = {}, + arguments = {} + } + + setmetatable(template, self) + self.__index = self + + return template +end + +function Template:set_option(name, description, default, required) + required = required == nil or required + self.options[name] = { + name = name, + default = default, + required = required, + kind = 'property', + description = description, + } + +end + +function Template:option(name) + if not self.options[name] then + self:set_option(name) + end + + return function() + return self.arguments[name] + end +end + +function Template:add_directory(path, env) + self.directories[#self.directories + 1] = { + path = path, + env = env, + } +end + +function Template:add_file(path, env) + self.files[#self.files + 1] = { + path = path, + env = env, + } +end |