Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
5984393
move old v1 files
JXSnack Aug 13, 2025
f0d38e0
go.mod
JXSnack Aug 13, 2025
4fb4470
very basic server
JXSnack Aug 13, 2025
4650868
add logging example
JXSnack Aug 13, 2025
791fcc3
implement logging
JXSnack Aug 13, 2025
e5531c0
very basic routes
JXSnack Aug 13, 2025
5f339ee
redo routes
JXSnack Oct 31, 2025
1e57530
readme
JXSnack Oct 31, 2025
9131526
session cdocs
JXSnack Oct 31, 2025
b274e92
basic overview for passplate
JXSnack Nov 1, 2025
6bbc97a
basic AST
JXSnack Nov 1, 2025
6f52d88
simple AST nodes
JXSnack Nov 1, 2025
50510df
add NewTextNode
JXSnack Nov 1, 2025
588fcd5
remove old
JXSnack Jan 1, 2026
69f4961
add server configuration
JXSnack Jan 1, 2026
40bff55
config validity
JXSnack Jan 1, 2026
e1f7160
add request logging
JXSnack Jan 1, 2026
c4ee77c
enhance logging by needing less request arguments and fixing colors
JXSnack Jan 6, 2026
bc0ebf8
remove old
JXSnack Jan 6, 2026
303b46f
use NewStandardConfiguration
JXSnack Jan 6, 2026
5d861be
basic request handler
JXSnack Jan 6, 2026
0dcfdf6
add default alert handler
JXSnack Jan 6, 2026
6ef4606
redo old
JXSnack Jan 6, 2026
7d71a0e
better clarification on error handling
JXSnack Jan 6, 2026
97f1533
log when server started
JXSnack Jan 6, 2026
6f9e5c1
basic static and error handling
JXSnack Jan 6, 2026
a6b74b6
add static page example
JXSnack Jan 6, 2026
37f7c2e
dynamic route path generation
JXSnack Jan 6, 2026
89b83c9
basic 404
JXSnack Jan 6, 2026
589276f
actually handle 404 too
JXSnack Jan 6, 2026
a12d0ac
clean id
JXSnack Jan 6, 2026
b2a20aa
add final Route struct
JXSnack Jan 6, 2026
9e6f994
make id always lowercase
JXSnack Jan 6, 2026
64fd9cc
very basic response handling
JXSnack Jan 6, 2026
4970008
add Route to request
JXSnack Jan 6, 2026
17fc292
simple route indexing
JXSnack Jan 6, 2026
7049432
extend Route handler variable
JXSnack Jan 6, 2026
9c95af5
allow for "/" as a URL
JXSnack Jan 6, 2026
4aa0f04
add hello world example
JXSnack Jan 6, 2026
4888fc6
bind routes to paths
JXSnack Jan 6, 2026
42458ed
render routes
JXSnack Jan 6, 2026
53ad385
temporary not found beautifier
JXSnack Jan 7, 2026
cc82939
standardise url splitting
JXSnack Jan 7, 2026
792f8f5
get parameters
JXSnack Jan 7, 2026
c1de9aa
add MustRun˚
JXSnack Jan 7, 2026
bce2143
add Json support
JXSnack Jan 7, 2026
c22d1f0
echo content type
JXSnack Jan 7, 2026
1d5bc31
add support for custom headers
JXSnack Jan 7, 2026
d4e1feb
add download QOL
JXSnack Jan 7, 2026
b53bed2
add file download capabilities
JXSnack Jan 7, 2026
bcda245
add file download example
JXSnack Jan 7, 2026
41c7812
add redirect functionality
JXSnack Jan 7, 2026
31e21ba
add data serving capabilities
JXSnack Jan 7, 2026
c7bad7c
add logs
JXSnack Jan 7, 2026
9e6040e
no double headers
JXSnack Jan 7, 2026
5afcef5
very basic nodes
JXSnack Jan 22, 2026
1b5ceee
indent roots
JXSnack Jan 22, 2026
3f9c6ba
parse from text
JXSnack Jan 22, 2026
dfa3011
Merge remote-tracking branch 'origin/v2' into v2
JXSnack Jan 23, 2026
bfe84ee
give root node a parent
JXSnack Jan 23, 2026
e63ccb4
add text expression
JXSnack Jan 23, 2026
ff61e7c
properly represent if node
JXSnack Jan 23, 2026
7582a2c
fix indentation
JXSnack Jan 23, 2026
3438411
migrate to a state machine
JXSnack Jan 23, 2026
372dbb5
if statement building
JXSnack Jan 23, 2026
c26d526
Expression node now only has one expression
JXSnack Jan 23, 2026
228f486
add error support
JXSnack Jan 23, 2026
8747842
add Kind method to expressions
JXSnack Feb 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,23 @@
# Compass
A Poorly Designed Go Web Framework

A Go http wrapper for quick development.

### Features

- Kinda extensible
- Passplate templating engine
- Session management
- Resource management
- Questionable caching
- No dependencies

Internally, there is also:
- Documentation on how things work and are implemented `/contributor-docs`
- Testing suite

### Why?

There are probably better wrappers out there. Heck, even better Go web server implementations. And faster, too. This
project was started to move from Python (Flask) to Go, but no suitable replacement was found (there probably already
is). Therefore, it was decided to make our own, because reinventing the wheel is always such a good and well-thought-out
decision™
6 changes: 6 additions & 0 deletions contributor-docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Compass Contributor Docs

We care about maintaining our work and passing down information. Therefore, these docs exist. **THIS IS NOT THE REGULAR
DOCUMENTATION,** which you can find at [wiki.snackbag.net/compass](https://wiki.snackbag.net/compass). This is meant as
a guide to future contributors, so they don't have to take wild guesses, reimplement things themselves, and we avoid
knowledge discrimination. When writing code for Compass, also write documentation.
23 changes: 23 additions & 0 deletions contributor-docs/passplate/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Passplate

The Compass templating engine. It consists of the following parts:

- lexer
- parser
- renderer
- interpreter

It brings support for variables and other logic components in normal HTML files, to be evaluated on the server-side. Its
inner workings deviate from the typical programming language interpreter. When a new request for a template is made (and
we exclude all caching magic), it looks similar to the following:

1. Load template file
2. Generate template AST
- parser goes before the lexer, because the lexer only runs within Passplate blocks
3. Render AST
- renderer and interpreter are separated, since the renderer mainly focuses around creating cohesive HTML, while the
interpreter executes the logic

## Useful files

You can find all Passplate files within the `passplate` directory.
37 changes: 37 additions & 0 deletions contributor-docs/passplate/ast.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# AST

Passplate generates an AST to be interpreted/rendered. Pseudo:

```json lines
[
{"name": "Text", "value": "<h1>hey</h1><p>"},
{"name": "Variable", "id": "name"},
{"name": "Text", "id": "</p>"}
]

/*

<h1>hey</h1>
<% if name == "cool" %>
<p>your name is cool</p>
<% else %>
<p>ur not cool</p>
<% end %>

*/

[
{"name": "Text", "value": "<h1>hey</h1>\n"},
{"name": "Statement",
"expr": { // name == "cool"
"a": {"type": "variable", "name": "name"},
"b": {"type": "string", "value": "cool"},
"operator": "=="
},

"pass": [{"name": "Text", "value": "<p>your name is cool</p>"}],
"else": [{"name": "Text", "value": "<p>ur not cool</p>"}]
}
]

```
18 changes: 18 additions & 0 deletions contributor-docs/sessions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Sessions

Compass handles sessions for you.

A session is built up like this:

- true ID; an integer stored only on the server as primary key, autoincrement.
- UUID; the visual session ID. Stored in the server's database as `UNIQUE`, and on the client as the `_compassId`
cookie. It is what the server uses to distinguish sessions per request.
- Value; a JSON string stored only on the server.
- Expiry; the time a session takes until it expires. Stored on the server as unix timestamp, while the client's cookie
is set to never disappear.

The server uses SQLite and has an index for the true ID and UUID.

## Useful files
- [/session.go](/session.go) - session creation, lookup & checking
- [/db.go](/db.go) - database setup
15 changes: 15 additions & 0 deletions examples/basics/01_hello_world/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package main

import (
"github.com/snackbag/compass"
)

func main() {
server := compass.NewServer(compass.NewStandardConfiguration())

server.AddRoute("/", func(request compass.Request) compass.Response {
return compass.Text("hi hello hey")
})

server.MustRun()
}
11 changes: 11 additions & 0 deletions examples/basics/02_logging/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package main

import "github.com/snackbag/compass"

func main() {
server := compass.NewServer(compass.NewStandardConfiguration())

server.Logger.Info("I'm simply logging some things")
server.Logger.Warn("I warn you about stuff")
server.Logger.Error("I am a big red scary text")
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions examples/basics/03_static_pages/assets/static/test.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Hello!
11 changes: 11 additions & 0 deletions examples/basics/03_static_pages/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package main

import (
"github.com/snackbag/compass"
)

// see result on localhost:3000/static/test.txt or hi.png
func main() {
server := compass.NewServer(compass.NewStandardConfiguration())
server.MustRun()
}
16 changes: 16 additions & 0 deletions examples/basics/04_url_parameters/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package main

import (
"github.com/snackbag/compass"
)

func main() {
server := compass.NewServer(compass.NewStandardConfiguration())

server.AddRoute("/@<username>", func(request compass.Request) compass.Response {
param := request.GetRouteParam("username") // returns empty if not found
return compass.Text(param)
})

server.MustRun()
}
17 changes: 17 additions & 0 deletions examples/basics/05_json/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package main

import "github.com/snackbag/compass"

func main() {
server := compass.NewServer(compass.NewStandardConfiguration())

server.AddRoute("/", func(request compass.Request) compass.Response {
return compass.JsonString(`{"test": 123, "wow": ["a", "b", "c"]}`)
})

server.AddRoute("/object", func(request compass.Request) compass.Response {
return compass.JsonMarshal(server.Config)
})

server.MustRun()
}
Binary file added examples/basics/06_download/example.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 17 additions & 0 deletions examples/basics/06_download/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package main

import "github.com/snackbag/compass"

func main() {
server := compass.NewServer(compass.NewStandardConfiguration())

server.AddRoute("/", func(request compass.Request) compass.Response {
return compass.DownloadBytes("example.txt", []byte("helo ereveryione!!"))
})

server.AddRoute("/file", func(request compass.Request) compass.Response {
return compass.DownloadFile("file.png", "example.png")
})

server.MustRun()
}
17 changes: 17 additions & 0 deletions examples/basics/07_redirect/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package main

import "github.com/snackbag/compass"

func main() {
server := compass.NewServer(compass.NewStandardConfiguration())

server.AddRoute("/target", func(request compass.Request) compass.Response {
return compass.Text("Woohooo! You've been redirected :D")
})

server.AddRoute("/", func(request compass.Request) compass.Response {
return compass.Redirect("/target", false)
})

server.MustRun()
}
Binary file added examples/basics/08_serve/example.mp3
Binary file not shown.
13 changes: 13 additions & 0 deletions examples/basics/08_serve/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package main

import "github.com/snackbag/compass"

func main() {
server := compass.NewServer(compass.NewStandardConfiguration())

server.AddRoute("/", func(request compass.Request) compass.Response {
return compass.ServeFile("example.mp3", "example.mp3")
})

server.MustRun()
}
16 changes: 16 additions & 0 deletions examples/passplate/01_basic_fill/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package main

import (
"fmt"
"github.com/snackbag/compass/passplate"
)

func main() {
text := `<h1>Good morning, <$username/>.</h1><p>You are<%if role == "admin"/>an admin<%elif role == "cool"/>pretty cool<%else/>just chill like that<%end/></p>
`
node, err := passplate.Read(text)
if err != nil {
fmt.Println(err)
}
fmt.Println(passplate.Represent(node, 0))
}
65 changes: 65 additions & 0 deletions logging.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package compass

import (
"fmt"
"net/http"
"time"
)

type Logger interface {
Info(message string)
Warn(message string)
Error(message string)

Request(r *http.Request, code int)
}

type SimpleLogger struct {
PrefixMaxLength int
}

func (s *SimpleLogger) log(color string, prefix string, message string) {
currentTime := time.Now().Format("[2006-01-02 15:04:05]")

if len(prefix) > s.PrefixMaxLength {
prefix = prefix[:s.PrefixMaxLength]
}
prefix = fmt.Sprintf("%-*s", s.PrefixMaxLength, prefix)

fmt.Printf("%s %s%s\033[0m %s\033[0m\n", currentTime, color, prefix, message)
}

func (s *SimpleLogger) Info(message string) {
s.log("\x1b[38;2;40;177;249m", "INFO", message)
}

func (s *SimpleLogger) Warn(message string) {
s.log("\033[1;33m", "WARN", message)
}

func (s *SimpleLogger) Error(message string) {
s.log("\033[1;31m", "ERROR", message)
}

func (s *SimpleLogger) Request(r *http.Request, code int) {
var colorCode string
switch {
case code >= 200 && code < 300:
colorCode = "\033[1;32m"
case code >= 300 && code < 400:
colorCode = "\033[1;33m"
case code >= 400 && code < 600:
colorCode = "\033[1;31m"
default:
colorCode = "\033[1;37m"
}

fmt.Printf(
"\x1b[0;34m%s %s%d\033[0m - \033[0;35m%s %s\033[0m \033[0;37m\"%s\"\033[0m\n",
r.RemoteAddr, colorCode, code, r.Method, r.URL.Path, r.UserAgent(),
)
}

func NewSimpleLogger() *SimpleLogger {
return &SimpleLogger{PrefixMaxLength: 5}
}
35 changes: 35 additions & 0 deletions passplate/ast.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package passplate

import (
"fmt"
"strings"
)

func Represent(_n Node, indent int) string {
switch n := _n.(type) {
case *RootNode:
{
builder := strings.Builder{}

for _, child := range n.Children {
i := strings.Repeat(" ", indent)

builder.WriteString(i + Represent(child, indent))
builder.WriteString("\n")
}

return builder.String()
}

case *TextNode:
return fmt.Sprintf("<Text: %s/>", n.Content)

case *ExprNode:
return fmt.Sprintf("<Expression: %s/>", n.Expression.Repr())

case *IfNode:
return n.Repr(indent)
}

return ""
}
Loading