4 Commits

Author SHA1 Message Date
2b2be7b4e7 I will save this but will probably change this 2025-12-29 17:11:28 -05:00
99c41a0d60 write stubs 2025-12-28 20:52:31 -05:00
1c229f1b3e update examples 2025-12-28 20:17:46 -05:00
5a733b8642 create default site 2025-12-28 17:07:08 -05:00
30 changed files with 607 additions and 65 deletions

8
.luarc.json Normal file
View File

@@ -0,0 +1,8 @@
{
"workspace.library": [
"./lsp"
],
"diagnostics.disable": [
"undefined-global"
]
}

View File

@@ -1,7 +0,0 @@
local foo = {}
foo.render = function()
return "This was called from a foo function"
end
return foo

View File

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

View File

@@ -0,0 +1,4 @@
# archive
This example demonstrates the archive feature of Fes it is useful for file
sharing purposes.

View File

Before

Width:  |  Height:  |  Size: 1.8 MiB

After

Width:  |  Height:  |  Size: 1.8 MiB

View File

@@ -1,17 +1,10 @@
local fes = require("fes")
local std = fes.std
local site = fes.fes()
site.copyright = fes.util.copyright("https://fsd.vxserver.dev", "fSD")
site:h1("Hello, World!")
site:note(fes.util.cc {
std.h2("Files"),
std.ul {
std.a("/archive", "to the file room!"),
}
})
site:a("/archive", fes.std.h2("To the file room!"))
return site

View File

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

23
examples/best/README.md Normal file
View File

@@ -0,0 +1,23 @@
# best
This is an example of best practices for the Fes framework.
## Parts
With best practice we can break our sites into a few parts.
## Index
The main page of the site loads in the header and the footer, as well as shows
some core information
## Include
Within include the header and footer are defined.
* **Header:** Site navigation and name display
* **Footer:** Extra and external information.
## Static
This is where we store our favicon.

View File

@@ -0,0 +1,13 @@
local footer = {}
footer.render = function(std)
return table.concat({
std.h2("Other resources"),
std.tl({
std.external("https://git.vxserver.dev/fSD/fes", "Fes source"),
std.external("https://docs.vxserver.dev/static/fes.html", "Documentation"),
}),
})
end
return footer

View File

@@ -0,0 +1,7 @@
local header = {}
header.render = function(std)
return std.center(std.ha("/", std.h1("Best Practices")))
end
return header

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,20 @@
local fes = require("fes")
local std = fes.std
local u = fes.util
local site = fes.fes()
site.copyright = fes.util.copyright("https://fsd.vxserver.dev", "fSD")
site.title = "Best practices"
site.favicon = "/static/favicon.ico"
site:banner(fes.app.header.render(std))
site:note(u.cc {
std.h2("Hello, World!"),
std.p("This is an example of the best practices/canonical Fes site.")
})
site:note(fes.app.footer.render(std))
return site

View File

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

View File

@@ -0,0 +1,33 @@
# default
```
fes new default
```
> **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).

6
examples/error/README.md Normal file
View File

@@ -0,0 +1,6 @@
# error
This shows what a Lua error looks like to the user. Lua errors are the most
common and the most critical so that is why they are shown to the user. Other,
lesser errors, are only shown to the developer because of their different
nature.

View File

@@ -1,6 +1,10 @@
local fes = require("fes")
local site = fes.fes()
site.copyright = fes.util.copyright("https://fsd.vxserver.dev", "fSD")
This is what an error looks like
site:h1("Hello, World!")
return site

View File

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

4
examples/hello/README.md Normal file
View File

@@ -0,0 +1,4 @@
# hello
This is a very simple hello world program, the only difference between this and
default is this README.

View File

@@ -0,0 +1,3 @@
# markdown
This example demonstrate Fes's ability to handle markdown routes.

View File

@@ -1 +1,3 @@
# Hello, World!
# Markdown!
**Fes** also supports markdown routes!

View File

@@ -0,0 +1,5 @@
# simple
This simple example shows the extensibility of the Fes framework. It shows the
you do not necessarily need to use the site object (although it is recommended)
you can define your own site, similar to how Lisps do things.

31
lsp/fes.lua Normal file
View File

@@ -0,0 +1,31 @@
---@meta
---@class Fes
---@field version string|nil
---@field title string|nil
---@field copyright string|nil
---@field favicon string|nil
---@field header string
---@field footer string
---@field parts string[]
---@field [string] fun(self: Fes, ...: any): Fes
local Fes = {}
Fes.__index = Fes
---@param header? string
---@param footer? string
---@return Fes
function Fes.fes(header, footer) end
---@param html string
---@return Fes
function Fes:custom(html) end
---@return string
function Fes:build() end
---@return string
function Fes:__tostring() end
return Fes

155
lsp/std.lua Normal file
View File

@@ -0,0 +1,155 @@
---@meta
---@class FesStd
local M = {}
---@return string
function M.fes_version() end
---@return string
function M.site_version() end
---@param link? string
---@param str? string
---@return string
function M.a(link, str) end
---@param link? string
---@param str? string
---@return string
function M.ha(link, str) end
---@param link string
---@param str string
---@return string
function M.external(link, str) end
---@param str string
---@return string
function M.note(str) end
---@param str string
---@return string
function M.muted(str) end
---@param str string
---@return string
function M.callout(str) end
---@param str? string
---@return string
function M.h1(str) end
---@param str? string
---@return string
function M.h2(str) end
---@param str? string
---@return string
function M.h3(str) end
---@param str? string
---@return string
function M.h4(str) end
---@param str? string
---@return string
function M.h5(str) end
---@param str? string
---@return string
function M.h6(str) end
---@param str? string
---@return string
function M.p(str) end
---@param str? string
---@return string
function M.pre(str) end
---@param str? string
---@return string
function M.code(str) end
---@param items? any[]
---@return string
function M.ul(items) end
---@param items? any[]
---@return string
function M.ol(items) end
---@param items? any[]
---@return string
function M.tl(items) end
---@param str? string
---@return string
function M.blockquote(str) end
---@return string
function M.hr() end
---@param src? string
---@param alt? string
---@return string
function M.img(src, alt) end
---@param str? string
---@return string
function M.strong(str) end
---@param str? string
---@return string
function M.em(str) end
---@return string
function M.br() end
---@param content? string
---@param class? string
---@return string
function M.div(content, class) end
---@param content? string
---@param class? string
---@return string
function M.span(content, class) end
---@param str any
---@return string
function M.escape(str) end
---@return string
function M.site_name() end
---@return string
function M.site_title() end
---@return any[]
function M.site_authors() end
---@param str? string
---@return string
function M.highlight(str) end
---@param str? string
---@return string
function M.banner(str) end
---@param str? string
---@return string
function M.center(str) end
---@param link? string
---@param str? string
---@return string
function M.nav(link, str) end
---@param right? string
---@param left? string
---@return string
function M.rl(right, left) end
return M

20
lsp/symbol.lua Normal file
View File

@@ -0,0 +1,20 @@
---@meta
---@class FesSymbol
local M = {}
--@type string
--@readonly
M.copyright = "©"
--@type string
--@readonly
M.registered_trademark = "®"
--@type string
--@readonly
M.trademark = "™"
return M

15
lsp/util.lua Normal file
View File

@@ -0,0 +1,15 @@
---@meta
---@class CopyrightUtil
local M = {}
---@param tbl string[]
---@return string
function M.cc(tbl) end
---@param link string
---@param holder string
---@return string
function M.copyright(link, holder) end
return M

64
main.go
View File

@@ -11,6 +11,7 @@ import (
"fes/modules/config"
"fes/modules/doc"
"fes/modules/lsp"
"fes/modules/new"
"fes/modules/server"
"fes/modules/version"
@@ -19,6 +20,9 @@ import (
//go:embed lib/*
var lib embed.FS
//go:embed lsp/*
var lspStubs embed.FS
//go:embed index.html
var documentation string
@@ -27,20 +31,23 @@ func init() {
config.Color = flag.Bool("no-color", false, "Disable color output")
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.LspStubs = lspStubs
config.Doc = documentation
}
func main() {
flag.Usage = func() {
fmt.Fprintf(flag.CommandLine.Output(), "Usage: %s [options] <command> <project_dir>\n", os.Args[0])
fmt.Fprintf(os.Stderr, "Commands:")
fmt.Fprintf(os.Stderr, " new <project_dir> Create a new project")
fmt.Fprintf(os.Stderr, " doc Open documentation")
fmt.Fprintf(os.Stderr, " run <project_dir> Start the server")
fmt.Fprintf(os.Stderr, "Options:")
fmt.Fprintln(flag.CommandLine.Output(), "Commands:")
fmt.Fprintln(flag.CommandLine.Output(), " new <project_dir> Create a new project")
fmt.Fprintln(flag.CommandLine.Output(), " doc Open documentation")
fmt.Fprintln(flag.CommandLine.Output(), " run <project_dir> Start the server")
fmt.Fprintln(flag.CommandLine.Output(), " lsp <sub_command> Work with Lsp")
fmt.Fprintln(flag.CommandLine.Output(), "Options:")
flag.PrintDefaults()
fmt.Fprintf(os.Stderr, "For bug reports, contact a developer and describe the issue. Provide the output of the `-V1` flag.")
fmt.Fprintln(flag.CommandLine.Output(), "For bug reports, contact a developer and describe the issue. Provide the output of the `-V1` flag.")
}
showVersion := flag.Bool("version", false, "Show version and exit")
@@ -67,32 +74,39 @@ func main() {
}
cmd := args[0]
var dir string
if cmd == "new" || cmd == "run" {
if len(args) < 2 {
fmt.Fprintf(os.Stderr, "Error: %s requires <project_dir>\n", cmd)
flag.Usage()
os.Exit(1)
}
dir = args[1]
}
var arg string
switch cmd {
case "new":
if err := new.Project(dir); err != nil {
fmt.Fprintln(os.Stderr, "Error:", err)
os.Exit(1)
}
case "doc":
if cmd == "doc" {
if err := doc.Open(); err != nil {
fmt.Fprintln(os.Stderr, "Error:", err)
os.Exit(1)
}
os.Exit(0)
}
if len(args) < 2 {
fmt.Fprintln(os.Stderr, "Error: not enough arguments")
flag.Usage()
os.Exit(1)
}
arg = args[1]
switch cmd {
case "new":
if err := new.Project(arg); err != nil {
fmt.Fprintln(os.Stderr, "Error:", err)
os.Exit(1)
}
case "lsp":
if err := lsp.Do(arg); err != nil {
fmt.Fprintln(os.Stderr, "Error:", err)
os.Exit(1)
}
case "run":
if err := server.Start(dir); err != nil {
if err := server.Start(arg); err != nil {
if errors.Is(err, os.ErrNotExist) {
fmt.Fprintf(os.Stderr, "%s does not exist\n", dir)
fmt.Fprintf(os.Stderr, "Try: fes new %s\n", dir)
fmt.Fprintf(os.Stderr, "%s does not exist\n", arg)
fmt.Fprintf(os.Stderr, "Try: fes new %s\n", arg)
os.Exit(1)
} else {
fmt.Fprintln(os.Stderr, "Error:", err)

View File

@@ -6,6 +6,8 @@ import (
)
var Lib embed.FS
var LspStubs embed.FS
var Doc string
var Port *int
var Color *bool

173
modules/lsp/lsp.go Normal file
View File

@@ -0,0 +1,173 @@
package lsp
import (
"embed"
"errors"
"flag"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"fes/modules/config"
)
var destRoot string = filepath.Join(func () string {
res, err := os.UserHomeDir()
if err != nil { panic(err) }
return res
}(), ".local", "LuaAddons", "fes")
func usage(status int) {
fmt.Fprintln(flag.CommandLine.Output(), "Lsp Usage:")
fmt.Fprintln(flag.CommandLine.Output(), " help Print this help and exit")
fmt.Fprintln(flag.CommandLine.Output(), " install Install the Lsp")
fmt.Fprintln(flag.CommandLine.Output(), " uninstall Uninstall the Lsp")
fmt.Fprintln(flag.CommandLine.Output(), " doctor Verify Lsp installation")
os.Exit(status)
}
func writeFile(fs embed.FS, src, dst string) error {
in, err := fs.Open(src)
if err != nil {
return err
}
defer in.Close()
if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil {
return err
}
out, err := os.Create(dst)
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, in)
return err
}
func install() error {
if err := os.RemoveAll(destRoot); err != nil {
return err
}
var found bool
err := fsWalkDir(config.LspStubs, "lsp", func(path string) error {
if path == "lsp" {
return nil
}
rel := strings.TrimPrefix(path, "lsp/")
dst := filepath.Join("library", destRoot, rel)
info, err := fsStat(config.LspStubs, path)
if err != nil {
return err
}
if info.IsDir() {
return os.MkdirAll(dst, 0755)
}
found = true
return writeFile(config.LspStubs, path, dst)
})
if err != nil {
return err
}
if !found {
return errors.New("no lsp stubs found in embedded fs")
}
err = os.WriteFile(filepath.Join("dstRoot", "config.json"), []byte(""), 0755)
fmt.Println("Lua LSP installed to:", destRoot)
return nil
}
func uninstall() error {
if _, err := os.Stat(destRoot); err != nil {
if os.IsNotExist(err) {
fmt.Println("Lua LSP not installed")
return nil
}
return err
}
if err := os.RemoveAll(destRoot); err != nil {
return err
}
fmt.Println("Lua LSP uninstalled from:", destRoot)
return nil
}
func doctor() error {
info, err := os.Stat(destRoot)
if err != nil {
if os.IsNotExist(err) {
return errors.New("lsp not installed")
}
return err
}
if !info.IsDir() {
return errors.New("lsp install path is not a directory")
}
var luaFiles int
err = filepath.Walk(destRoot, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() && strings.HasSuffix(info.Name(), ".lua") {
luaFiles++
}
return nil
})
if err != nil {
return err
}
if luaFiles == 0 {
return errors.New("no lua stubs found in lsp directory")
}
fmt.Println("LSP stubs: OK")
fmt.Println("Location:", destRoot)
fmt.Println()
fmt.Println("LuaLS configuration required:")
fmt.Println()
fmt.Println(` "Lua.workspace.library": {`)
fmt.Printf(` "%s": true`, destRoot)
fmt.Println()
fmt.Println(` }`)
fmt.Println()
fmt.Println("Restart your editor after installing or updating the LSP stubs")
return nil
}
func Do(arg string) error {
switch arg {
case "help":
usage(0)
case "install":
return install()
case "uninstall":
return uninstall()
case "doctor":
return doctor()
default:
fmt.Fprintf(os.Stderr, "Unknown command: %s\n", arg)
usage(1)
}
return nil
}

36
modules/lsp/util.go Normal file
View File

@@ -0,0 +1,36 @@
package lsp
import (
"embed"
"os"
"path/filepath"
)
func fsWalkDir(fs embed.FS, root string, fn func(string) error) error {
entries, err := fs.ReadDir(root)
if err != nil {
return err
}
for _, e := range entries {
p := filepath.Join(root, e.Name())
if err := fn(p); err != nil {
return err
}
if e.IsDir() {
if err := fsWalkDir(fs, p, fn); err != nil {
return err
}
}
}
return nil
}
func fsStat(fs embed.FS, path string) (os.FileInfo, error) {
f, err := fs.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
return f.Stat()
}