10 Commits

Author SHA1 Message Date
3430141184 new creation format 2025-12-28 15:51:58 -05:00
c5fe2eb7e7 maint: tidy 2025-12-28 14:08:55 -05:00
afd0d9eef4 doc: fix update date 2025-12-27 19:17:19 -05:00
b593aa26f0 doc: update documentation 2025-12-27 19:16:59 -05:00
fb8dc3cb90 fix: use 0.0.0.0 instead of 127.0.0.0 2025-12-27 16:29:55 -05:00
c681e342a0 fix: update docker interface 2025-12-27 12:04:04 -05:00
99e437b42b Docker image first version 2025-12-26 22:03:49 -05:00
56f22bb472 limit philosphy to 80 width 2025-12-26 15:00:27 -05:00
e53cc17025 default stylua formatting 2025-12-26 13:24:30 -05:00
2798cd6553 Update README.md 2025-12-26 10:16:42 -05:00
15 changed files with 877 additions and 666 deletions

27
Dockerfile Normal file
View File

@@ -0,0 +1,27 @@
FROM golang:1.25-alpine AS builder
WORKDIR /src
RUN apk add --no-cache git build-base
COPY go.mod go.sum ./
RUN go mod download
COPY . .
ENV CGO_ENABLED=0
ENV GOOS=linux
ENV GOARCH=amd64
RUN go build -ldflags="-X fes/modules/version.gitCommit=$(git rev-parse --short HEAD) -s -w" -o fes
FROM scratch
COPY --from=builder /src/fes /fes
WORKDIR /app
EXPOSE 8080
ENTRYPOINT ["/fes"]
CMD ["run", "/app"]

View File

@@ -34,7 +34,7 @@ go install fes
## Documentation
Run `fes run doc` for the documentation website or goto [docs.vxserver.dev](https://docs.vxserver.dev)
Run `fes doc` for the documentation website or goto [docs.vxserver.dev](https://docs.vxserver.dev)
## License

View File

@@ -0,0 +1,5 @@
[app]
name = "hello"
version = "0.0.1"
authors = ["vx-clutch"]

View File

@@ -0,0 +1,8 @@
local fes = require("fes")
local site = fes.fes()
site.copyright = fes.util.copyright("https://fsd.vxserver.dev", "fSD")
site:h1("Hello, World!")
return site

View File

@@ -0,0 +1,7 @@
services:
hello:
image: git.vxserver.dev/fsd/fes:latest
ports:
- "3000:3000"
volumes:
- ./app:/app

14
go.mod
View File

@@ -3,13 +3,15 @@ module fes
go 1.25.4
require (
github.com/fatih/color v1.18.0 // indirect
github.com/gomarkdown/markdown v0.0.0-20250810172220-2e2c11897d1a // indirect
github.com/gomarkdown/mdtohtml v0.0.0-20240124153210-d773061d1585 // indirect
github.com/fatih/color v1.18.0
github.com/gomarkdown/markdown v0.0.0-20250810172220-2e2c11897d1a
github.com/pelletier/go-toml/v2 v2.2.4
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
github.com/yuin/gopher-lua v1.1.1
)
require (
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/yuin/gopher-lua v1.1.1 // indirect
golang.org/x/sys v0.25.0 // indirect
)

2
go.sum
View File

@@ -1,9 +1,7 @@
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/gomarkdown/markdown v0.0.0-20230922112808-5421fefb8386/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
github.com/gomarkdown/markdown v0.0.0-20250810172220-2e2c11897d1a h1:l7A0loSszR5zHd/qK53ZIHMO8b3bBSmENnQ6eKnUT0A=
github.com/gomarkdown/markdown v0.0.0-20250810172220-2e2c11897d1a/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
github.com/gomarkdown/mdtohtml v0.0.0-20240124153210-d773061d1585/go.mod h1:6grYm5/uY15CwgBBqwA3+o/cAzaxssckznJ0B35ouBY=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=

View File

@@ -183,6 +183,7 @@ footer {
<li><a href="#introduction">Introduction</a></li>
<li><a href="#installation">Installation</a></li>
<li><a href="#usage">Usage</a></li>
<li><a href="#quick">Quick Start</a></li>
<li><a href="#cli-reference">Cli Reference</a></li>
<li><a href="#reference">Reference</a></li>
</ul>
@@ -210,6 +211,34 @@ footer {
</ul>
</section>
<section id="quick">
<h2>Quick Start</h2>
<pre><code>fes new hello</code></pre>
<p>This creates a new project under the name <code>hello</code>.</p>
<pre><code>fes run hello</code></pre>
<p>This runs your project <code>hello</code>, by default at <a href="localhost:3000" target="_blank">localhost:3000</a>.</p>
<h3>Extensions</h3>
<p>Let's add a paragraph to this simple site. Right now you have the following page:</p>
<pre><code>local fes = require("fes")
local site = fes.fes()
-- site.copyright = fes.util.copyright("https://fsd.vxserver.dev", "vx-clutch")
site:h1("Hello, World!")
return site</code></pre>
<p>To add a simple paragraph modify like so:</p>
<pre><code>local fes = require("fes")
local site = fes.fes()
-- site.copyright = fes.util.copyright("https://fsd.vxserver.dev", "vx-clutch")
site:h1("Hello, World!")
site:p("This is a paragraph")
return site</code></pre>
</section>
<section id="cli-reference">
<h2>Cli Reference</h2>
<table> <thead>
@@ -219,18 +248,6 @@ footer {
</tr>
</thead>
<tbody>
<tr>
<td><code>--help</code></td>
<td>Display help information</td>
</tr>
<tr>
<td><code>--no-color</code></td>
<td>Disable color output</td>
</tr>
<tr>
<td><code>-p &lt;port&gt;</code></td>
<td>Set the server port</td>
</tr>
<tr>
<td><code>new &lt;project&gt;</code></td>
<td>Create a new projet called &lt;project&gt;</td>
@@ -243,6 +260,30 @@ footer {
<td><code>run &lt;project&gt;</code></td>
<td>Run the projet called &lt;project&gt;</td>
</tr>
<tr>
<td><code>-help</code></td>
<td>Display help information.</td>
</tr>
<tr>
<td><code>-V1</code></td>
<td>Print extended version information, this is very helpful when it comes to bug reporting.</td>
</tr>
<tr>
<td><code>-no-color</code></td>
<td>Disable color output.</td>
</tr>
<tr>
<td><code>-p &lt;port&gt;</code></td>
<td>Set the server port.</td>
</tr>
<tr>
<td><code>-static</code></td>
<td>Render and save all pages. (this feature is yet to be implemented)</td>
</tr>
<tr>
<td><code>-version</code></td>
<td>Print the version.</td>
</tr>
</tbody>
</table>
</section>
@@ -586,7 +627,7 @@ return hello</pre></code> This can be called from another with,
</section>
<footer>
<p>Last updated: 2025-12-16</p>
<p>Last updated: 2025-12-27</p>
</footer>
</main>
</body>

View File

@@ -1,7 +1,7 @@
-- Module options:
local always_use_lpeg = false
local register_global_module_table = false
local global_module_name = 'json'
local global_module_name = "json"
--[==[
@@ -47,8 +47,7 @@ local pairs, type, tostring, tonumber, getmetatable, setmetatable =
local error, require, pcall, select = error, require, pcall, select
local floor, huge = math.floor, math.huge
local strrep, gsub, strsub, strbyte, strchar, strfind, strlen, strformat =
string.rep, string.gsub, string.sub, string.byte, string.char,
string.find, string.len, string.format
string.rep, string.gsub, string.sub, string.byte, string.char, string.find, string.len, string.format
local strmatch = string.match
local concat = table.concat
@@ -69,24 +68,28 @@ local _ENV = nil -- blocking globals in Lua 5.2 and later
pcall(function()
-- Enable access to blocked metatables.
-- Don't worry, this module doesn't change anything in them.
local debmeta = require "debug".getmetatable
if debmeta then getmetatable = debmeta end
local debmeta = require("debug").getmetatable
if debmeta then
getmetatable = debmeta
end
end)
json.null = setmetatable({}, {
__tojson = function () return "null" end
__tojson = function()
return "null"
end,
})
local function isarray(tbl)
local max, n, arraylen = 0, 0, 0
for k, v in pairs(tbl) do
if k == 'n' and type(v) == 'number' then
if k == "n" and type(v) == "number" then
arraylen = v
if v > max then
max = v
end
else
if type(k) ~= 'number' or k < 1 or floor(k) ~= k then
if type(k) ~= "number" or k < 1 or floor(k) ~= k then
return false
end
if k > max then
@@ -102,8 +105,13 @@ local function isarray (tbl)
end
local escapecodes = {
["\""] = "\\\"", ["\\"] = "\\\\", ["\b"] = "\\b", ["\f"] = "\\f",
["\n"] = "\\n", ["\r"] = "\\r", ["\t"] = "\\t"
['"'] = '\\"',
["\\"] = "\\\\",
["\b"] = "\\b",
["\f"] = "\\f",
["\n"] = "\\n",
["\r"] = "\\r",
["\t"] = "\\t",
}
local function escapeutf8(uchar)
@@ -149,7 +157,7 @@ end
local function quotestring(value)
-- based on the regexp "escapable" in https://github.com/douglascrockford/JSON-js
value = fsub (value, "[%z\1-\31\"\\\127]", escapeutf8)
value = fsub(value, '[%z\1-\31"\\\127]', escapeutf8)
if strfind(value, "[\194\216\220\225\226\239]") then
value = fsub(value, "\194[\128-\159\173]", escapeutf8)
value = fsub(value, "\216[\128-\132]", escapeutf8)
@@ -160,7 +168,7 @@ local function quotestring (value)
value = fsub(value, "\239\187\191", escapeutf8)
value = fsub(value, "\239\191[\176-\191]", escapeutf8)
end
return "\"" .. value .. "\""
return '"' .. value .. '"'
end
json.quotestring = quotestring
@@ -206,8 +214,7 @@ end
function json.addnewline(state)
if state.indent then
state.bufferlen = addnewline2 (state.level or 0,
state.buffer, state.bufferlen or #(state.buffer))
state.bufferlen = addnewline2(state.level or 0, state.buffer, state.bufferlen or #state.buffer)
end
end
@@ -215,7 +222,7 @@ local encode2 -- forward declaration
local function addpair(key, value, prev, indent, level, buffer, buflen, tables, globalorder, state)
local kt = type(key)
if kt ~= 'string' and kt ~= 'number' then
if kt ~= "string" and kt ~= "number" then
return nil, "type '" .. kt .. "' is not supported as a key by JSON."
end
if prev then
@@ -236,7 +243,7 @@ end
local function appendcustom(res, buffer, state)
local buflen = state.bufferlen
if type (res) == 'string' then
if type(res) == "string" then
buflen = buflen + 1
buffer[buflen] = res
end
@@ -251,7 +258,9 @@ local function exception(reason, value, state, buffer, buflen, defaultmessage)
else
state.bufferlen = buflen
local ret, msg = handler(reason, value, state, defaultmessage)
if not ret then return nil, msg or defaultmessage end
if not ret then
return nil, msg or defaultmessage
end
return appendcustom(ret, buffer, state)
end
end
@@ -263,22 +272,24 @@ end
encode2 = function(value, indent, level, buffer, buflen, tables, globalorder, state)
local valtype = type(value)
local valmeta = getmetatable(value)
valmeta = type (valmeta) == 'table' and valmeta -- only tables
valmeta = type(valmeta) == "table" and valmeta -- only tables
local valtojson = valmeta and valmeta.__tojson
if valtojson then
if tables[value] then
return exception('reference cycle', value, state, buffer, buflen)
return exception("reference cycle", value, state, buffer, buflen)
end
tables[value] = true
state.bufferlen = buflen
local ret, msg = valtojson(value, state)
if not ret then return exception('custom encoder failed', value, state, buffer, buflen, msg) end
if not ret then
return exception("custom encoder failed", value, state, buffer, buflen, msg)
end
tables[value] = nil
buflen = appendcustom(ret, buffer, state)
elseif value == nil then
buflen = buflen + 1
buffer[buflen] = "null"
elseif valtype == 'number' then
elseif valtype == "number" then
local s
if value ~= value or value >= huge or -value >= huge then
-- This is the behaviour of the original JSON implementation.
@@ -288,20 +299,20 @@ encode2 = function (value, indent, level, buffer, buflen, tables, globalorder, s
end
buflen = buflen + 1
buffer[buflen] = s
elseif valtype == 'boolean' then
elseif valtype == "boolean" then
buflen = buflen + 1
buffer[buflen] = value and "true" or "false"
elseif valtype == 'string' then
elseif valtype == "string" then
buflen = buflen + 1
buffer[buflen] = quotestring(value)
elseif valtype == 'table' then
elseif valtype == "table" then
if tables[value] then
return exception('reference cycle', value, state, buffer, buflen)
return exception("reference cycle", value, state, buffer, buflen)
end
tables[value] = true
level = level + 1
local isa, n = isarray(value)
if n == 0 and valmeta and valmeta.__jsontype == 'object' then
if n == 0 and valmeta and valmeta.__jsontype == "object" then
isa = false
end
local msg
@@ -310,7 +321,9 @@ encode2 = function (value, indent, level, buffer, buflen, tables, globalorder, s
buffer[buflen] = "["
for i = 1, n do
buflen, msg = encode2(value[i], indent, level, buffer, buflen, tables, globalorder, state)
if not buflen then return nil, msg end
if not buflen then
return nil, msg
end
if i < n then
buflen = buflen + 1
buffer[buflen] = ","
@@ -332,21 +345,27 @@ encode2 = function (value, indent, level, buffer, buflen, tables, globalorder, s
if v ~= nil then
used[k] = true
buflen, msg = addpair(k, v, prev, indent, level, buffer, buflen, tables, globalorder, state)
if not buflen then return nil, msg end
if not buflen then
return nil, msg
end
prev = true -- add a seperator before the next element
end
end
for k, v in pairs(value) do
if not used[k] then
buflen, msg = addpair(k, v, prev, indent, level, buffer, buflen, tables, globalorder, state)
if not buflen then return nil, msg end
if not buflen then
return nil, msg
end
prev = true -- add a seperator before the next element
end
end
else -- unordered
for k, v in pairs(value) do
buflen, msg = addpair(k, v, prev, indent, level, buffer, buflen, tables, globalorder, state)
if not buflen then return nil, msg end
if not buflen then
return nil, msg
end
prev = true -- add a seperator before the next element
end
end
@@ -358,8 +377,14 @@ encode2 = function (value, indent, level, buffer, buflen, tables, globalorder, s
end
tables[value] = nil
else
return exception ('unsupported type', value, state, buffer, buflen,
"type '" .. valtype .. "' is not supported by JSON.")
return exception(
"unsupported type",
value,
state,
buffer,
buflen,
"type '" .. valtype .. "' is not supported by JSON."
)
end
return buflen
end
@@ -370,8 +395,16 @@ function json.encode (value, state)
local buffer = oldbuffer or {}
state.buffer = buffer
updatedecpoint()
local ret, msg = encode2 (value, state.indent, state.level or 0,
buffer, state.bufferlen or 0, state.tables or {}, state.keyorder, state)
local ret, msg = encode2(
value,
state.indent,
state.level or 0,
buffer,
state.bufferlen or 0,
state.tables or {},
state.keyorder,
state
)
if not ret then
error(msg, 2)
elseif oldbuffer == buffer then
@@ -406,17 +439,23 @@ end
local function scanwhite(str, pos)
while true do
pos = strfind(str, "%S", pos)
if not pos then return nil end
if not pos then
return nil
end
local sub2 = strsub(str, pos, pos + 1)
if sub2 == "\239\187" and strsub(str, pos + 2, pos + 2) == "\191" then
-- UTF-8 Byte Order Mark
pos = pos + 3
elseif sub2 == "//" then
pos = strfind(str, "[\n\r]", pos + 2)
if not pos then return nil end
if not pos then
return nil
end
elseif sub2 == "/*" then
pos = strfind(str, "*/", pos + 2)
if not pos then return nil end
if not pos then
return nil
end
pos = pos + 2
else
return pos
@@ -425,8 +464,14 @@ local function scanwhite (str, pos)
end
local escapechars = {
["\""] = "\"", ["\\"] = "\\", ["/"] = "/", ["b"] = "\b", ["f"] = "\f",
["n"] = "\n", ["r"] = "\r", ["t"] = "\t"
['"'] = '"',
["\\"] = "\\",
["/"] = "/",
["b"] = "\b",
["f"] = "\f",
["n"] = "\n",
["r"] = "\r",
["t"] = "\t",
}
local function unichar(value)
@@ -435,17 +480,16 @@ local function unichar (value)
elseif value <= 0x007f then
return strchar(value)
elseif value <= 0x07ff then
return strchar (0xc0 + floor(value/0x40),
0x80 + (floor(value) % 0x40))
return strchar(0xc0 + floor(value / 0x40), 0x80 + (floor(value) % 0x40))
elseif value <= 0xffff then
return strchar (0xe0 + floor(value/0x1000),
0x80 + (floor(value/0x40) % 0x40),
0x80 + (floor(value) % 0x40))
return strchar(0xe0 + floor(value / 0x1000), 0x80 + (floor(value / 0x40) % 0x40), 0x80 + (floor(value) % 0x40))
elseif value <= 0x10ffff then
return strchar (0xf0 + floor(value/0x40000),
return strchar(
0xf0 + floor(value / 0x40000),
0x80 + (floor(value / 0x1000) % 0x40),
0x80 + (floor(value / 0x40) % 0x40),
0x80 + (floor(value) % 0x40))
0x80 + (floor(value) % 0x40)
)
else
return nil
end
@@ -455,7 +499,7 @@ local function scanstring (str, pos)
local lastpos = pos + 1
local buffer, n = {}, 0
while true do
local nextpos = strfind (str, "[\"\\]", lastpos)
local nextpos = strfind(str, '["\\]', lastpos)
if not nextpos then
return unterminated(str, "string", pos)
end
@@ -463,7 +507,7 @@ local function scanstring (str, pos)
n = n + 1
buffer[n] = strsub(str, lastpos, nextpos - 1)
end
if strsub (str, nextpos, nextpos) == "\"" then
if strsub(str, nextpos, nextpos) == '"' then
lastpos = nextpos + 1
break
else
@@ -517,36 +561,48 @@ local scanvalue -- forward declaration
local function scantable(what, closechar, str, startpos, nullval, objectmeta, arraymeta)
local tbl, n = {}, 0
local pos = startpos + 1
if what == 'object' then
if what == "object" then
setmetatable(tbl, objectmeta)
else
setmetatable(tbl, arraymeta)
end
while true do
pos = scanwhite(str, pos)
if not pos then return unterminated (str, what, startpos) end
if not pos then
return unterminated(str, what, startpos)
end
local char = strsub(str, pos, pos)
if char == closechar then
return tbl, pos + 1
end
local val1, err
val1, pos, err = scanvalue(str, pos, nullval, objectmeta, arraymeta)
if err then return nil, pos, err end
if err then
return nil, pos, err
end
pos = scanwhite(str, pos)
if not pos then return unterminated (str, what, startpos) end
if not pos then
return unterminated(str, what, startpos)
end
char = strsub(str, pos, pos)
if char == ":" then
if val1 == nil then
return nil, pos, "cannot use nil as table index (at " .. loc(str, pos) .. ")"
end
pos = scanwhite(str, pos + 1)
if not pos then return unterminated (str, what, startpos) end
if not pos then
return unterminated(str, what, startpos)
end
local val2
val2, pos, err = scanvalue(str, pos, nullval, objectmeta, arraymeta)
if err then return nil, pos, err end
if err then
return nil, pos, err
end
tbl[val1] = val2
pos = scanwhite(str, pos)
if not pos then return unterminated (str, what, startpos) end
if not pos then
return unterminated(str, what, startpos)
end
char = strsub(str, pos, pos)
else
n = n + 1
@@ -566,10 +622,10 @@ scanvalue = function (str, pos, nullval, objectmeta, arraymeta)
end
local char = strsub(str, pos, pos)
if char == "{" then
return scantable ('object', "}", str, pos, nullval, objectmeta, arraymeta)
return scantable("object", "}", str, pos, nullval, objectmeta, arraymeta)
elseif char == "[" then
return scantable ('array', "]", str, pos, nullval, objectmeta, arraymeta)
elseif char == "\"" then
return scantable("array", "]", str, pos, nullval, objectmeta, arraymeta)
elseif char == '"' then
return scanstring(str, pos)
else
local pstart, pend = strfind(str, "^%-?[%d%.]+[eE]?[%+%-]?%d*", pos)
@@ -598,7 +654,7 @@ local function optionalmetatables(...)
if select("#", ...) > 0 then
return ...
else
return {__jsontype = 'object'}, {__jsontype = 'array'}
return { __jsontype = "object" }, { __jsontype = "array" }
end
end
@@ -610,8 +666,8 @@ end
function json.use_lpeg()
local g = require("lpeg")
if type(g.version) == 'function' and g.version() == "0.11" then
error "due to a bug in LPeg 0.11, it cannot be used for JSON matching"
if type(g.version) == "function" and g.version() == "0.11" then
error("due to a bug in LPeg 0.11, it cannot be used for JSON matching")
end
local pegmatch = g.match
@@ -633,16 +689,16 @@ function json.use_lpeg ()
return ErrorCall(str, pos - 1, "unterminated " .. what, state)
end
local SingleLineComment = P"//" * (1 - S"\n\r")^0
local MultiLineComment = P"/*" * (1 - P"*/")^0 * P"*/"
local Space = (S" \n\r\t" + P"\239\187\191" + SingleLineComment + MultiLineComment)^0
local SingleLineComment = P("//") * (1 - S("\n\r")) ^ 0
local MultiLineComment = P("/*") * (1 - P("*/")) ^ 0 * P("*/")
local Space = (S(" \n\r\t") + P("\239\187\191") + SingleLineComment + MultiLineComment) ^ 0
local function ErrUnterminated(what)
return g.Cmt(g.Cc(what) * g.Carg(2), ErrorUnterminatedCall)
end
local PlainChar = 1 - S"\"\\\n\r"
local EscapeSequence = (P"\\" * g.C (S"\"\\/bfnrt" + Err "unsupported escape sequence")) / escapechars
local PlainChar = 1 - S('"\\\n\r')
local EscapeSequence = (P("\\") * g.C(S('"\\/bfnrt') + Err("unsupported escape sequence"))) / escapechars
local HexDigit = R("09", "af", "AF")
local function UTF16Surrogate(match, pos, high, low)
high, low = tonumber(high, 16), tonumber(low, 16)
@@ -655,15 +711,15 @@ function json.use_lpeg ()
local function UTF16BMP(hex)
return unichar(tonumber(hex, 16))
end
local U16Sequence = (P"\\u" * g.C (HexDigit * HexDigit * HexDigit * HexDigit))
local U16Sequence = (P("\\u") * g.C(HexDigit * HexDigit * HexDigit * HexDigit))
local UnicodeEscape = g.Cmt(U16Sequence * U16Sequence, UTF16Surrogate) + U16Sequence / UTF16BMP
local Char = UnicodeEscape + EscapeSequence + PlainChar
local String = P"\"" * (g.Cs (Char ^ 0) * P"\"" + ErrUnterminated "string")
local Integer = P"-"^(-1) * (P"0" + (R"19" * R"09"^0))
local Fractal = P"." * R"09"^0
local Exponent = (S"eE") * (S"+-")^(-1) * R"09"^1
local Number = (Integer * Fractal^(-1) * Exponent^(-1))/str2num
local Constant = P"true" * g.Cc (true) + P"false" * g.Cc (false) + P"null" * g.Carg (1)
local String = P('"') * (g.Cs(Char ^ 0) * P('"') + ErrUnterminated("string"))
local Integer = P("-") ^ -1 * (P("0") + (R("19") * R("09") ^ 0))
local Fractal = P(".") * R("09") ^ 0
local Exponent = (S("eE")) * (S("+-")) ^ -1 * R("09") ^ 1
local Number = (Integer * Fractal ^ -1 * Exponent ^ -1) / str2num
local Constant = P("true") * g.Cc(true) + P("false") * g.Cc(false) + P("null") * g.Carg(1)
local SimpleValue = Number + String + Constant
local ArrayContent, ObjectContent
@@ -676,15 +732,15 @@ function json.use_lpeg ()
local t, nt = {}, 0
repeat
obj, cont, npos = pegmatch(ArrayContent, str, pos, nullval, state)
if cont == 'end' then
if cont == "end" then
return ErrorUnterminatedCall(str, start, "array", state)
end
pos = npos
if cont == 'cont' or cont == 'last' then
if cont == "cont" or cont == "last" then
nt = nt + 1
t[nt] = obj
end
until cont ~= 'cont'
until cont ~= "cont"
return pos, setmetatable(t, state.arraymeta)
end
@@ -695,27 +751,34 @@ function json.use_lpeg ()
local t = {}
repeat
key, obj, cont, npos = pegmatch(ObjectContent, str, pos, nullval, state)
if cont == 'end' then
if cont == "end" then
return ErrorUnterminatedCall(str, start, "object", state)
end
pos = npos
if cont == 'cont' or cont == 'last' then
if cont == "cont" or cont == "last" then
t[key] = obj
end
until cont ~= 'cont'
until cont ~= "cont"
return pos, setmetatable(t, state.objectmeta)
end
local Array = P"[" * g.Cmt (g.Carg(1) * g.Carg(2), parsearray)
local Object = P"{" * g.Cmt (g.Carg(1) * g.Carg(2), parseobject)
local Array = P("[") * g.Cmt(g.Carg(1) * g.Carg(2), parsearray)
local Object = P("{") * g.Cmt(g.Carg(1) * g.Carg(2), parseobject)
local Value = Space * (Array + Object + SimpleValue)
local ExpectedValue = Value + Space * Err "value expected"
local ExpectedKey = String + Err "key expected"
local End = P(-1) * g.Cc'end'
local ErrInvalid = Err "invalid JSON"
ArrayContent = (Value * Space * (P"," * g.Cc'cont' + P"]" * g.Cc'last'+ End + ErrInvalid) + g.Cc(nil) * (P"]" * g.Cc'empty' + End + ErrInvalid)) * g.Cp()
local Pair = g.Cg (Space * ExpectedKey * Space * (P":" + Err "colon expected") * ExpectedValue)
ObjectContent = (g.Cc(nil) * g.Cc(nil) * P"}" * g.Cc'empty' + End + (Pair * Space * (P"," * g.Cc'cont' + P"}" * g.Cc'last' + End + ErrInvalid) + ErrInvalid)) * g.Cp()
local ExpectedValue = Value + Space * Err("value expected")
local ExpectedKey = String + Err("key expected")
local End = P(-1) * g.Cc("end")
local ErrInvalid = Err("invalid JSON")
ArrayContent = (
Value * Space * (P(",") * g.Cc("cont") + P("]") * g.Cc("last") + End + ErrInvalid)
+ g.Cc(nil) * (P("]") * g.Cc("empty") + End + ErrInvalid)
) * g.Cp()
local Pair = g.Cg(Space * ExpectedKey * Space * (P(":") + Err("colon expected")) * ExpectedValue)
ObjectContent = (
g.Cc(nil) * g.Cc(nil) * P("}") * g.Cc("empty")
+ End
+ (Pair * Space * (P(",") * g.Cc("cont") + P("}") * g.Cc("last") + End + ErrInvalid) + ErrInvalid)
) * g.Cp()
local DecodeValue = ExpectedValue * g.Cp()
jsonlpeg.version = json.version
@@ -738,7 +801,9 @@ function json.use_lpeg ()
end
-- cache result of this function:
json.use_lpeg = function () return jsonlpeg end
json.use_lpeg = function()
return jsonlpeg
end
jsonlpeg.use_lpeg = json.use_lpeg
return jsonlpeg
@@ -749,4 +814,3 @@ if always_use_lpeg then
end
return json

View File

@@ -4,7 +4,8 @@ local M = {}
M.__index = M
function M.fes(header, footer)
local config = {} local site_config = {}
local config = {}
local site_config = {}
local fes_mod = package.loaded.fes
if fes_mod and fes_mod.config then
config = fes_mod.config
@@ -311,7 +312,7 @@ em, i { font-style: italic; }
</body>
</html>
]],
parts = {}
parts = {},
}
return setmetatable(self, M)
@@ -336,7 +337,11 @@ function M:build()
local header = self.header
header = header:gsub("{{TITLE}}", self.title or "Document")
local favicon_html = self.favicon and ('<link rel="icon" type="image/x-icon" href="' .. self.favicon .. '">')
header = header:gsub("{{FAVICON}}", favicon_html or [[<link rel="icon" href="data:image/svg+xml,<svg xmlns=%%22http://www.w3.org/2000/svg%%22 viewBox=%%220 0 100 100%%22><text y=%%22.9em%%22 font-size=%%2290%%22>🔥</text></svg>">]])
header = header:gsub(
"{{FAVICON}}",
favicon_html
or [[<link rel="icon" href="data:image/svg+xml,<svg xmlns=%%22http://www.w3.org/2000/svg%%22 viewBox=%%220 0 100 100%%22><text y=%%22.9em%%22 font-size=%%2290%%22>🔥</text></svg>">]]
)
local footer = self.footer:gsub("{{COPYRIGHT}}", self.copyright or "&#169; The Copyright Holder")
return header .. table.concat(self.parts, "\n") .. footer
end

View File

@@ -19,29 +19,29 @@ end
function M.a(link, str)
link = link or "https://example.com"
str = str or link
return "<a href=\"" .. link .. "\">" .. str .. "</a>"
return '<a href="' .. link .. '">' .. str .. "</a>"
end
function M.ha(link, str)
link = link or "https://example.com"
str = str or link
return "<a class=\"hidden\" href=\"" .. link .. "\">" .. str .. "</a>"
return '<a class="hidden" href="' .. link .. '">' .. str .. "</a>"
end
function M.external(link, str)
return "<a target=\"_blank\" href=\"" .. link .. "\">" .. str .. "</a>"
return '<a target="_blank" href="' .. link .. '">' .. str .. "</a>"
end
function M.note(str)
return '<div class="note">' .. str .. '</div>'
return '<div class="note">' .. str .. "</div>"
end
function M.muted(str)
return '<div class="muted">' .. str .. '</div>'
return '<div class="muted">' .. str .. "</div>"
end
function M.callout(str)
return '<div class="callout">' .. str .. '</div>'
return '<div class="callout">' .. str .. "</div>"
end
function M.h1(str)
@@ -55,7 +55,8 @@ end
function M.h3(str)
return "<h3>" .. (str or "") .. "</h3>"
end
function M.h4(str) return "<h4>" .. (str or "") .. "</h4>"
function M.h4(str)
return "<h4>" .. (str or "") .. "</h4>"
end
function M.h5(str)

View File

@@ -25,7 +25,8 @@ var documentation string
func init() {
config.Port = flag.Int("p", 3000, "Set the server port")
config.Color = flag.Bool("no-color", false, "Disable color output")
config.Static = flag.Bool("static", false, "Render and save all pages.")
config.Static = flag.Bool("static", false, "Render and save all pages")
config.Docker = flag.Bool("docker", false, "Create a docker project")
config.Lib = lib
config.Doc = documentation
}

View File

@@ -10,6 +10,7 @@ var Doc string
var Port *int
var Color *bool
var Static *bool
var Docker *bool
type AppConfig struct {
App struct {

View File

@@ -1,6 +1,7 @@
package new
import (
"fes/modules/config"
"fmt"
"os"
"os/exec"
@@ -26,7 +27,7 @@ func getName() string {
}
/* helper function for writing files */
func write(path string, format string, args ...interface{}) error {
func write(path string, format string, args ...any) error {
dir := filepath.Dir(path)
if err := os.MkdirAll(dir, 0755); err != nil {
panic(err)
@@ -49,6 +50,22 @@ func Project(dir string) error {
return err
}
if *config.Docker {
write("docker-compose.yml", `services:
%s:
image: git.vxserver.dev/fsd/fes:latest
ports:
- "3000:3000"
volumes:
- ./app:/app`, dir)
if err := os.Mkdir("app", 0755); err != nil {
return err
}
if err := os.Chdir("app"); err != nil {
return err
}
}
name := getName()
write("www/index.lua", `local fes = require("fes")
@@ -64,5 +81,38 @@ return site`, name)
name = "%s"
version = "0.0.1"
authors = ["%s"]`, dir, name)
write("README.md", strings.ReplaceAll(`# %s
$$$$$$
fes new %s
$$$$$$
> **Know what you are doing?** Delete this file. Have fun!
## Project Structure
Inside your Fes project, you'll see the following directories and files:
$$$$$$
.
├── Fes.toml
├── README.md
└── www
└── index.lua
$$$$$$
Fes looks for $$.lua$$ files in the $$www/$$ directory. Each file is exposed as a route based on its file name.
## Commands
All commands are run from the root of the project, from a terminal:
| Command | Action |
| :------------------------ | :----------------------------------------------- |
| $$fes run .$$ | Runs the project at $$.$$ |
## What to learn more?
Check out [Fes's docs](https://docs.vxserver.dev/static/fes.html).`, "$$", "`"), dir, dir)
return nil
}

View File

@@ -426,6 +426,7 @@ func Start(dir string) error {
} else if strings.HasSuffix(route, ".md") {
data, err = os.ReadFile(route)
data = []byte(markdownToHTML(string(data)))
data = []byte("<style>body {max-width: 80ch;}</style>\n" + string(data))
} else {
data, err = os.ReadFile(route)
}
@@ -438,5 +439,5 @@ func Start(dir string) error {
})
fmt.Printf("Server is running on http://localhost:%d\n", *config.Port)
return http.ListenAndServe(fmt.Sprintf(":%d", *config.Port), nil)
return http.ListenAndServe(fmt.Sprintf("0.0.0.0:%d", *config.Port), nil)
}