Compare commits
7 Commits
new-defaul
...
4eaead6abc
| Author | SHA1 | Date | |
|---|---|---|---|
| 4eaead6abc | |||
| 4abf2969ca | |||
| 1c229f1b3e | |||
| 5a733b8642 | |||
| 11ab1630be | |||
| 5fabd0233d | |||
| c43e905729 |
@@ -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 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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[app]
|
||||
|
||||
name = "hello"
|
||||
name = "best"
|
||||
version = "0.0.1"
|
||||
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]
|
||||
|
||||
name = "advanced"
|
||||
name = "default"
|
||||
version = "0.0.1"
|
||||
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 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
|
||||
|
||||
@@ -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.
|
||||
12
main.go
12
main.go
@@ -34,13 +34,13 @@ func init() {
|
||||
func main() {
|
||||
flag.Usage = func() {
|
||||
fmt.Fprintf(flag.CommandLine.Output(), "Usage: %s [options] <command> <project_dir>\n", os.Args[0])
|
||||
fmt.Println("Commands:")
|
||||
fmt.Println(" new <project_dir> Create a new project")
|
||||
fmt.Println(" doc Open documentation")
|
||||
fmt.Println(" run <project_dir> Start the server")
|
||||
fmt.Println("Options:")
|
||||
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:")
|
||||
flag.PrintDefaults()
|
||||
fmt.Println("For bug reports, contact a developer and describe the issue. Provide the output of the `-V1` flag.")
|
||||
fmt.Fprintf(os.Stderr, "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")
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/pkg/browser"
|
||||
)
|
||||
|
||||
/* open documentation in browser */
|
||||
func Open() error {
|
||||
fmt.Println("Opening documentation in browser")
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package new
|
||||
|
||||
import (
|
||||
"fes/modules/config"
|
||||
"fes/modules/ui"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -114,5 +115,15 @@ All commands are run from the root of the project, from a terminal:
|
||||
## What to learn more?
|
||||
|
||||
Check out [Fes's docs](https://docs.vxserver.dev/static/fes.html).`, "$$", "`"), dir, dir)
|
||||
|
||||
ui.Hint("you can run this with `fes run %s`", dir)
|
||||
|
||||
fmt.Println("Created new Fes project at", func() string {
|
||||
if res, err := filepath.Abs(dir); err == nil {
|
||||
return res
|
||||
}
|
||||
return dir
|
||||
}())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -17,11 +17,21 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
/* this is the request data we pass over the bus to the application, via the fes.bus interface */
|
||||
type reqData struct {
|
||||
path 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 {
|
||||
for _, entry := range entries {
|
||||
path := filepath.Join(dir, entry.Name())
|
||||
@@ -59,6 +69,7 @@ func handleDir(entries []os.DirEntry, dir string, routes map[string]string, base
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO(vx-clutch): this should not be a function
|
||||
func loadIncludeModules(L *lua.LState, includeDir string) *lua.LTable {
|
||||
app := L.NewTable()
|
||||
ents, err := os.ReadDir(includeDir)
|
||||
@@ -94,7 +105,8 @@ func loadIncludeModules(L *lua.LState, includeDir string) *lua.LTable {
|
||||
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()
|
||||
defer L.Close()
|
||||
|
||||
@@ -219,6 +231,7 @@ func loadLua(entry string, cfg *config.AppConfig, requestData reqData) ([]byte,
|
||||
return []byte(""), nil
|
||||
}
|
||||
|
||||
/* this indexes and generate the page for viewing the archive directory */
|
||||
func generateArchiveIndex(fsPath string, urlPath string) (string, error) {
|
||||
info, err := os.Stat(fsPath)
|
||||
if err != nil {
|
||||
@@ -309,6 +322,9 @@ func generateArchiveIndex(fsPath string, urlPath string) (string, error) {
|
||||
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 {
|
||||
notFoundData := []byte(`
|
||||
<html>
|
||||
@@ -320,9 +336,13 @@ func generateNotFoundData(cfg *config.AppConfig) []byte {
|
||||
</html>
|
||||
`)
|
||||
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
|
||||
}
|
||||
} 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 {
|
||||
if buf, err := os.ReadFile("www/404.html"); err == nil {
|
||||
notFoundData = buf
|
||||
@@ -331,6 +351,7 @@ func generateNotFoundData(cfg *config.AppConfig) []byte {
|
||||
return notFoundData
|
||||
}
|
||||
|
||||
/* helper to load all special directories */
|
||||
func loadDirs() map[string]string {
|
||||
routes := make(map[string]string)
|
||||
|
||||
@@ -355,6 +376,7 @@ func loadDirs() map[string]string {
|
||||
return routes
|
||||
}
|
||||
|
||||
/* helper to parse the Fes.toml and generate config */
|
||||
func parseConfig() config.AppConfig {
|
||||
tomlDocument, err := os.ReadFile("Fes.toml")
|
||||
if err != nil {
|
||||
@@ -372,15 +394,21 @@ func parseConfig() config.AppConfig {
|
||||
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
|
||||
if info, err := os.Stat(fsPath); err == nil && info.IsDir() {
|
||||
if page, err := generateArchiveIndex(fsPath, route); err == nil {
|
||||
w.Write([]byte(page))
|
||||
return nil
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/* start the Fes server */
|
||||
func Start(dir string) error {
|
||||
if err := os.Chdir(dir); err != nil {
|
||||
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
|
||||
|
||||
if strings.HasPrefix(route, "/archive") {
|
||||
readArchive(w, route)
|
||||
err = readArchive(w, route)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
w.Write([]byte(notFoundData))
|
||||
@@ -422,7 +450,7 @@ func Start(dir string) error {
|
||||
|
||||
var data []byte
|
||||
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") {
|
||||
data, err = os.ReadFile(route)
|
||||
data = []byte(markdownToHTML(string(data)))
|
||||
|
||||
@@ -11,6 +11,11 @@ import (
|
||||
"github.com/fatih/color"
|
||||
)
|
||||
|
||||
const (
|
||||
hint_color = 0xbda02a
|
||||
)
|
||||
|
||||
/* print out the current path (route) and relevant error */
|
||||
func Path(path string, err error) {
|
||||
path = strings.TrimPrefix(path, "/")
|
||||
|
||||
@@ -29,29 +34,45 @@ func Path(path string, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
/* print general system warning */
|
||||
func Warning(msg string, err error) error {
|
||||
fmt.Printf("%s: %s: %v\n", version.PROGRAM_NAME, color.MagentaString("warning"), err)
|
||||
return err
|
||||
}
|
||||
|
||||
/* print general system error */
|
||||
func Error(msg string, err error) error {
|
||||
fmt.Printf("%s: %s: %v\n", version.PROGRAM_NAME, color.RedString("error"), err)
|
||||
return err
|
||||
}
|
||||
|
||||
/* print fatality and panic */
|
||||
func Fatal(msg string, err error) error {
|
||||
fmt.Printf("%s: %s: %v\n", version.PROGRAM_NAME, color.RedString("fatal"), err)
|
||||
panic(err)
|
||||
}
|
||||
|
||||
/* print a useful hint to the user */
|
||||
func Hint(format string, args ...any) {
|
||||
color.RGB(func(hex int) (r, g, b int) {
|
||||
r = (hex >> 16) & 0xFF
|
||||
g = (hex >> 8) & 0xFF
|
||||
b = hex & 0xFF
|
||||
return
|
||||
}(hint_color)).Printf("hint: %s\n", fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
/* print message using the ok status color */
|
||||
func OK(msg string) {
|
||||
color.Green(msg)
|
||||
}
|
||||
|
||||
/* print message using the warning status color */
|
||||
func WARN(msg string) {
|
||||
color.Magenta(msg)
|
||||
}
|
||||
|
||||
/* print message using the error status color */
|
||||
func ERROR(msg string) {
|
||||
color.Red(msg)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user