Compare commits
7 Commits
new-defaul
...
lsp
| Author | SHA1 | Date | |
|---|---|---|---|
| 2b2be7b4e7 | |||
| 99c41a0d60 | |||
| 1c229f1b3e | |||
| 5a733b8642 | |||
| 11ab1630be | |||
| 5fabd0233d | |||
| c43e905729 |
8
.luarc.json
Normal file
8
.luarc.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"workspace.library": [
|
||||||
|
"./lsp"
|
||||||
|
],
|
||||||
|
"diagnostics.disable": [
|
||||||
|
"undefined-global"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
local foo = {}
|
|
||||||
|
|
||||||
foo.render = function()
|
|
||||||
return "This was called from a foo function"
|
|
||||||
end
|
|
||||||
|
|
||||||
return foo
|
|
||||||
@@ -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
|
|
||||||
4
examples/archive/README.md
Normal file
4
examples/archive/README.md
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# archive
|
||||||
|
|
||||||
|
This example demonstrates the archive feature of Fes it is useful for file
|
||||||
|
sharing purposes.
|
||||||
|
Before Width: | Height: | Size: 1.8 MiB After Width: | Height: | Size: 1.8 MiB |
@@ -1,17 +1,10 @@
|
|||||||
local fes = require("fes")
|
local fes = require("fes")
|
||||||
local std = fes.std
|
|
||||||
|
|
||||||
local site = fes.fes()
|
local site = fes.fes()
|
||||||
|
|
||||||
site.copyright = fes.util.copyright("https://fsd.vxserver.dev", "fSD")
|
site.copyright = fes.util.copyright("https://fsd.vxserver.dev", "fSD")
|
||||||
|
|
||||||
site:h1("Hello, World!")
|
site:h1("Hello, World!")
|
||||||
|
|
||||||
site:note(fes.util.cc {
|
site:a("/archive", fes.std.h2("To the file room!"))
|
||||||
std.h2("Files"),
|
|
||||||
std.ul {
|
|
||||||
std.a("/archive", "to the file room!"),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return site
|
return site
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
[app]
|
[app]
|
||||||
|
|
||||||
name = "hello"
|
name = "best"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
authors = ["vx-clutch"]
|
authors = ["vx-clutch"]
|
||||||
23
examples/best/README.md
Normal file
23
examples/best/README.md
Normal 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.
|
||||||
13
examples/best/include/footer.lua
Normal file
13
examples/best/include/footer.lua
Normal 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
|
||||||
7
examples/best/include/header.lua
Normal file
7
examples/best/include/header.lua
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
local header = {}
|
||||||
|
|
||||||
|
header.render = function(std)
|
||||||
|
return std.center(std.ha("/", std.h1("Best Practices")))
|
||||||
|
end
|
||||||
|
|
||||||
|
return header
|
||||||
BIN
examples/best/static/favicon.ico
Normal file
BIN
examples/best/static/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
20
examples/best/www/index.lua
Normal file
20
examples/best/www/index.lua
Normal 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
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
[app]
|
[app]
|
||||||
|
|
||||||
name = "advanced"
|
name = "default"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
authors = ["vx-clutch"]
|
authors = ["vx-clutch"]
|
||||||
33
examples/default/README.md
Normal file
33
examples/default/README.md
Normal 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
6
examples/error/README.md
Normal 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.
|
||||||
@@ -1,6 +1,10 @@
|
|||||||
local fes = require("fes")
|
local fes = require("fes")
|
||||||
local site = fes.fes()
|
local site = fes.fes()
|
||||||
|
|
||||||
|
site.copyright = fes.util.copyright("https://fsd.vxserver.dev", "fSD")
|
||||||
|
|
||||||
This is what an error looks like
|
This is what an error looks like
|
||||||
|
|
||||||
|
site:h1("Hello, World!")
|
||||||
|
|
||||||
return site
|
return site
|
||||||
|
|||||||
@@ -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
4
examples/hello/README.md
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# hello
|
||||||
|
|
||||||
|
This is a very simple hello world program, the only difference between this and
|
||||||
|
default is this README.
|
||||||
3
examples/markdown/README.md
Normal file
3
examples/markdown/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# markdown
|
||||||
|
|
||||||
|
This example demonstrate Fes's ability to handle markdown routes.
|
||||||
@@ -1 +1,3 @@
|
|||||||
# Hello, World!
|
# Markdown!
|
||||||
|
|
||||||
|
**Fes** also supports markdown routes!
|
||||||
|
|||||||
5
examples/simple/README.md
Normal file
5
examples/simple/README.md
Normal 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
31
lsp/fes.lua
Normal 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
155
lsp/std.lua
Normal 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
20
lsp/symbol.lua
Normal 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
15
lsp/util.lua
Normal 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
64
main.go
@@ -11,6 +11,7 @@ import (
|
|||||||
|
|
||||||
"fes/modules/config"
|
"fes/modules/config"
|
||||||
"fes/modules/doc"
|
"fes/modules/doc"
|
||||||
|
"fes/modules/lsp"
|
||||||
"fes/modules/new"
|
"fes/modules/new"
|
||||||
"fes/modules/server"
|
"fes/modules/server"
|
||||||
"fes/modules/version"
|
"fes/modules/version"
|
||||||
@@ -19,6 +20,9 @@ import (
|
|||||||
//go:embed lib/*
|
//go:embed lib/*
|
||||||
var lib embed.FS
|
var lib embed.FS
|
||||||
|
|
||||||
|
//go:embed lsp/*
|
||||||
|
var lspStubs embed.FS
|
||||||
|
|
||||||
//go:embed index.html
|
//go:embed index.html
|
||||||
var documentation string
|
var documentation string
|
||||||
|
|
||||||
@@ -27,20 +31,23 @@ func init() {
|
|||||||
config.Color = flag.Bool("no-color", false, "Disable color output")
|
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.Docker = flag.Bool("docker", false, "Create a docker project")
|
||||||
|
|
||||||
config.Lib = lib
|
config.Lib = lib
|
||||||
|
config.LspStubs = lspStubs
|
||||||
config.Doc = documentation
|
config.Doc = documentation
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
flag.Usage = func() {
|
flag.Usage = func() {
|
||||||
fmt.Fprintf(flag.CommandLine.Output(), "Usage: %s [options] <command> <project_dir>\n", os.Args[0])
|
fmt.Fprintf(flag.CommandLine.Output(), "Usage: %s [options] <command> <project_dir>\n", os.Args[0])
|
||||||
fmt.Println("Commands:")
|
fmt.Fprintln(flag.CommandLine.Output(), "Commands:")
|
||||||
fmt.Println(" new <project_dir> Create a new project")
|
fmt.Fprintln(flag.CommandLine.Output(), " new <project_dir> Create a new project")
|
||||||
fmt.Println(" doc Open documentation")
|
fmt.Fprintln(flag.CommandLine.Output(), " doc Open documentation")
|
||||||
fmt.Println(" run <project_dir> Start the server")
|
fmt.Fprintln(flag.CommandLine.Output(), " run <project_dir> Start the server")
|
||||||
fmt.Println("Options:")
|
fmt.Fprintln(flag.CommandLine.Output(), " lsp <sub_command> Work with Lsp")
|
||||||
|
fmt.Fprintln(flag.CommandLine.Output(), "Options:")
|
||||||
flag.PrintDefaults()
|
flag.PrintDefaults()
|
||||||
fmt.Println("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")
|
showVersion := flag.Bool("version", false, "Show version and exit")
|
||||||
@@ -67,32 +74,39 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cmd := args[0]
|
cmd := args[0]
|
||||||
var dir string
|
var arg 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]
|
|
||||||
}
|
|
||||||
|
|
||||||
switch cmd {
|
if cmd == "doc" {
|
||||||
case "new":
|
|
||||||
if err := new.Project(dir); err != nil {
|
|
||||||
fmt.Fprintln(os.Stderr, "Error:", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
case "doc":
|
|
||||||
if err := doc.Open(); err != nil {
|
if err := doc.Open(); err != nil {
|
||||||
fmt.Fprintln(os.Stderr, "Error:", err)
|
fmt.Fprintln(os.Stderr, "Error:", err)
|
||||||
os.Exit(1)
|
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":
|
case "run":
|
||||||
if err := server.Start(dir); err != nil {
|
if err := server.Start(arg); err != nil {
|
||||||
if errors.Is(err, os.ErrNotExist) {
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
fmt.Fprintf(os.Stderr, "%s does not exist\n", dir)
|
fmt.Fprintf(os.Stderr, "%s does not exist\n", arg)
|
||||||
fmt.Fprintf(os.Stderr, "Try: fes new %s\n", dir)
|
fmt.Fprintf(os.Stderr, "Try: fes new %s\n", arg)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprintln(os.Stderr, "Error:", err)
|
fmt.Fprintln(os.Stderr, "Error:", err)
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var Lib embed.FS
|
var Lib embed.FS
|
||||||
|
var LspStubs embed.FS
|
||||||
|
|
||||||
var Doc string
|
var Doc string
|
||||||
var Port *int
|
var Port *int
|
||||||
var Color *bool
|
var Color *bool
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/pkg/browser"
|
"github.com/pkg/browser"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/* open documentation in browser */
|
||||||
func Open() error {
|
func Open() error {
|
||||||
fmt.Println("Opening documentation in browser")
|
fmt.Println("Opening documentation in browser")
|
||||||
|
|
||||||
|
|||||||
173
modules/lsp/lsp.go
Normal file
173
modules/lsp/lsp.go
Normal 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
36
modules/lsp/util.go
Normal 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()
|
||||||
|
}
|
||||||
@@ -17,11 +17,21 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/* this is the request data we pass over the bus to the application, via the fes.bus interface */
|
||||||
type reqData struct {
|
type reqData struct {
|
||||||
path string
|
path string
|
||||||
params map[string]string
|
params map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* performs relavent handling based on the directory passaed
|
||||||
|
*
|
||||||
|
* Special directories
|
||||||
|
* - www/ <= contains lua routes.
|
||||||
|
* - static/ <= static content accessable at /static/path or /static/dir/path.
|
||||||
|
* - include/ <= globally accessable lua functions, cannot directly access "fes" right now.
|
||||||
|
* - archive/ <= contains user facing files such as archives or dists.
|
||||||
|
*
|
||||||
|
*/
|
||||||
func handleDir(entries []os.DirEntry, dir string, routes map[string]string, base string, isStatic bool) error {
|
func handleDir(entries []os.DirEntry, dir string, routes map[string]string, base string, isStatic bool) error {
|
||||||
for _, entry := range entries {
|
for _, entry := range entries {
|
||||||
path := filepath.Join(dir, entry.Name())
|
path := filepath.Join(dir, entry.Name())
|
||||||
@@ -59,6 +69,7 @@ func handleDir(entries []os.DirEntry, dir string, routes map[string]string, base
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(vx-clutch): this should not be a function
|
||||||
func loadIncludeModules(L *lua.LState, includeDir string) *lua.LTable {
|
func loadIncludeModules(L *lua.LState, includeDir string) *lua.LTable {
|
||||||
app := L.NewTable()
|
app := L.NewTable()
|
||||||
ents, err := os.ReadDir(includeDir)
|
ents, err := os.ReadDir(includeDir)
|
||||||
@@ -94,7 +105,8 @@ func loadIncludeModules(L *lua.LState, includeDir string) *lua.LTable {
|
|||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadLua(entry string, cfg *config.AppConfig, requestData reqData) ([]byte, error) {
|
/* renders the given lua route */
|
||||||
|
func renderRoute(entry string, cfg *config.AppConfig, requestData reqData) ([]byte, error) {
|
||||||
L := lua.NewState()
|
L := lua.NewState()
|
||||||
defer L.Close()
|
defer L.Close()
|
||||||
|
|
||||||
@@ -219,6 +231,7 @@ func loadLua(entry string, cfg *config.AppConfig, requestData reqData) ([]byte,
|
|||||||
return []byte(""), nil
|
return []byte(""), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* this indexes and generate the page for viewing the archive directory */
|
||||||
func generateArchiveIndex(fsPath string, urlPath string) (string, error) {
|
func generateArchiveIndex(fsPath string, urlPath string) (string, error) {
|
||||||
info, err := os.Stat(fsPath)
|
info, err := os.Stat(fsPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -309,6 +322,9 @@ func generateArchiveIndex(fsPath string, urlPath string) (string, error) {
|
|||||||
return b.String(), nil
|
return b.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* generates the data for the not found page. Checks for user-defined source in this order
|
||||||
|
* 404.lua => 404.md => 404.html => default.
|
||||||
|
*/
|
||||||
func generateNotFoundData(cfg *config.AppConfig) []byte {
|
func generateNotFoundData(cfg *config.AppConfig) []byte {
|
||||||
notFoundData := []byte(`
|
notFoundData := []byte(`
|
||||||
<html>
|
<html>
|
||||||
@@ -320,9 +336,13 @@ func generateNotFoundData(cfg *config.AppConfig) []byte {
|
|||||||
</html>
|
</html>
|
||||||
`)
|
`)
|
||||||
if _, err := os.Stat(filepath.Join("www", "404.lua")); err == nil {
|
if _, err := os.Stat(filepath.Join("www", "404.lua")); err == nil {
|
||||||
if nf, err := loadLua("www/404.lua", cfg, reqData{}); err == nil {
|
if nf, err := renderRoute("www/404.lua", cfg, reqData{}); err == nil {
|
||||||
notFoundData = nf
|
notFoundData = nf
|
||||||
}
|
}
|
||||||
|
} else if _, err := os.Stat("www/404.md"); err == nil {
|
||||||
|
if buf, err := os.ReadFile("www/404.html"); err == nil {
|
||||||
|
notFoundData = []byte(markdownToHTML(string(buf)))
|
||||||
|
}
|
||||||
} else if _, err := os.Stat("www/404.html"); err == nil {
|
} else if _, err := os.Stat("www/404.html"); err == nil {
|
||||||
if buf, err := os.ReadFile("www/404.html"); err == nil {
|
if buf, err := os.ReadFile("www/404.html"); err == nil {
|
||||||
notFoundData = buf
|
notFoundData = buf
|
||||||
@@ -331,6 +351,7 @@ func generateNotFoundData(cfg *config.AppConfig) []byte {
|
|||||||
return notFoundData
|
return notFoundData
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* helper to load all special directories */
|
||||||
func loadDirs() map[string]string {
|
func loadDirs() map[string]string {
|
||||||
routes := make(map[string]string)
|
routes := make(map[string]string)
|
||||||
|
|
||||||
@@ -355,6 +376,7 @@ func loadDirs() map[string]string {
|
|||||||
return routes
|
return routes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* helper to parse the Fes.toml and generate config */
|
||||||
func parseConfig() config.AppConfig {
|
func parseConfig() config.AppConfig {
|
||||||
tomlDocument, err := os.ReadFile("Fes.toml")
|
tomlDocument, err := os.ReadFile("Fes.toml")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -372,15 +394,21 @@ func parseConfig() config.AppConfig {
|
|||||||
return cfg
|
return cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
func readArchive(w http.ResponseWriter, route string) {
|
/* helper to read the archive files */
|
||||||
|
func readArchive(w http.ResponseWriter, route string) error {
|
||||||
fsPath := "." + route
|
fsPath := "." + route
|
||||||
if info, err := os.Stat(fsPath); err == nil && info.IsDir() {
|
if info, err := os.Stat(fsPath); err == nil && info.IsDir() {
|
||||||
if page, err := generateArchiveIndex(fsPath, route); err == nil {
|
if page, err := generateArchiveIndex(fsPath, route); err == nil {
|
||||||
w.Write([]byte(page))
|
w.Write([]byte(page))
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* start the Fes server */
|
||||||
func Start(dir string) error {
|
func Start(dir string) error {
|
||||||
if err := os.Chdir(dir); err != nil {
|
if err := os.Chdir(dir); err != nil {
|
||||||
return ui.Error(fmt.Sprintf("failed to change directory to %s", dir), err)
|
return ui.Error(fmt.Sprintf("failed to change directory to %s", dir), err)
|
||||||
@@ -405,7 +433,7 @@ func Start(dir string) error {
|
|||||||
route = r.URL.Path
|
route = r.URL.Path
|
||||||
|
|
||||||
if strings.HasPrefix(route, "/archive") {
|
if strings.HasPrefix(route, "/archive") {
|
||||||
readArchive(w, route)
|
err = readArchive(w, route)
|
||||||
} else {
|
} else {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
w.Write([]byte(notFoundData))
|
w.Write([]byte(notFoundData))
|
||||||
@@ -422,7 +450,7 @@ func Start(dir string) error {
|
|||||||
|
|
||||||
var data []byte
|
var data []byte
|
||||||
if strings.HasSuffix(route, ".lua") {
|
if strings.HasSuffix(route, ".lua") {
|
||||||
data, err = loadLua(route, &cfg, reqData{path: r.URL.Path, params: params})
|
data, err = renderRoute(route, &cfg, reqData{path: r.URL.Path, params: params})
|
||||||
} else if strings.HasSuffix(route, ".md") {
|
} else if strings.HasSuffix(route, ".md") {
|
||||||
data, err = os.ReadFile(route)
|
data, err = os.ReadFile(route)
|
||||||
data = []byte(markdownToHTML(string(data)))
|
data = []byte(markdownToHTML(string(data)))
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/* print out the current path (route) and relevant error */
|
||||||
func Path(path string, err error) {
|
func Path(path string, err error) {
|
||||||
path = strings.TrimPrefix(path, "/")
|
path = strings.TrimPrefix(path, "/")
|
||||||
|
|
||||||
@@ -29,29 +30,35 @@ func Path(path string, err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* print general system warning */
|
||||||
func Warning(msg string, err error) error {
|
func Warning(msg string, err error) error {
|
||||||
fmt.Printf("%s: %s: %v\n", version.PROGRAM_NAME, color.MagentaString("warning"), err)
|
fmt.Printf("%s: %s: %v\n", version.PROGRAM_NAME, color.MagentaString("warning"), err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* print general system error */
|
||||||
func Error(msg string, err error) error {
|
func Error(msg string, err error) error {
|
||||||
fmt.Printf("%s: %s: %v\n", version.PROGRAM_NAME, color.RedString("error"), err)
|
fmt.Printf("%s: %s: %v\n", version.PROGRAM_NAME, color.RedString("error"), err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* print fatality and panic */
|
||||||
func Fatal(msg string, err error) error {
|
func Fatal(msg string, err error) error {
|
||||||
fmt.Printf("%s: %s: %v\n", version.PROGRAM_NAME, color.RedString("fatal"), err)
|
fmt.Printf("%s: %s: %v\n", version.PROGRAM_NAME, color.RedString("fatal"), err)
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* print message using the ok status color */
|
||||||
func OK(msg string) {
|
func OK(msg string) {
|
||||||
color.Green(msg)
|
color.Green(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* print message using the warning status color */
|
||||||
func WARN(msg string) {
|
func WARN(msg string) {
|
||||||
color.Magenta(msg)
|
color.Magenta(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* print message using the error status color */
|
||||||
func ERROR(msg string) {
|
func ERROR(msg string) {
|
||||||
color.Red(msg)
|
color.Red(msg)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user