32 Commits

Author SHA1 Message Date
murat 3f2380e6a2 initial readme 2026-06-18 10:24:46 +03:00
murat 28a973991f BREAKING CHANGES(sitecontents and title): pageContent and pageTitle shortcodes 2026-06-18 10:02:39 +03:00
murat 9ae0960750 feat(shortcodes): list.item.<option> shortcodes 2026-06-18 09:40:10 +03:00
murat a98460360c fix(public directory): not deleting old files
fixed by deleting all html files in the public directory
2026-06-17 17:15:35 +03:00
murat 5fbe2da916 feat: use the html file named same as the md file for template if exists
known bug: /public/ directory doesn't cleaned before recreating html
files
2026-06-17 17:05:26 +03:00
murat e74d70af1b feat(options): draft control 2026-06-16 21:29:25 +03:00
murat fcc6ef114a chore: remove unused variables 2026-06-16 21:05:21 +03:00
murat 87bbceb647 chore: update <br> cleanup 2026-06-16 20:55:46 +03:00
murat 1aa5380836 feat(styling): paragraphs 2026-06-16 20:53:14 +03:00
murat 6e1659d542 chore: removing <br> tags around the block elements 2026-06-16 19:39:28 +03:00
murat 565d4add81 feat(styling): ul and ol lists 2026-06-16 19:26:14 +03:00
murat 49467fee22 fix(blockquote): force \n 2026-06-16 19:00:52 +03:00
murat e5f4026a68 blockquote line by line checking 2026-06-16 18:48:19 +03:00
murat f125640ace chore(styling): cleanup 2026-06-16 18:37:00 +03:00
murat 96f1eac252 fix(img): alt text 2026-06-16 18:32:57 +03:00
murat 67824a60b4 feat(styling): horizontal rule 2026-06-16 18:29:59 +03:00
murat 2b89ff29e2 bold and italic text alternative types 2026-06-16 18:18:29 +03:00
murat 74fe93a0ca feat(styling): images 2026-06-16 18:15:44 +03:00
murat 6a294583fa feat(styling): code 2026-06-16 18:15:30 +03:00
murat 60b91d159e feat(styling): blockquote 2026-06-16 18:11:52 +03:00
murat dd97b16890 fix(headers): added h4 h5 h6 2026-06-16 18:05:05 +03:00
murat b7ae3ca52a feat(styling): bold and italic texts 2026-06-16 17:53:00 +03:00
murat 4ca73d02ae feat(styling): headers 2026-06-16 17:46:26 +03:00
murat dbe8bcbb44 feat(styling): line breaks 2026-06-16 17:34:55 +03:00
murat 33540f028f fix page title handling 2026-06-04 20:58:16 +03:00
murat 80a8119f6a replace sitename and sitetitle 2026-06-04 20:27:26 +03:00
murat 8b43950cce proper options handling 2026-06-04 20:17:42 +03:00
murat 21fcf6702c add md contents to the html files 2026-06-04 02:06:28 +03:00
murat f9d9f5b26a replace markdownFiles.content with md file content 2026-06-04 01:34:45 +03:00
murat 82ac8e0998 create bash script for easily running the lua code 2026-06-04 01:04:20 +03:00
murat 964e58456a create html files for md files inside the public/ directory 2026-06-04 01:03:07 +03:00
murat df50db67fe required for lua lsp 2026-06-04 01:02:17 +03:00
5 changed files with 317 additions and 9 deletions
+7
View File
@@ -0,0 +1,7 @@
{
"workspace": {
"library": [
"types"
]
}
}
+20
View File
@@ -1,2 +1,22 @@
# luago # luago
Static website generator written in Lua Static website generator written in Lua
> Still in development
> Please don't use for production yet
> There are a lot of work to do
## what it does
- reads .md files in that directory and create html files
- transforms basic markdown syntax to html styling
### features
- read all md files
- check if index.md and index.html files are exists (if not stop with error)
- read options section on every md file
- clear public/ directory and create if not exists
- create html files using same name template html file
- if there are no html file for that md file, use index.html
- {{pageContent}}, {{pageTitle}} and {{list:}}-{{:list}} shortcodes
- list.item.<option> shortcode inside list shortcode
luago name
: **lua** + **hugo** (can't thing of anything better)
Executable
+3
View File
@@ -0,0 +1,3 @@
#!/usr/bin/env bash
lua /home/murat/Projects/luago/luago.lua
+246 -9
View File
@@ -12,19 +12,32 @@ for file in lfs.dir(currentDir) do
-- ignore "." and ".." files -- ignore "." and ".." files
if not(file:sub(1,2) == ".." or file:sub(1,1) == '.') then if not(file:sub(1,2) == ".." or file:sub(1,1) == '.') then
local filePath = currentDir .. '/' .. file local filePath = currentDir .. '/' .. file
-- match file names ending with ".md"
if file:match("index.md") then haveIndexmd = true end if file:match("index.md") then haveIndexmd = true end
if file:match("index.html") then haveIndexhtml = true end if file:match("index.html") then haveIndexhtml = true end
-- match file names ending with ".md"
if file:match("%.md$") then if file:match("%.md$") then
markdownFileCount = markdownFileCount + 1 markdownFileCount = markdownFileCount + 1
markdownFiles[markdownFileCount] = { markdownFiles[markdownFileCount] = {
options = {}, options = {},
filePath = filePath, filePath = filePath,
content = "" fileName = file:sub(1, file:len()-3),
content = "",
hasHtml = false
} }
end end
end end
end end
-- check html files with same name as the md files
for file in lfs.dir(currentDir) do
if file:match("%.html$") then
for index, value in ipairs(markdownFiles) do
if file:match(value.fileName .. ".html") then
markdownFiles[index].hasHtml = true
print(value.fileName .. "file has a html template file.")
end
end
end
end
-- stop the program if "index.html" or "index.md" doesn't exist -- stop the program if "index.html" or "index.md" doesn't exist
if (not(haveIndexhtml) and not(haveIndexmd)) then return print("Can't find index.html or index.md!") end if (not(haveIndexhtml) and not(haveIndexmd)) then return print("Can't find index.html or index.md!") end
@@ -49,11 +62,19 @@ for index, file in ipairs(markdownFiles) do
fileOptions = fileOptions:sub(fileOptions:find("\n")+1, fileOptions:len()) fileOptions = fileOptions:sub(fileOptions:find("\n")+1, fileOptions:len())
end end
for i, value in ipairs(lines) do -- fill options table
for _, value in ipairs(lines) do
if value:sub(value:len(), value:len()) == "\n" then if value:sub(value:len(), value:len()) == "\n" then
value = value:sub(1, value:find("\n")-1) value = value:sub(1, value:find("\n")-1)
end end
markdownFiles[index].options[i] = value local colonPos = value:find(":")
if colonPos then
local key = value:sub(1, colonPos - 1):match("^%s*(.-)%s*$")
local val = value:sub(colonPos + 1):match("^%s*(.-)%s*$")
if key and key ~= "" then
markdownFiles[index].options[key] = val
end
end
end end
else else
return print(file.filePath .. " file doesn't have a options section!") return print(file.filePath .. " file doesn't have a options section!")
@@ -63,19 +84,34 @@ for index, file in ipairs(markdownFiles) do
end end
end end
for index, value in ipairs(markdownFiles) do -- replace markdownFiles.content with md file content
for index, _ in ipairs(markdownFiles) do
local f = io.open(markdownFiles[index].filePath)
if f then
local content = f:read("*a")
f:close()
-- delete options section
content = content:sub(5, content:len())
content = content:sub(content:find("---\n")+4, content:len())
markdownFiles[index].content = content
end
end
for index, _ in ipairs(markdownFiles) do
print("---------------")
-- print file path with index -- print file path with index
print(index .. ': ' .. markdownFiles[index].filePath) print(index .. ': ' .. markdownFiles[index].filePath)
local f = io.open(markdownFiles[index].filePath, 'r') local f = io.open(markdownFiles[index].filePath, 'r')
if f then if f then
local content = f:read("*all")
f:close() f:close()
-- print file content -- print file content
print(content) print(markdownFiles[index].content)
-- print file options -- print file options
print("--- OPTIONS ---") print("--- OPTIONS ---")
for i, value in ipairs(markdownFiles[index].options) do for key, val in pairs(markdownFiles[index].options) do
print(markdownFiles[index].options[i]) print(key .. ": " .. val)
end end
print("---------------") print("---------------")
else else
@@ -83,3 +119,204 @@ for index, value in ipairs(markdownFiles) do
end end
end end
-- get index.html content
local indexFile = io.open("index.html"):read("*all")
-- delete html files and create "public/" directory
for file in lfs.dir(currentDir .. "/public") do
if file:match("%.html$") then
os.remove(currentDir .. "/public/" .. file)
end
end
lfs.mkdir("public")
for index, _ in ipairs(markdownFiles) do
if markdownFiles[index].options["draft"] ~= "yes" then
local workDir = lfs.currentdir()
-- create html file for that md file
local htmlFile = io.open(workDir .. "/public/" .. markdownFiles[index].fileName .. ".html", 'w')
if htmlFile then
local content = indexFile
if markdownFiles[index].hasHtml then content = io.open(markdownFiles[index].fileName .. ".html"):read("*all") end
-- STYLING
local function styleLists(s)
local lines = {}
for line in (s .. "\n"):gmatch("([^\n]*)\n") do
table.insert(lines, line)
end
local result = {}
local inUl = false
local inOl = false
for _, line in ipairs(lines) do
local ulItem = line:match("^[%-%*%+]%s+(.*)")
local olItem = line:match("^%d+%.%s+(.*)")
if ulItem then
if inOl then table.insert(result, "</ol>"); inOl = false end
if not inUl then table.insert(result, "<ul>"); inUl = true end
table.insert(result, "<li>" .. ulItem .. "</li>") -- ulItem!
elseif olItem then
if inUl then table.insert(result, "</ul>"); inUl = false end
if not inOl then table.insert(result, "<ol>"); inOl = true end
table.insert(result, "<li>" .. olItem .. "</li>")
else
if inUl then table.insert(result, "</ul>"); inUl = false end
if inOl then table.insert(result, "</ol>"); inOl = false end
table.insert(result, line)
end
end
if inUl then table.insert(result, "</ul>") end
if inOl then table.insert(result, "</ol>") end
return table.concat(result, "\n")
end
local function styleParagraph(s)
local lines = {}
for line in (s .. "\n"):gmatch("([^\n]*)\n") do
table.insert(lines, line)
end
local result = {}
local pLines = {}
local function flushP()
if #pLines > 0 then
table.insert(result, "<p>" .. table.concat(pLines, "\n") .. "</p>")
pLines = {}
end
end
for _, line in ipairs(lines) do
local isBlock = line:match("^<") or line:match("^#")
local isEmpty = line:match("^%s*$")
if isBlock or isEmpty then
flushP()
table.insert(result, line)
else
table.insert(pLines, line)
end
end
flushP()
return table.concat(result, "\n")
end
local function styling(s)
-- blockquote
local lines = {}
for line in (s .. "\n"):gmatch("([^\n]*)\n?") do
local nline = line:gsub("^>%s*(.*)", "<blockquote>%1</blockquote>")
table.insert(lines, nline)
end
s = table.concat(lines, "\n")
-- lists
s = styleLists(s)
-- paragraph
s = styleParagraph(s)
s = s:gsub("<p></p>", "")
-- line breaks
s = s:gsub("\r?\n", "<br>")
-- remove <br> tags around block elements
local blockTags = { "ul", "ol", "li", "blockquote", "p",
"h1", "h2", "h3", "h4", "h5", "h6", "hr" }
for _, tag in ipairs(blockTags) do
s = s:gsub("(</" .. tag .. ">)<br>", "%1")
s = s:gsub("<br>(</" .. tag .. ">)", "%1")
s = s:gsub("(<" .. tag .. ">)<br>", "%1")
s = s:gsub("<br>(<" .. tag .. ">)", "%1")
end
-- self-closing hr
s = s:gsub("(<hr>)<br>", "%1")
s = s:gsub("<br>(<hr>)", "%1")
-- headers
s = s:gsub("######%s*(.-)%s*<br>", "<h6>%1</h6>")
s = s:gsub("#####%s*(.-)%s*<br>", "<h5>%1</h5>")
s = s:gsub("####%s*(.-)%s*<br>", "<h4>%1</h4>")
s = s:gsub("###%s*(.-)%s*<br>", "<h3>%1</h3>")
s = s:gsub("##%s*(.-)%s*<br>", "<h2>%1</h2>")
s = s:gsub("#%s*(.-)%s*<br>", "<h1>%1</h1>")
-- TODO: h1 and h2 using == and --
-- bold and italic text
s = s:gsub("%*%*(.-)%*%*", "<b>%1</b>")
s = s:gsub("%*(.-)%*", "<i>%1</i>")
s = s:gsub("%_%_(.-)%_%_", "<b>%1</b>")
s = s:gsub("%_(.-)%_", "<i>%1</i>")
-- images
s = s:gsub("!%[(.-)%]%((.-)%)", '<img src="%2" alt="%1"></img>')
-- links
s = s:gsub("%[(.-)%]%((.-)%)", '<a href="%2">%1</a>')
-- code
s = s:gsub("`(.-)`", "<code>%1</code>")
-- horizontal rule
s = s:gsub("%-%-%-", "<hr>")
return s
end
markdownFiles[index].content = styling(markdownFiles[index].content)
-- start_idx, end_idx = c
-- {{pageContent}} shortcode
content = content:gsub("{{pageContent}}", markdownFiles[index].content)
-- {{pageTitle}} shortcode
content = content:gsub("{{pageTitle}}", markdownFiles[index].options["title"])
local start_idx, end_idx
-- list shortcodes
while content:find("{{list:}}", 1, true) do
start_idx, end_idx = content:find("{{list:}}")
if start_idx and end_idx then
local first = content:sub(1, start_idx - 1)
local second = content:sub(end_idx + 1)
if second:find("{{:list}}", 1, true) then
start_idx, end_idx = second:find("{{:list}}")
if start_idx and end_idx then
local shortcode = second:sub(1, start_idx - 1)
local third = second:sub(end_idx + 1)
local function formatShortcode(element)
local dir = markdownFiles[index].filePath:match("(.*)/[^/]+%.md$")
local results = {}
for mdfile in lfs.dir(dir) do
if mdfile:match("%.md$") and mdfile ~= markdownFiles[index].fileName .. ".md" then
local fileName = mdfile:match("(.-)%.md$")
for i, _ in ipairs(markdownFiles) do
if markdownFiles[i].fileName == fileName then
local copy = element
-- replace items starting with list.item.
for option in copy:gmatch("{{list%.item%.(.-)}}") do
local value = markdownFiles[i].options[option]
if value then
copy = copy:gsub("{{list%.item%." .. option .. "}}", value)
end
end
table.insert(results, copy)
end
end
end
end
return table.concat(results, "\n")
end
shortcode = formatShortcode(shortcode)
content = first .. shortcode .. third
end
end
end
end
htmlFile:write(content)
htmlFile:close()
else
print("Error: Cannot create html file for " .. markdownFiles[index].filePath)
end
end
end
+41
View File
@@ -0,0 +1,41 @@
---@meta
---@class lfs
local lfs = {}
---@param filepath string
---@param aname? string|table
---@return table|string|nil result
---@return string? error_msg
function lfs.attributes(filepath, aname) end
---@param path string
---@return boolean success
---@return string? error_msg
function lfs.chdir(path) end
---@return string path
function lfs.currentdir() end
---@param path string
---@return fun():string iter
---@return table dir_obj
function lfs.dir(path) end
---@param dirname string
---@return boolean success
---@return string? error_msg
function lfs.mkdir(dirname) end
---@param dirname string
---@return boolean success
---@return string? error_msg
function lfs.rmdir(dirname) end
---@param filepath string
---@param atime? number
---@param mtime? number
---@return boolean success
---@return string? error_msg
function lfs.touch(filepath, atime, mtime) end
return lfs