Redoing a bunch of stuff, came up with better ideas.

This commit is contained in:
n* 2025-02-26 14:39:07 -03:00
parent 5bf5a19953
commit e35e507381
19 changed files with 339 additions and 432 deletions

1
.gitignore vendored
View file

@ -1,2 +1 @@
deps
napkin.ini

6
.luarc.json Normal file
View file

@ -0,0 +1,6 @@
{
"$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json",
"runtime.version": "LuaJIT",
"addonManager.enable": true,
"workspace.userThirdParty": ["luv"]
}

18
LICENSE
View file

@ -1,18 +0,0 @@
MIT License
Copyright (c) 2025 Nelson "n" Lopez
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge, publish, distribute,
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -1,11 +0,0 @@
![napkin logo](./logo.svg)
an experimental blogging platform
## running
you will need to install luvit and it's adjacent utilities from [here](https://luvit.io)
```
git clone git@kitsunes.dev:n/napkin.git
luvi .
```

View file

@ -1,88 +0,0 @@
getmetatable("").__mod = function(s, tab)
return (s:gsub('($%b{})', function(w) return tab[w:sub(3, -2)] or w end))
end
local setup = function()
local template = [[
[server]
domain = ${domain}
port = ${port}
local_only = false
[database]
type = sqlite
file = ${database}
[static]
from = ${static}
[mappings]
; devlog-1 = @myuser/adding-cheese-pt1
]]
local read = function(...)
local a
repeat
a = io.read(...)
until a
return a
end
local with_defaults = function(question, default)
io.write(question .. " (" .. default .. ") > ")
io.flush()
local answer = read()
if #answer == 0 then
return default
end
return question
end
print
"hey there! i can see that you haven't set napkin up yet, so we will create an ini file to set napkin up.\n"
local domain = ""
repeat
io.write("what's your domain? > ")
io.flush()
domain = read()
until #domain >= 4
local port = ""
repeat
io.write("what's the desired port? > ")
io.flush()
port = read()
until tonumber(port)
local database = with_defaults("sqlite database?", "./napkin.sqlite")
local static = with_defaults("static serve folder?", "./static/")
if true then
local populate = ""
repeat
io.write("the static serve folder is empty, populate it? [Y,n] > ")
io.flush()
populate = read()
until populate == "y" or populate == "n" or populate == ""
if populate ~= "n" then
print("everything going!")
end
end
print "\nlastly, where napkin's config file should go, the default (./napkin.ini) will be automatically loaded whenever napkin is called with no --config argument, otherwise you will need said argument. you can also cancel this whole process by pressing CTRL+C\n"
local config = with_defaults("where should the config file go? ", "./napkin.ini")
print "\nand just like that, it's all done! be sure to have sqlite, ffmpeg and imagemagick installed, then run napkin again.\n\nhave a nice day!!! :)"
local file = assert(io.open(config, "w"), "cannot open file!")
file:write(template % {
domain = domain,
port = port,
database = database,
static = static
})
file:close()
os.exit()
end

View file

@ -1 +0,0 @@
local config = require "config"

View file

@ -0,0 +1,13 @@
{
"owner": 8675309,
"name": "Super Cool Blog",
"biography": "Hey there! Name's Mike, but everyone calls me Supreme Mike around these parts. I'm just a regular dude trying to make it big in this crazy world. I love long walks on the beach, feeding ducks at the park, and occasionally BASE jumping off the local Walmart -- you know, normal stuff! My philosophy in life is to take everything in stri- WAIT A SECOND, IS THAT PIZZA I SMELL??? OH MY ABSOLUTE FREAKING GOD I CANNOT STAND PIZZA!!1! Do you know what PIZZA has DONE to our SOCIETY?!? The AUDACITY of those flat circular MONSTROSITIES to call themselves FOOD! Every time I see someone eating pizza I just want to SCREAM into the VOID because HOW CAN PEOPLE BE SO BLIND?! It's just sauce and cheese ON BREAD! Wake up SHEEPLE! This is why I've dedicated my entire existence to DESTROYING every pizza I see. I will not REST until every LAST SLICE is ERADICATED from this earth! ...anyway, I also enjoy gardening and collecting vintage stamps.",
"index": [
{
"title": "Why I hate pizza.",
"url": "why-i-hate-pizza",
"creation": 1674933627
}
]
}

86
ini.lua
View file

@ -1,86 +0,0 @@
local function unescape(str)
local escape_sequences = {
["n"] = "\n",
["t"] = "\t",
["r"] = "\r",
["b"] = "\b",
["f"] = "\f",
["v"] = "\v",
["\\"] = "\\",
["\""] = "\"",
["'"] = "'"
}
return str:gsub("\\(.)", escape_sequences)
end
---@param s string
local function parse(s)
local function parse_string(input)
local str
local first = input:sub(1, 1)
local terminator
if first == "'" or first == '"' then
terminator = first
end
return str, false
end
local current_line = 0
-- For each line
for line in s:gmatch("[^\r\n]+") do
-- Count up
current_line = current_line + 1
-- Find a possible comment
local p = line:find(";")
-- If it exists and has not been escaped (\;)
if p and line:sub(p - 1, 1) ~= "\\" then
line = line:sub(1, p - 1) -- Trim!
end
-- Trim whitespace, we don't need it.
line = line:gsub('%s+', '')
-- If by now, the line is fully empty, then ignore.
-- (Usually this means that the line was only whitespace or a comment)
if #line == 0 then
goto continue
end
-- If line starts with [, then
if line:sub(1, 1) == "[" then
-- Check if it ends with ] (if not, fail)
if line:sub(#line) ~= "]" then
return false, current_line .. ": Malformed label! Misplaced/missing ']'"
end
-- Then assume it's a label, and extract the name
local label = line:sub(2, #line - 1)
-- We "unescape" all the stuff (ex. \n to newline)
print(unescape(label))
goto continue
end
-- Otherwise, line MUST be a statement!
-- Matches for (value name = my stuff)
local proto_key, proto_value = line:match("^(.-)%s*=%s*(.-)$")
if not proto_key then
return nil, current_line .. ": Malformed assign!"
end
::continue::
end
end
local data, err = parse [[
[label]
packman = sjsj
]]
print(err)

View file

@ -1,4 +0,0 @@
here are some libraries i came up with, for some reason.
yeah i just didnt see anything quite like it on the lua
landscape and i had to do it myself 🔥

View file

@ -1,107 +0,0 @@
local function unescape(str)
local escape_sequences = {
["n"] = "\n",
["t"] = "\t",
["r"] = "\r",
["b"] = "\b",
["f"] = "\f",
["v"] = "\v",
["\\"] = "\\",
["\""] = "\"",
["'"] = "'"
}
return str:gsub("\\(.)", escape_sequences)
end
---@param s string
local function parse(s)
local out = {}
local section = out
local current_line = 0
-- For each line
for line in s:gmatch("[^\r\n]+") do
-- Count up
current_line = current_line + 1
-- Find a possible comment
local p = line:find(";")
-- If it exists and has not been escaped (\;)
if p and line:sub(p - 1, 1) ~= "\\" then
line = line:sub(1, p - 1) -- Trim!
end
-- Trim whitespace, we don't need it.
line = line:gsub('%s+', '')
-- If by now, the line is fully empty, then ignore.
-- (Usually this means that the line was only whitespace or a comment)
if #line == 0 then
goto continue
end
-- If line starts with [, then
if line:sub(1, 1) == "[" then
-- Check if it ends with ] (if not, fail)
if line:sub(#line) ~= "]" then
return false, current_line .. ": Malformed label! Misplaced/missing ']'"
end
-- Then assume it's a label, and extract the name
local label = line:sub(2, #line - 1)
out[label] = out[label] or {}
section = out[label]
goto continue
end
-- Otherwise, line MUST be a statement!
-- Matches for (value name = my stuff)
local proto_key, proto_value = line:match("^(.-)%s*=%s*(.-)$")
if not proto_key then
return nil, current_line .. ": Malformed assign!"
end
-- Get start of value, to check whether we have to parse a "string" or not
local start = proto_value:sub(1, 1)
if proto_value == "none" then -- Handle none
section[proto_key] = nil
elseif start == "'" or start == '"' then -- Handle strings
local last = proto_value:find(start, 2)
while last and proto_value:sub(last - 1, last - 1) == "\\" do
last = proto_value:find(start, last + 1)
end
if not last then
return nil, current_line .. ": Unterminated string!"
end
section[proto_key] = unescape(proto_value:sub(2, last - 1))
elseif proto_value == "true" then -- Handle true
section[proto_key] = true
elseif proto_value == "false" then -- Handle false
section[proto_key] = false
else -- Handle
section[proto_key] =
tonumber(proto_value) or -- Numbers and
unescape(proto_value) -- Regular values!
end
::continue::
end
return out
end
-- TODO!
local function encode()
end
return {
parse = parse
}

View file

@ -1,97 +0,0 @@
-- io operations for prompt utilities
local write = io.write
local read = io.read
-- handle luvit's different io behavior
if package.loaded.luvibundle then
read = function()
local o
repeat
o = io.read()
until o
return o
end
end
-- basic prompt for text input
local prompt = function(question, default)
local r
repeat
write(question)
if default then
write(" (", default, ")")
end
write(" > ")
r = read()
if r == "" then
r = default
end
until r ~= nil
return r
end
-- yes/no prompt returning boolean
local prompt_binary = function(question, default)
local p = " [y/n]"
if default == "y" then
p = " [Y/n]"
elseif default == "n" then
p = " [y/N]"
else
default = nil
end
local r
repeat
write(question, p)
write(" > ")
r = read()
if r == "" then
r = default and "y"
end
until r == "y" or r == "n"
return r == "y"
end
-- number prompt with optional min/max bounds
local prompt_number = function(question, default, min, max)
local num
while true do
write(question)
if default then
write(" (", default, ")")
end
if max and min then
write(" [", min, "..", max, "]")
elseif max then
write(" [..", max, "]")
elseif min then
write(" [", min, "..]")
end
write(" > ")
local r = read()
if r == "" then
r = default
end
num = tonumber(r)
if num and
((not min) or (num >= min)) and
((not max) or (num <= max)) then
break
end
end
return num
end
return {
prompt = prompt,
binary = prompt_binary,
number = prompt_number
}

View file

@ -1 +0,0 @@
<svg viewBox="0 0 158.8 39.7" xmlns="http://www.w3.org/2000/svg"><path d="M107.8 0 99 13.3 98.4.6l-8 1.8L88.7 37l10.2 1.3-.9-14.4 14.2 13.6L116 28l-11-9 10-14zm21 1.8 1.2 35.3 9.1 1.3-.5-10.5L141 19l4-6.4 2.9-.4 2.6 3.8v22l8.3-.3-1.4-29.4-4.3-6.2h-8.3L139.4 9l-2 5-.3-12.3zm-87.5.4-9.6 2.9 3 7.6 7.7-2.7 5.3 1.1.7 5.3-4 2-7-1.5-3.8 4.4-2.5 7-.3 6 6.2 2.8 7-.3 4-2.3 1.5 4L59 37l-3.3-5.6 3.7-5.9V16l-2.1-9.8-5.9-3zm35.7.3-11.7.6-3.7 4.8 1.5 30.6 8-.2-.6-12h9l6.4-4.1.8-8.6-1.4-7.2zM0 3.1l1.2 35.3 9.1 1.3-.6-10.5 2.5-8.9 3.9-6.4 3-.4 2.6 3.8v22.1l8.3-.3-1.5-29.4-4.2-6.2H16l-5.4 6.9-2 5-.3-12.3zm125.8.2-7.1.7-2 32.3 8.1 1.7zm-56 6.4 9.8 4.4-8 4zM38.6 24.9h6.9l-6.6 6Z" stroke-width="5.3" stroke-linecap="square" paint-order="markers stroke fill" stroke="#fff"/></svg>

Before

Width:  |  Height:  |  Size: 767 B

View file

@ -1,15 +1,37 @@
local weblit = require('weblit')
local config = require('config')
local database = require('database')
local radon = require("./radon")
local fs = require("fs")
weblit.app
.bind({ host = "0.0.0.0", port = 1337 })
.bind({ host = "127.0.0.1", port = 1337 })
-- Configure weblit server
.use(weblit.logger)
.use(weblit.autoHeaders)
.use(weblit.static("static"))
.route(
{ path = "/static/:path:" },
weblit.static("static")
)
.route({ path = "/p/:post" }, function()
end)
.route({ path = "/@:blog" }, function(req, res)
local data = dofile("template/blog.lua")
res.body = radon.compile(data)
res.code = 200
res.headers["Content-Type"] = "text/html"
end)
.route({ path = "/@:blog/:post" }, function(req, res)
end)
.route({ path = "/" }, function(req, res)
end)
-- Start the server
.start()

View file

@ -1,19 +1,16 @@
return {
name = "napkin",
name = "radon",
version = "0.0.1",
description = "an experimental blogging platform",
tags = { "lua", "lit", "luvit", "fediverse" },
description = "A simple description of my little package.",
tags = { "lua", "lit", "luvit" },
license = "MIT",
author = { name = "Nelson Lopez", email = "darltrash@icloud.com" },
homepage = "n.cyrneko.eu/napkin",
author = { name = "n", email = "darltrash@icloud.com" },
homepage = "https://github.com/radontest",
dependencies = {
"creationix/weblit",
"TohruMKDM/cli"
"creationix/weblit"
},
files = {
"**.lua",
"static/",
"!test*"
}
}

158
radon.lua Normal file
View file

@ -0,0 +1,158 @@
local tag = function(name)
return function(content)
local data = ""
local t = type(content)
if t == "function" then
local o = {}
local e = content(o)
if e then table.insert(o, e) end
content = o
t = "table"
end
if t == "table" then
for key, value in pairs(content) do
if type(key) == "string" then
data = data .. ' ' .. key .. '="' .. value .. '"'
end
end
content = table.concat(content, "")
end
if t == "nil" then
content = ""
end
return ("<%s%s>%s</%s>"):format(name, data, content, name)
end
end
local radon = {
p = tag("p"),
h1 = tag("h1"),
h2 = tag("h2"),
h3 = tag("h3"),
h4 = tag("h4"),
h5 = tag("h5"),
h6 = tag("h6"),
div = tag("div"),
span = tag("span"),
a = tag("a"),
img = tag("img"),
br = tag("br"),
hr = tag("hr"),
table = tag("table"),
tr = tag("tr"),
td = tag("td"),
th = tag("th"),
thead = tag("thead"),
tbody = tag("tbody"),
tfoot = tag("tfoot"),
ul = tag("ul"),
ol = tag("ol"),
li = tag("li"),
dl = tag("dl"),
dt = tag("dt"),
dd = tag("dd"),
form = tag("form"),
input = tag("input"),
textarea = tag("textarea"),
select = tag("select"),
option = tag("option"),
button = tag("button"),
label = tag("label"),
script = tag("script"),
link = tag("link"),
meta = tag("meta"),
title = tag("title"),
head = tag("head"),
body = tag("body"),
html = tag("html"),
nav = tag("nav"),
header = tag("header"),
footer = tag("footer"),
main = tag("main"),
figure = tag("figure"),
figcaption = tag("figcaption"),
blockquote = tag("blockquote"),
pre = tag("pre"),
code = tag("code"),
em = tag("em"),
strong = tag("strong"),
i = tag("i"),
b = tag("b"),
small = tag("small"),
sub = tag("sub"),
sup = tag("sup"),
time = tag("time"),
progress = tag("progress"),
audio = tag("audio"),
video = tag("video"),
source = tag("source"),
iframe = tag("iframe"),
canvas = tag("canvas"),
svg = tag("svg"),
section = tag("section"),
article = tag("article"),
aside = tag("aside"),
details = tag("details"),
summary = tag("summary"),
dialog = tag("dialog"),
menu = tag("menu"),
menuitem = tag("menuitem"),
meter = tag("meter"),
abbr = tag("abbr"),
address = tag("address"),
bdi = tag("bdi"),
bdo = tag("bdo"),
cite = tag("cite"),
datalist = tag("datalist"),
dfn = tag("dfn"),
ins = tag("ins"),
del = tag("del"),
kbd = tag("kbd"),
mark = tag("mark"),
noscript = tag("noscript"),
output = tag("output"),
picture = tag("picture"),
ruby = tag("ruby"),
rt = tag("rt"),
rp = tag("rp"),
samp = tag("samp"),
template = tag("template"),
var = tag("var"),
wbr = tag("wbr"),
fieldset = tag("fieldset"),
legend = tag("legend"),
optgroup = tag("optgroup"),
track = tag("track"),
colgroup = tag("colgroup"),
col = tag("col"),
caption = tag("caption"),
object = tag("object"),
param = tag("param"),
map = tag("map"),
area = tag("area"),
style = tag("style"),
}
radon.compile = function(func, ...)
local p = _G.p
_G.p = nil
setmetatable(_G, { __index = radon })
local r = func(...)
setmetatable(_G, nil)
_G.p = p
return r
end
setmetatable(radon, {
__call = function(_, func, ...)
return radon.compile(func, ...)
end
})
return radon

View file

@ -1,5 +1,50 @@
/*
please do not modify this, it's a crucial part of Napkin's UI normalization
body {
max-width: 600px;
margin: auto;
padding: 20px;
if you want to customize your Napkin instance, please modify instance.css!
*/
font-family:
system-ui,
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
"Roboto",
"Oxygen",
"Inter",
"Ubuntu",
"Cantarell",
"Fira Sans",
"Droid Sans",
"Helvetica Neue",
sans-serif;
}
h1,
h2,
h3,
h4,
h5,
p {
margin: 0;
}
.post {
border: 1px solid #ccc;
padding: 10px;
}
.post-list {
}
.biography {
border: 1px solid #ccc;
padding: 10px;
text-align: justify;
}
section {
margin-bottom: 40px;
gap: 15px;
display: flex;
flex-direction: column;
}

View file

@ -1 +0,0 @@
<h1>penia!</h1>

81
template/blog.lua Normal file
View file

@ -0,0 +1,81 @@
---@diagnostic disable:undefined-global
return function()
local user = {
handle = "@supercool",
name = "Super Cool",
biography =
"i am a super cool person, hello, welcome to my thing, uhhh, that thing where you write a thing and then you're like \"okay now what\" and write some more things because you can't think of anything else to write, and then you add some funny emojis like 🌙 🎂 🍋 okay anyway the point is i'm doing that thing where i just keep writing more and more stuff because i'm supposed to make it longer and more boring and not actually say anything",
posts = {
{
title = "Do not ever consume pizza.",
timestamp = os.time()
},
{
title = "Why I don't believe in aliens.",
timestamp = os.time() - 90000
},
{
title = "Pizza SUCKS, here's why.",
timestamp = os.time() - 90000 * 4,
pinned = true
}
},
}
return html {
head {
meta { charset = "UTF-8" },
meta { ["http-equiv"] = "X-UA-Compatible", content = "IE=edge" },
meta { name = "viewport", content = "width=device-width, initial-scale=1.0" },
title "Document",
link { href = "static/baseline.css", rel = "stylesheet" },
},
body {
h1(user.name),
p({
a { href = "/", "instance.com/" },
" ",
a { href = "/" .. user.handle, user.handle .. "/" }
}),
br(),
section({ class = "biography", user.biography }),
section(function(o)
o.class = "post-list pinned-list"
table.insert(o, h2("Pinned:"))
for _, post in ipairs(user.posts) do
if post.pinned then
table.insert(o, div {
class = "post",
h2("📌 " .. post.title),
p(os.date("%Y-%m-%d %H:%M:%S", post.timestamp))
})
end
end
end),
section(function(o)
o.class = "post-list"
table.insert(o, h2("Posts:"))
for _, post in ipairs(user.posts) do
table.insert(o, div {
class = "post",
h2(post.title),
p(os.date("%Y-%m-%d %H:%M:%S", post.timestamp))
})
end
end)
}
}
end