15 |
16 |
17 | ## 🤖 Stack
18 |
19 | | Tech | Stack |
20 | |----------------|----------|
21 | | Htmx | Frontend |
22 | | Ocaml & Reason | Backend |
23 | | Turso w/ Go | Database |
24 |
25 | ## 🚀 Building + Running
26 |
27 | The nix flake has two build targets:
28 | 1. dune project derivation: produces a binary that one can run as is
29 | 2. docker image: produces a minimal image that contains the derivation
30 |
31 | The derivation can be built with `nix build` and the image can be built with
32 | `nix build .#docker`.
33 |
34 | You can run it easily (assuming you have docker setup) with `make run`. If you
35 | want to clean up afterwards, use `make clean`.
36 |
37 | ## 🧮 Complications
38 |
39 | It uses Ocaml for the webserver and Reason for templating. I opted to use Turso
40 | for the DB but the client support for Ocaml is _poor_. So, I compiled Go to C
41 | and use Dune to bind the C code to Ocaml...probably not the best idea but it
42 | does work. Nix is used to build the code and with GitHub workflows, it gets
43 | deployed. Locally, I test using the Makefile. This will let you easily run and
44 | clean.
45 |
--------------------------------------------------------------------------------
/blogs/to_learn_or_to_do.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ethanthoma/ocaml-webserver/9a5736548d4bc7b3cfc052c949a61f455276f2b2/blogs/to_learn_or_to_do.md
--------------------------------------------------------------------------------
/db/init.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE blogs (
2 | slug VARCHAR(255) NOT NULL UNIQUE,
3 | title VARCHAR(255) NOT NULL,
4 | description TEXT NOT NULL,
5 | content TEXT NOT NULL,
6 | date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
7 | tags TEXT NOT NULL,
8 | PRIMARY KEY (slug)
9 | );
10 |
--------------------------------------------------------------------------------
/db/insert.sh:
--------------------------------------------------------------------------------
1 | slug="how_to_build_a_website.md"
2 | title="How to Build a Website: Part Two"
3 | description="Combing Ocaml, HTMX, Go, NIx, etc to make a simple website."
4 | file_path="./blogs/${slug}"
5 | contents="$(cat "$file_path")"
6 | contents="$(echo "$contents" | sed "s/'/''/g")"
7 | tags="Nix, htmx, webdev, Ocaml, Go, C"
8 |
9 | sql="INSERT INTO blogs (title, slug, description, content, tags, date) VALUES('${title}', '${slug}', '${description}', '${contents}', '${tags}', date());"
10 |
11 | echo "$sql" | turso db shell $TURSO_DATABASE_NAME
12 |
--------------------------------------------------------------------------------
/db/update.sh:
--------------------------------------------------------------------------------
1 | slug="how_to_build_a_website.md"
2 | file_path="./blogs/${slug}"
3 | contents="$(cat "$file_path")"
4 | contents="$(echo "$contents" | sed "s/'/''/g")"
5 |
6 | sql="UPDATE blogs SET content = "
7 | sql="${sql} '"${contents}"'"
8 | sql="${sql} WHERE slug = '${slug}'"
9 |
10 | echo "$sql" | turso db shell $TURSO_DATABASE_NAME
11 |
--------------------------------------------------------------------------------
/dune-project:
--------------------------------------------------------------------------------
1 | (lang dune 3.7)
2 |
3 | (using ctypes 0.3)
4 |
5 | (name webserver)
6 |
7 | (generate_opam_files true)
8 |
9 | (source (github ethanthoma/ocaml-webserver))
10 |
11 | (authors "Ethan Thoma")
12 |
13 | (maintainers "Ethan Thoma")
14 |
15 | (license LICENSE)
16 |
17 | (package
18 | (name webserver)
19 | (synopsis "Htmx webserver")
20 | (description "Htmx webserver")
21 | (depends
22 | ocaml
23 | dune
24 | dream
25 | reason
26 | tyxml-jsx
27 | fuzzy_match
28 | omd
29 | ppx_string
30 | ctypes
31 | ctypes-foreign
32 | crunch
33 | )
34 | (tags (topics "Htmx" "JSX" "Ocaml"))
35 | )
36 |
--------------------------------------------------------------------------------
/flake.nix:
--------------------------------------------------------------------------------
1 | {
2 | inputs = {
3 | nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
4 | opam-nix.url = "github:tweag/opam-nix";
5 | flake-utils.url = "github:numtide/flake-utils";
6 | };
7 |
8 | outputs = { self, flake-utils, opam-nix, nixpkgs }:
9 | let
10 | package = "webserver";
11 | src = ./.;
12 | in flake-utils.lib.eachDefaultSystem (system:
13 | let
14 | pkgs = nixpkgs.legacyPackages.${system};
15 |
16 | goDeps = with pkgs; [
17 | go
18 | libffi
19 | ];
20 |
21 | cssDeps = with pkgs; [
22 | lightningcss
23 | ];
24 |
25 | deps = goDeps ++ cssDeps;
26 |
27 | on = opam-nix.lib.${system};
28 |
29 | scope = on.buildDuneProject { } package src {
30 | ocaml-base-compiler = "*";
31 | dream = "*";
32 | reason = "*";
33 | tyxml-jsx = "*";
34 | fuzzy_match = "*";
35 | omd = "*";
36 | };
37 |
38 | overlay = final: prev: {
39 | ${package} = prev.${package}.overrideAttrs (oa: {
40 | buildInputs = oa.buildInputs ++ [
41 | final.dream
42 | final.reason
43 | final.tyxml-jsx
44 | final.fuzzy_match
45 | final.omd
46 | ];
47 | nativeBuildInputs = oa.nativeBuildInputs ++ deps;
48 | buildPhase = "HOME=$TMPDIR dune build --release";
49 | postInstall = ''
50 | cp -rf $src/public $out/public
51 | cp -rf $src/blogs $out/blogs
52 | '';
53 | });
54 | };
55 |
56 | legacyPackages = scope.overrideScope' overlay;
57 |
58 | derivation = self.legacyPackages.${system}.${package};
59 | in {
60 | inherit legacyPackages;
61 |
62 | packages.default = derivation;
63 |
64 | packages.docker = pkgs.callPackage ./nix/docker.nix {
65 | inherit pkgs package derivation;
66 | };
67 |
68 | devShells.default = pkgs.callPackage ./nix/shell.nix {
69 | inherit pkgs;
70 |
71 | devDeps = deps;
72 | };
73 | }
74 | );
75 | }
76 |
--------------------------------------------------------------------------------
/lib/turso/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: build
2 |
3 | build:
4 | go build -o dllgo_turso.so -buildmode=c-shared -mod=vendor ./cmd/
5 | go build -o libgo_turso.a -buildmode=c-archive -mod=vendor ./cmd/
6 |
--------------------------------------------------------------------------------
/lib/turso/README.md:
--------------------------------------------------------------------------------
1 | # Turso Ocaml-bindings
2 |
3 | **NOTE: this library is setup just for my blogs db and is not generic**
4 |
5 | This is a simple go-to-c Turso library. The Ocaml code binds via Ctypes and Dune.
6 |
7 | Look at cmd/C.go to see how code is exported from the pkg directory. In the
8 | future, I will probably make this more generic but it is sufficient for now.
9 |
10 | If you are wanting to use this code in your own project, you will need to add
11 | this to your executable stanza for Dune:
12 | ```dune
13 | (link_flags -cclib -Wl,--whole-archive -cclib ./lib/turso/libgo_turso.a -cclib -Wl,--no-whole-archive)
14 | ```
15 |
--------------------------------------------------------------------------------
/lib/turso/cache.ml:
--------------------------------------------------------------------------------
1 | module Cache = struct
2 | type 'a t = {
3 | mutable data : 'a;
4 | mutable last_updated : float;
5 | update_function : unit -> 'a;
6 | max_age : float;
7 | }
8 |
9 | let needs_update cache =
10 | let now = Unix.gettimeofday () in
11 | now -. cache.last_updated > cache.max_age
12 |
13 | let update cache =
14 | Dream.log "Cache.update: Checking...";
15 | if needs_update cache then (
16 | Dream.log "Cache.update: Fetching from DB...";
17 | let%lwt new_data = Lwt_preemptive.detach cache.update_function () in
18 | cache.data <- new_data;
19 | cache.last_updated <- Unix.gettimeofday ();
20 | Lwt.return_unit)
21 | else Lwt.return_unit
22 |
23 | let rec schedule_update cache =
24 | Lwt.bind
25 | (Lwt_unix.sleep (60. *. 60. *. 24.))
26 | (fun () ->
27 | Dream.log "Cache.schedule_update: daily cache fetch";
28 | let _ = update cache in
29 | schedule_update cache)
30 |
31 | let get cache =
32 | let current_data = cache.data in
33 | let _ = Lwt.async (fun () -> update cache) in
34 | current_data
35 |
36 | let init ~(update_function : unit -> 'a) ?(max_age : float = 60.) () =
37 | let cache =
38 | {
39 | data = update_function ();
40 | last_updated = Unix.gettimeofday ();
41 | update_function;
42 | max_age;
43 | }
44 | in
45 | let _ = Lwt.async (fun () -> schedule_update cache) in
46 | cache
47 | end
48 |
49 | module Blogs = struct
50 | let update () =
51 | Dream.log "Blogs: Updating blog cache...";
52 | let blogs = Turso.get_blogs () in
53 | blogs
54 |
55 | let cache = Cache.init ~update_function:update ~max_age:(60. *. 5.) ()
56 | let cache_by_slug = Hashtbl.create 5
57 |
58 | let update_by_slug slug =
59 | Dream.log "Blogs: Updating slug cache...";
60 | Turso.get_blog_by_slug slug
61 |
62 | let get () = Cache.get cache
63 |
64 | let get_by_slug slug =
65 | try Cache.get (Hashtbl.find cache_by_slug slug)
66 | with Not_found ->
67 | let new_cache =
68 | Cache.init
69 | ~update_function:(fun () -> update_by_slug slug)
70 | ~max_age:(60. *. 60.) ()
71 | in
72 | Hashtbl.add cache_by_slug slug new_cache;
73 | Cache.get new_cache
74 | end
75 |
--------------------------------------------------------------------------------
/lib/turso/cache.mli:
--------------------------------------------------------------------------------
1 | module Blogs : sig
2 | val get : unit -> Turso.blog list
3 | val get_by_slug : string -> Turso.blog
4 | end
5 |
--------------------------------------------------------------------------------
/lib/turso/cmd/C.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | // #cgo CFLAGS: -fpic
4 | import "C"
5 |
6 | import (
7 | "fmt"
8 | "os"
9 |
10 | "github.com/ethanthoma/webserver/lib/turso/pkg/blog"
11 | db "github.com/ethanthoma/webserver/lib/turso/pkg/database"
12 | )
13 |
14 | var database = os.Getenv("TURSO_DATABASE")
15 | var token = os.Getenv("TURSO_DATABASE_TOKEN")
16 |
17 | //export Init
18 | func Init() {
19 | db.Init(database, token)
20 | }
21 |
22 | //export GetBlogTable
23 | func GetBlogTable() *C.char {
24 | blogs, err := blog.GetBlogTable()
25 | if err != nil {
26 | fmt.Println("tb error")
27 | return nil
28 | }
29 | return C.CString(blogs)
30 | }
31 |
32 | //export GetBlogBySlug
33 | func GetBlogBySlug(slug *C.char) *C.char {
34 | blog, err := blog.GetBlogBySlug(C.GoString(slug))
35 | if err != nil {
36 | return nil
37 | }
38 | return C.CString(blog)
39 | }
40 |
41 | func main() { }
42 |
--------------------------------------------------------------------------------
/lib/turso/dune:
--------------------------------------------------------------------------------
1 | (library
2 | (name turso)
3 | (public_name webserver.turso)
4 | (foreign_archives go_turso)
5 | (libraries ctypes ctypes.foreign yojson dream)
6 | (preprocess
7 | (pps lwt_ppx))
8 | (flags
9 | (:standard -w -49))
10 | (wrapped false)
11 | (library_flags
12 | (-cclib -lgo_turso)))
13 |
14 | (rule
15 | (deps
16 | (source_tree .))
17 | (targets libgo_turso.a dllgo_turso.so libgo_turso.h)
18 | (action
19 | (no-infer
20 | (ignore-stdout
21 | (progn
22 | (run make))))))
23 |
24 | (env
25 | (utop
26 | (env-vars
27 | (OCAML_INTEROP_NO_CAML_STARTUP true))))
28 |
--------------------------------------------------------------------------------
/lib/turso/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/ethanthoma/webserver/lib/turso
2 |
3 | go 1.18
4 |
5 | require (
6 | github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230512164433-5d1fd1a340c9 // indirect
7 | github.com/klauspost/compress v1.15.15 // indirect
8 | github.com/libsql/sqlite-antlr4-parser v0.0.0-20230802215326-5cb5bb604475 // indirect
9 | github.com/tursodatabase/libsql-client-go v0.0.0-20240220085343-4ae0eb9d0898 // indirect
10 | golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect
11 | nhooyr.io/websocket v1.8.7 // indirect
12 | )
13 |
--------------------------------------------------------------------------------
/lib/turso/go.sum:
--------------------------------------------------------------------------------
1 | github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230512164433-5d1fd1a340c9 h1:goHVqTbFX3AIo0tzGr14pgfAW2ZfPChKO21Z9MGf/gk=
2 | github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230512164433-5d1fd1a340c9/go.mod h1:pSwJ0fSY5KhvocuWSx4fz3BA8OrA1bQn+K1Eli3BRwM=
3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
5 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
6 | github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
7 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
8 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
9 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
10 | github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
11 | github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
12 | github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
13 | github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
14 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
15 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
16 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
17 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
18 | github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
19 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
20 | github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
21 | github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw=
22 | github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4=
23 | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
24 | github.com/libsql/sqlite-antlr4-parser v0.0.0-20230802215326-5cb5bb604475 h1:6PfEMwfInASh9hkN83aR0j4W/eKaAZt/AURtXAXlas0=
25 | github.com/libsql/sqlite-antlr4-parser v0.0.0-20230802215326-5cb5bb604475/go.mod h1:20nXSmcf0nAscrzqsXeC2/tA3KkV2eCiJqYuyAgl+ss=
26 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
27 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
28 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
29 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
30 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
31 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
32 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
33 | github.com/tursodatabase/libsql-client-go v0.0.0-20240220085343-4ae0eb9d0898 h1:1MvEhzI5pvP27e9Dzz861mxk9WzXZLSJwzOU67cKTbU=
34 | github.com/tursodatabase/libsql-client-go v0.0.0-20240220085343-4ae0eb9d0898/go.mod h1:9bKuHS7eZh/0mJndbUOrCx8Ej3PlsRDszj4L7oVYMPQ=
35 | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
36 | github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
37 | golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA=
38 | golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
39 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
40 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
41 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
42 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
43 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
44 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
45 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
46 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
47 | nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g=
48 | nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
49 |
--------------------------------------------------------------------------------
/lib/turso/pkg/blog/model.go:
--------------------------------------------------------------------------------
1 | package blog
2 |
3 | import "time"
4 |
5 | type Blog struct {
6 | Slug string
7 | Title string
8 | Description string
9 | Content string
10 | Date time.Time
11 | Tags []string
12 | }
13 |
--------------------------------------------------------------------------------
/lib/turso/pkg/blog/service.go:
--------------------------------------------------------------------------------
1 | package blog
2 |
3 | import (
4 | "database/sql"
5 | "encoding/json"
6 | "fmt"
7 | "strings"
8 |
9 | db "github.com/ethanthoma/webserver/lib/turso/pkg/database"
10 | )
11 |
12 | func rowsToBlogs (rows *sql.Rows) ([]Blog, error) {
13 | var blogs []Blog
14 |
15 | for rows.Next() {
16 | var blog Blog
17 |
18 | var tags string
19 |
20 | if err := rows.Scan(
21 | &blog.Slug,
22 | &blog.Title,
23 | &blog.Description,
24 | &blog.Content,
25 | &blog.Date,
26 | &tags,
27 | ); err != nil {
28 | fmt.Println("Error scanning row:", err)
29 | return nil, err
30 | }
31 |
32 | blog.Tags = strings.Split(tags, ",")
33 |
34 | blogs = append(blogs, blog)
35 | }
36 |
37 | if err := rows.Err(); err != nil {
38 | str := fmt.Sprint("Error during rows iteration:", err)
39 | fmt.Println(str)
40 | return nil, err
41 | }
42 |
43 | return blogs, nil
44 | }
45 |
46 | func GetBlogTable() (string, error) {
47 | rows, err := db.DB.Query(`
48 | SELECT
49 | *
50 | FROM
51 | blogs
52 | ORDER BY
53 | date
54 | DESC;
55 | `)
56 | if err != nil {
57 | return "", err
58 | }
59 | defer rows.Close()
60 |
61 | blogs, err := rowsToBlogs(rows)
62 | if err != nil {
63 | return "", err
64 | }
65 |
66 | jsonBlogs, err := json.Marshal(blogs)
67 | if err != nil {
68 | return "", err
69 | }
70 |
71 | return string(jsonBlogs), nil
72 | }
73 |
74 | func GetBlogBySlug(slug string) (string, error) {
75 | rows, err := db.DB.Query(fmt.Sprint(`
76 | SELECT
77 | *
78 | FROM
79 | blogs
80 | WHERE
81 | slug = '`,slug,`'
82 | LIMIT
83 | 1
84 | ;
85 | `))
86 | if err != nil {
87 | return "", err
88 | }
89 | defer rows.Close()
90 |
91 | blogs, err := rowsToBlogs(rows)
92 | if err != nil {
93 | return "", err
94 | }
95 |
96 | if len(blogs) == 0 {
97 | return "", err
98 | }
99 |
100 | blog := blogs[0]
101 |
102 | jsonBlog, err := json.Marshal(blog)
103 | if err != nil {
104 | return "", err
105 | }
106 |
107 | return string(jsonBlog), nil
108 | }
109 |
--------------------------------------------------------------------------------
/lib/turso/pkg/database/database.go:
--------------------------------------------------------------------------------
1 | package database
2 |
3 | import (
4 | "database/sql"
5 | "fmt"
6 | "os"
7 |
8 | _ "github.com/tursodatabase/libsql-client-go/libsql"
9 | )
10 |
11 | var DB *sql.DB
12 |
13 | func Init(database string, token string) {
14 | url := fmt.Sprintf("libsql://%s.turso.io?authToken=%s", database, token)
15 |
16 | var err error
17 | DB, err = sql.Open("libsql", url)
18 | if err != nil {
19 | fmt.Fprintf(os.Stderr, "failed to open db %s: %s", url, err)
20 | os.Exit(1)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/lib/turso/turso.ml:
--------------------------------------------------------------------------------
1 | open Ctypes
2 | open Foreign
3 |
4 | let () =
5 | let init = foreign "Init" (void @-> returning void) in
6 | init ()
7 |
8 | type blog = {
9 | filename : string;
10 | title : string;
11 | description : string;
12 | content : string;
13 | tags : string list;
14 | date : string;
15 | }
16 |
17 | let empty_blog =
18 | {
19 | filename = "not_yet_released.md";
20 | title = "Not Yet Released";
21 | description = "try again in the future!";
22 | content = "# Not Yet Released\n\nPlease try again in the future!";
23 | tags = [];
24 | date = "The Future";
25 | }
26 |
27 | let json_to_blog json =
28 | let open Yojson.Basic.Util in
29 | let title = json |> member "Title" |> to_string in
30 | let filename = json |> member "Slug" |> to_string in
31 | let description = json |> member "Description" |> to_string in
32 | let content = json |> member "Content" |> to_string in
33 | let date = String.sub (json |> member "Date" |> to_string) 0 10 in
34 | let tags = json |> member "Tags" |> to_list |> filter_string in
35 | { filename; title; description; content; tags; date }
36 |
37 | let get_blogs () =
38 | let get_blog_table = foreign "GetBlogTable" (void @-> returning string) in
39 | let json =
40 | let str_json = get_blog_table () in
41 | if str_json <> "" then Some (Yojson.Basic.from_string str_json) else None
42 | in
43 | match json with
44 | | Some json -> (
45 | let list_opt =
46 | Yojson.Basic.Util.to_option Yojson.Basic.Util.to_list json
47 | in
48 | match list_opt with Some list -> List.map json_to_blog list | None -> [])
49 | | None -> []
50 |
51 | let get_blog_by_slug slug =
52 | let get_blog_by_slug =
53 | foreign "GetBlogBySlug" (string @-> returning string)
54 | in
55 | let json =
56 | let str_json = get_blog_by_slug slug in
57 | if str_json <> "" then Some (Yojson.Basic.from_string str_json) else None
58 | in
59 | match json with Some json -> json_to_blog json | None -> empty_blog
60 |
--------------------------------------------------------------------------------
/lib/turso/turso.mli:
--------------------------------------------------------------------------------
1 | type blog = {
2 | filename : string;
3 | title : string;
4 | description : string;
5 | content : string;
6 | tags : string list;
7 | date : string;
8 | }
9 |
10 | val empty_blog : blog
11 |
12 | val get_blogs : unit -> blog list
13 | val get_blog_by_slug : string -> blog
14 |
--------------------------------------------------------------------------------
/lib/turso/vendor/github.com/antlr/antlr4/runtime/Go/antlr/v4/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2021 The ANTLR Project
2 |
3 | Redistribution and use in source and binary forms, with or without modification,
4 | are permitted provided that the following conditions are met:
5 |
6 | 1. Redistributions of source code must retain the above copyright notice,
7 | this list of conditions and the following disclaimer.
8 |
9 | 2. Redistributions in binary form must reproduce the above copyright notice,
10 | this list of conditions and the following disclaimer in the documentation
11 | and/or other materials provided with the distribution.
12 |
13 | 3. Neither the name of the copyright holder nor the names of its
14 | contributors may be used to endorse or promote products derived from this
15 | software without specific prior written permission.
16 |
17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
18 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
21 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
24 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 |
--------------------------------------------------------------------------------
/lib/turso/vendor/github.com/antlr/antlr4/runtime/Go/antlr/v4/antlrdoc.go:
--------------------------------------------------------------------------------
1 | /*
2 | Package antlr implements the Go version of the ANTLR 4 runtime.
3 |
4 | # The ANTLR Tool
5 |
6 | ANTLR (ANother Tool for Language Recognition) is a powerful parser generator for reading, processing, executing,
7 | or translating structured text or binary files. It's widely used to build languages, tools, and frameworks.
8 | From a grammar, ANTLR generates a parser that can build parse trees and also generates a listener interface
9 | (or visitor) that makes it easy to respond to the recognition of phrases of interest.
10 |
11 | # Code Generation
12 |
13 | ANTLR supports the generation of code in a number of [target languages], and the generated code is supported by a
14 | runtime library, written specifically to support the generated code in the target language. This library is the
15 | runtime for the Go target.
16 |
17 | To generate code for the go target, it is generally recommended to place the source grammar files in a package of
18 | their own, and use the `.sh` script method of generating code, using the go generate directive. In that same directory
19 | it is usual, though not required, to place the antlr tool that should be used to generate the code. That does mean
20 | that the antlr tool JAR file will be checked in to your source code control though, so you are free to use any other
21 | way of specifying the version of the ANTLR tool to use, such as aliasing in `.zshrc` or equivalent, or a profile in
22 | your IDE, or configuration in your CI system.
23 |
24 | Here is a general template for an ANTLR based recognizer in Go:
25 |
26 | .
27 | ├── myproject
28 | ├── parser
29 | │ ├── mygrammar.g4
30 | │ ├── antlr-4.12.0-complete.jar
31 | │ ├── error_listeners.go
32 | │ ├── generate.go
33 | │ ├── generate.sh
34 | ├── go.mod
35 | ├── go.sum
36 | ├── main.go
37 | └── main_test.go
38 |
39 | Make sure that the package statement in your grammar file(s) reflects the go package they exist in.
40 | The generate.go file then looks like this:
41 |
42 | package parser
43 |
44 | //go:generate ./generate.sh
45 |
46 | And the generate.sh file will look similar to this:
47 |
48 | #!/bin/sh
49 |
50 | alias antlr4='java -Xmx500M -cp "./antlr4-4.12.0-complete.jar:$CLASSPATH" org.antlr.v4.Tool'
51 | antlr4 -Dlanguage=Go -no-visitor -package parser *.g4
52 |
53 | depending on whether you want visitors or listeners or any other ANTLR options.
54 |
55 | From the command line at the root of your package “myproject” you can then simply issue the command:
56 |
57 | go generate ./...
58 |
59 | # Copyright Notice
60 |
61 | Copyright (c) 2012-2022 The ANTLR Project. All rights reserved.
62 |
63 | Use of this file is governed by the BSD 3-clause license, which can be found in the [LICENSE.txt] file in the project root.
64 |
65 | [target languages]: https://github.com/antlr/antlr4/tree/master/runtime
66 | [LICENSE.txt]: https://github.com/antlr/antlr4/blob/master/LICENSE.txt
67 | */
68 | package antlr
69 |
--------------------------------------------------------------------------------
/lib/turso/vendor/github.com/antlr/antlr4/runtime/Go/antlr/v4/atn_deserialization_options.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2012-2022 The ANTLR Project. All rights reserved.
2 | // Use of this file is governed by the BSD 3-clause license that
3 | // can be found in the LICENSE.txt file in the project root.
4 |
5 | package antlr
6 |
7 | import "errors"
8 |
9 | var defaultATNDeserializationOptions = ATNDeserializationOptions{true, true, false}
10 |
11 | type ATNDeserializationOptions struct {
12 | readOnly bool
13 | verifyATN bool
14 | generateRuleBypassTransitions bool
15 | }
16 |
17 | func (opts *ATNDeserializationOptions) ReadOnly() bool {
18 | return opts.readOnly
19 | }
20 |
21 | func (opts *ATNDeserializationOptions) SetReadOnly(readOnly bool) {
22 | if opts.readOnly {
23 | panic(errors.New("Cannot mutate read only ATNDeserializationOptions"))
24 | }
25 | opts.readOnly = readOnly
26 | }
27 |
28 | func (opts *ATNDeserializationOptions) VerifyATN() bool {
29 | return opts.verifyATN
30 | }
31 |
32 | func (opts *ATNDeserializationOptions) SetVerifyATN(verifyATN bool) {
33 | if opts.readOnly {
34 | panic(errors.New("Cannot mutate read only ATNDeserializationOptions"))
35 | }
36 | opts.verifyATN = verifyATN
37 | }
38 |
39 | func (opts *ATNDeserializationOptions) GenerateRuleBypassTransitions() bool {
40 | return opts.generateRuleBypassTransitions
41 | }
42 |
43 | func (opts *ATNDeserializationOptions) SetGenerateRuleBypassTransitions(generateRuleBypassTransitions bool) {
44 | if opts.readOnly {
45 | panic(errors.New("Cannot mutate read only ATNDeserializationOptions"))
46 | }
47 | opts.generateRuleBypassTransitions = generateRuleBypassTransitions
48 | }
49 |
50 | func DefaultATNDeserializationOptions() *ATNDeserializationOptions {
51 | return NewATNDeserializationOptions(&defaultATNDeserializationOptions)
52 | }
53 |
54 | func NewATNDeserializationOptions(other *ATNDeserializationOptions) *ATNDeserializationOptions {
55 | o := new(ATNDeserializationOptions)
56 | if other != nil {
57 | *o = *other
58 | o.readOnly = false
59 | }
60 | return o
61 | }
62 |
--------------------------------------------------------------------------------
/lib/turso/vendor/github.com/antlr/antlr4/runtime/Go/antlr/v4/atn_simulator.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2012-2022 The ANTLR Project. All rights reserved.
2 | // Use of this file is governed by the BSD 3-clause license that
3 | // can be found in the LICENSE.txt file in the project root.
4 |
5 | package antlr
6 |
7 | var ATNSimulatorError = NewDFAState(0x7FFFFFFF, NewBaseATNConfigSet(false))
8 |
9 | type IATNSimulator interface {
10 | SharedContextCache() *PredictionContextCache
11 | ATN() *ATN
12 | DecisionToDFA() []*DFA
13 | }
14 |
15 | type BaseATNSimulator struct {
16 | atn *ATN
17 | sharedContextCache *PredictionContextCache
18 | decisionToDFA []*DFA
19 | }
20 |
21 | func NewBaseATNSimulator(atn *ATN, sharedContextCache *PredictionContextCache) *BaseATNSimulator {
22 | b := new(BaseATNSimulator)
23 |
24 | b.atn = atn
25 | b.sharedContextCache = sharedContextCache
26 |
27 | return b
28 | }
29 |
30 | func (b *BaseATNSimulator) getCachedContext(context PredictionContext) PredictionContext {
31 | if b.sharedContextCache == nil {
32 | return context
33 | }
34 |
35 | visited := make(map[PredictionContext]PredictionContext)
36 |
37 | return getCachedBasePredictionContext(context, b.sharedContextCache, visited)
38 | }
39 |
40 | func (b *BaseATNSimulator) SharedContextCache() *PredictionContextCache {
41 | return b.sharedContextCache
42 | }
43 |
44 | func (b *BaseATNSimulator) ATN() *ATN {
45 | return b.atn
46 | }
47 |
48 | func (b *BaseATNSimulator) DecisionToDFA() []*DFA {
49 | return b.decisionToDFA
50 | }
51 |
--------------------------------------------------------------------------------
/lib/turso/vendor/github.com/antlr/antlr4/runtime/Go/antlr/v4/atn_type.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2012-2022 The ANTLR Project. All rights reserved.
2 | // Use of this file is governed by the BSD 3-clause license that
3 | // can be found in the LICENSE.txt file in the project root.
4 |
5 | package antlr
6 |
7 | // Represent the type of recognizer an ATN applies to.
8 | const (
9 | ATNTypeLexer = 0
10 | ATNTypeParser = 1
11 | )
12 |
--------------------------------------------------------------------------------
/lib/turso/vendor/github.com/antlr/antlr4/runtime/Go/antlr/v4/char_stream.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2012-2022 The ANTLR Project. All rights reserved.
2 | // Use of this file is governed by the BSD 3-clause license that
3 | // can be found in the LICENSE.txt file in the project root.
4 |
5 | package antlr
6 |
7 | type CharStream interface {
8 | IntStream
9 | GetText(int, int) string
10 | GetTextFromTokens(start, end Token) string
11 | GetTextFromInterval(*Interval) string
12 | }
13 |
--------------------------------------------------------------------------------
/lib/turso/vendor/github.com/antlr/antlr4/runtime/Go/antlr/v4/common_token_factory.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2012-2022 The ANTLR Project. All rights reserved.
2 | // Use of this file is governed by the BSD 3-clause license that
3 | // can be found in the LICENSE.txt file in the project root.
4 |
5 | package antlr
6 |
7 | // TokenFactory creates CommonToken objects.
8 | type TokenFactory interface {
9 | Create(source *TokenSourceCharStreamPair, ttype int, text string, channel, start, stop, line, column int) Token
10 | }
11 |
12 | // CommonTokenFactory is the default TokenFactory implementation.
13 | type CommonTokenFactory struct {
14 | // copyText indicates whether CommonToken.setText should be called after
15 | // constructing tokens to explicitly set the text. This is useful for cases
16 | // where the input stream might not be able to provide arbitrary substrings of
17 | // text from the input after the lexer creates a token (e.g. the
18 | // implementation of CharStream.GetText in UnbufferedCharStream panics an
19 | // UnsupportedOperationException). Explicitly setting the token text allows
20 | // Token.GetText to be called at any time regardless of the input stream
21 | // implementation.
22 | //
23 | // The default value is false to avoid the performance and memory overhead of
24 | // copying text for every token unless explicitly requested.
25 | copyText bool
26 | }
27 |
28 | func NewCommonTokenFactory(copyText bool) *CommonTokenFactory {
29 | return &CommonTokenFactory{copyText: copyText}
30 | }
31 |
32 | // CommonTokenFactoryDEFAULT is the default CommonTokenFactory. It does not
33 | // explicitly copy token text when constructing tokens.
34 | var CommonTokenFactoryDEFAULT = NewCommonTokenFactory(false)
35 |
36 | func (c *CommonTokenFactory) Create(source *TokenSourceCharStreamPair, ttype int, text string, channel, start, stop, line, column int) Token {
37 | t := NewCommonToken(source, ttype, channel, start, stop)
38 |
39 | t.line = line
40 | t.column = column
41 |
42 | if text != "" {
43 | t.SetText(text)
44 | } else if c.copyText && source.charStream != nil {
45 | t.SetText(source.charStream.GetTextFromInterval(NewInterval(start, stop)))
46 | }
47 |
48 | return t
49 | }
50 |
51 | func (c *CommonTokenFactory) createThin(ttype int, text string) Token {
52 | t := NewCommonToken(nil, ttype, TokenDefaultChannel, -1, -1)
53 | t.SetText(text)
54 |
55 | return t
56 | }
57 |
--------------------------------------------------------------------------------
/lib/turso/vendor/github.com/antlr/antlr4/runtime/Go/antlr/v4/dfa.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2012-2022 The ANTLR Project. All rights reserved.
2 | // Use of this file is governed by the BSD 3-clause license that
3 | // can be found in the LICENSE.txt file in the project root.
4 |
5 | package antlr
6 |
7 | type DFA struct {
8 | // atnStartState is the ATN state in which this was created
9 | atnStartState DecisionState
10 |
11 | decision int
12 |
13 | // states is all the DFA states. Use Map to get the old state back; Set can only
14 | // indicate whether it is there. Go maps implement key hash collisions and so on and are very
15 | // good, but the DFAState is an object and can't be used directly as the key as it can in say JAva
16 | // amd C#, whereby if the hashcode is the same for two objects, then Equals() is called against them
17 | // to see if they really are the same object.
18 | //
19 | //
20 | states *JStore[*DFAState, *ObjEqComparator[*DFAState]]
21 |
22 | numstates int
23 |
24 | s0 *DFAState
25 |
26 | // precedenceDfa is the backing field for isPrecedenceDfa and setPrecedenceDfa.
27 | // True if the DFA is for a precedence decision and false otherwise.
28 | precedenceDfa bool
29 | }
30 |
31 | func NewDFA(atnStartState DecisionState, decision int) *DFA {
32 | dfa := &DFA{
33 | atnStartState: atnStartState,
34 | decision: decision,
35 | states: NewJStore[*DFAState, *ObjEqComparator[*DFAState]](dfaStateEqInst),
36 | }
37 | if s, ok := atnStartState.(*StarLoopEntryState); ok && s.precedenceRuleDecision {
38 | dfa.precedenceDfa = true
39 | dfa.s0 = NewDFAState(-1, NewBaseATNConfigSet(false))
40 | dfa.s0.isAcceptState = false
41 | dfa.s0.requiresFullContext = false
42 | }
43 | return dfa
44 | }
45 |
46 | // getPrecedenceStartState gets the start state for the current precedence and
47 | // returns the start state corresponding to the specified precedence if a start
48 | // state exists for the specified precedence and nil otherwise. d must be a
49 | // precedence DFA. See also isPrecedenceDfa.
50 | func (d *DFA) getPrecedenceStartState(precedence int) *DFAState {
51 | if !d.getPrecedenceDfa() {
52 | panic("only precedence DFAs may contain a precedence start state")
53 | }
54 |
55 | // s0.edges is never nil for a precedence DFA
56 | if precedence < 0 || precedence >= len(d.getS0().getEdges()) {
57 | return nil
58 | }
59 |
60 | return d.getS0().getIthEdge(precedence)
61 | }
62 |
63 | // setPrecedenceStartState sets the start state for the current precedence. d
64 | // must be a precedence DFA. See also isPrecedenceDfa.
65 | func (d *DFA) setPrecedenceStartState(precedence int, startState *DFAState) {
66 | if !d.getPrecedenceDfa() {
67 | panic("only precedence DFAs may contain a precedence start state")
68 | }
69 |
70 | if precedence < 0 {
71 | return
72 | }
73 |
74 | // Synchronization on s0 here is ok. When the DFA is turned into a
75 | // precedence DFA, s0 will be initialized once and not updated again. s0.edges
76 | // is never nil for a precedence DFA.
77 | s0 := d.getS0()
78 | if precedence >= s0.numEdges() {
79 | edges := append(s0.getEdges(), make([]*DFAState, precedence+1-s0.numEdges())...)
80 | s0.setEdges(edges)
81 | d.setS0(s0)
82 | }
83 |
84 | s0.setIthEdge(precedence, startState)
85 | }
86 |
87 | func (d *DFA) getPrecedenceDfa() bool {
88 | return d.precedenceDfa
89 | }
90 |
91 | // setPrecedenceDfa sets whether d is a precedence DFA. If precedenceDfa differs
92 | // from the current DFA configuration, then d.states is cleared, the initial
93 | // state s0 is set to a new DFAState with an empty outgoing DFAState.edges to
94 | // store the start states for individual precedence values if precedenceDfa is
95 | // true or nil otherwise, and d.precedenceDfa is updated.
96 | func (d *DFA) setPrecedenceDfa(precedenceDfa bool) {
97 | if d.getPrecedenceDfa() != precedenceDfa {
98 | d.states = NewJStore[*DFAState, *ObjEqComparator[*DFAState]](dfaStateEqInst)
99 | d.numstates = 0
100 |
101 | if precedenceDfa {
102 | precedenceState := NewDFAState(-1, NewBaseATNConfigSet(false))
103 |
104 | precedenceState.setEdges(make([]*DFAState, 0))
105 | precedenceState.isAcceptState = false
106 | precedenceState.requiresFullContext = false
107 | d.setS0(precedenceState)
108 | } else {
109 | d.setS0(nil)
110 | }
111 |
112 | d.precedenceDfa = precedenceDfa
113 | }
114 | }
115 |
116 | func (d *DFA) getS0() *DFAState {
117 | return d.s0
118 | }
119 |
120 | func (d *DFA) setS0(s *DFAState) {
121 | d.s0 = s
122 | }
123 |
124 | // sortedStates returns the states in d sorted by their state number.
125 | func (d *DFA) sortedStates() []*DFAState {
126 |
127 | vs := d.states.SortedSlice(func(i, j *DFAState) bool {
128 | return i.stateNumber < j.stateNumber
129 | })
130 |
131 | return vs
132 | }
133 |
134 | func (d *DFA) String(literalNames []string, symbolicNames []string) string {
135 | if d.getS0() == nil {
136 | return ""
137 | }
138 |
139 | return NewDFASerializer(d, literalNames, symbolicNames).String()
140 | }
141 |
142 | func (d *DFA) ToLexerString() string {
143 | if d.getS0() == nil {
144 | return ""
145 | }
146 |
147 | return NewLexerDFASerializer(d).String()
148 | }
149 |
--------------------------------------------------------------------------------
/lib/turso/vendor/github.com/antlr/antlr4/runtime/Go/antlr/v4/dfa_serializer.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2012-2022 The ANTLR Project. All rights reserved.
2 | // Use of this file is governed by the BSD 3-clause license that
3 | // can be found in the LICENSE.txt file in the project root.
4 |
5 | package antlr
6 |
7 | import (
8 | "fmt"
9 | "strconv"
10 | "strings"
11 | )
12 |
13 | // DFASerializer is a DFA walker that knows how to dump them to serialized
14 | // strings.
15 | type DFASerializer struct {
16 | dfa *DFA
17 | literalNames []string
18 | symbolicNames []string
19 | }
20 |
21 | func NewDFASerializer(dfa *DFA, literalNames, symbolicNames []string) *DFASerializer {
22 | if literalNames == nil {
23 | literalNames = make([]string, 0)
24 | }
25 |
26 | if symbolicNames == nil {
27 | symbolicNames = make([]string, 0)
28 | }
29 |
30 | return &DFASerializer{
31 | dfa: dfa,
32 | literalNames: literalNames,
33 | symbolicNames: symbolicNames,
34 | }
35 | }
36 |
37 | func (d *DFASerializer) String() string {
38 | if d.dfa.getS0() == nil {
39 | return ""
40 | }
41 |
42 | buf := ""
43 | states := d.dfa.sortedStates()
44 |
45 | for _, s := range states {
46 | if s.edges != nil {
47 | n := len(s.edges)
48 |
49 | for j := 0; j < n; j++ {
50 | t := s.edges[j]
51 |
52 | if t != nil && t.stateNumber != 0x7FFFFFFF {
53 | buf += d.GetStateString(s)
54 | buf += "-"
55 | buf += d.getEdgeLabel(j)
56 | buf += "->"
57 | buf += d.GetStateString(t)
58 | buf += "\n"
59 | }
60 | }
61 | }
62 | }
63 |
64 | if len(buf) == 0 {
65 | return ""
66 | }
67 |
68 | return buf
69 | }
70 |
71 | func (d *DFASerializer) getEdgeLabel(i int) string {
72 | if i == 0 {
73 | return "EOF"
74 | } else if d.literalNames != nil && i-1 < len(d.literalNames) {
75 | return d.literalNames[i-1]
76 | } else if d.symbolicNames != nil && i-1 < len(d.symbolicNames) {
77 | return d.symbolicNames[i-1]
78 | }
79 |
80 | return strconv.Itoa(i - 1)
81 | }
82 |
83 | func (d *DFASerializer) GetStateString(s *DFAState) string {
84 | var a, b string
85 |
86 | if s.isAcceptState {
87 | a = ":"
88 | }
89 |
90 | if s.requiresFullContext {
91 | b = "^"
92 | }
93 |
94 | baseStateStr := a + "s" + strconv.Itoa(s.stateNumber) + b
95 |
96 | if s.isAcceptState {
97 | if s.predicates != nil {
98 | return baseStateStr + "=>" + fmt.Sprint(s.predicates)
99 | }
100 |
101 | return baseStateStr + "=>" + fmt.Sprint(s.prediction)
102 | }
103 |
104 | return baseStateStr
105 | }
106 |
107 | type LexerDFASerializer struct {
108 | *DFASerializer
109 | }
110 |
111 | func NewLexerDFASerializer(dfa *DFA) *LexerDFASerializer {
112 | return &LexerDFASerializer{DFASerializer: NewDFASerializer(dfa, nil, nil)}
113 | }
114 |
115 | func (l *LexerDFASerializer) getEdgeLabel(i int) string {
116 | var sb strings.Builder
117 | sb.Grow(6)
118 | sb.WriteByte('\'')
119 | sb.WriteRune(rune(i))
120 | sb.WriteByte('\'')
121 | return sb.String()
122 | }
123 |
124 | func (l *LexerDFASerializer) String() string {
125 | if l.dfa.getS0() == nil {
126 | return ""
127 | }
128 |
129 | buf := ""
130 | states := l.dfa.sortedStates()
131 |
132 | for i := 0; i < len(states); i++ {
133 | s := states[i]
134 |
135 | if s.edges != nil {
136 | n := len(s.edges)
137 |
138 | for j := 0; j < n; j++ {
139 | t := s.edges[j]
140 |
141 | if t != nil && t.stateNumber != 0x7FFFFFFF {
142 | buf += l.GetStateString(s)
143 | buf += "-"
144 | buf += l.getEdgeLabel(j)
145 | buf += "->"
146 | buf += l.GetStateString(t)
147 | buf += "\n"
148 | }
149 | }
150 | }
151 | }
152 |
153 | if len(buf) == 0 {
154 | return ""
155 | }
156 |
157 | return buf
158 | }
159 |
--------------------------------------------------------------------------------
/lib/turso/vendor/github.com/antlr/antlr4/runtime/Go/antlr/v4/diagnostic_error_listener.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2012-2022 The ANTLR Project. All rights reserved.
2 | // Use of this file is governed by the BSD 3-clause license that
3 | // can be found in the LICENSE.txt file in the project root.
4 |
5 | package antlr
6 |
7 | import (
8 | "strconv"
9 | )
10 |
11 | //
12 | // This implementation of {@link ANTLRErrorListener} can be used to identify
13 | // certain potential correctness and performance problems in grammars. "reports"
14 | // are made by calling {@link Parser//NotifyErrorListeners} with the appropriate
15 | // message.
16 | //
17 | //
18 | //
Ambiguities: These are cases where more than one path through the
19 | // grammar can Match the input.
20 | //
Weak context sensitivity: These are cases where full-context
21 | // prediction resolved an SLL conflict to a unique alternative which equaled the
22 | // minimum alternative of the SLL conflict.
23 | //
Strong (forced) context sensitivity: These are cases where the
24 | // full-context prediction resolved an SLL conflict to a unique alternative,
25 | // and the minimum alternative of the SLL conflict was found to not be
26 | // a truly viable alternative. Two-stage parsing cannot be used for inputs where
27 | // d situation occurs.
28 | //
29 |
30 | type DiagnosticErrorListener struct {
31 | *DefaultErrorListener
32 |
33 | exactOnly bool
34 | }
35 |
36 | func NewDiagnosticErrorListener(exactOnly bool) *DiagnosticErrorListener {
37 |
38 | n := new(DiagnosticErrorListener)
39 |
40 | // whether all ambiguities or only exact ambiguities are Reported.
41 | n.exactOnly = exactOnly
42 | return n
43 | }
44 |
45 | func (d *DiagnosticErrorListener) ReportAmbiguity(recognizer Parser, dfa *DFA, startIndex, stopIndex int, exact bool, ambigAlts *BitSet, configs ATNConfigSet) {
46 | if d.exactOnly && !exact {
47 | return
48 | }
49 | msg := "reportAmbiguity d=" +
50 | d.getDecisionDescription(recognizer, dfa) +
51 | ": ambigAlts=" +
52 | d.getConflictingAlts(ambigAlts, configs).String() +
53 | ", input='" +
54 | recognizer.GetTokenStream().GetTextFromInterval(NewInterval(startIndex, stopIndex)) + "'"
55 | recognizer.NotifyErrorListeners(msg, nil, nil)
56 | }
57 |
58 | func (d *DiagnosticErrorListener) ReportAttemptingFullContext(recognizer Parser, dfa *DFA, startIndex, stopIndex int, conflictingAlts *BitSet, configs ATNConfigSet) {
59 |
60 | msg := "reportAttemptingFullContext d=" +
61 | d.getDecisionDescription(recognizer, dfa) +
62 | ", input='" +
63 | recognizer.GetTokenStream().GetTextFromInterval(NewInterval(startIndex, stopIndex)) + "'"
64 | recognizer.NotifyErrorListeners(msg, nil, nil)
65 | }
66 |
67 | func (d *DiagnosticErrorListener) ReportContextSensitivity(recognizer Parser, dfa *DFA, startIndex, stopIndex, prediction int, configs ATNConfigSet) {
68 | msg := "reportContextSensitivity d=" +
69 | d.getDecisionDescription(recognizer, dfa) +
70 | ", input='" +
71 | recognizer.GetTokenStream().GetTextFromInterval(NewInterval(startIndex, stopIndex)) + "'"
72 | recognizer.NotifyErrorListeners(msg, nil, nil)
73 | }
74 |
75 | func (d *DiagnosticErrorListener) getDecisionDescription(recognizer Parser, dfa *DFA) string {
76 | decision := dfa.decision
77 | ruleIndex := dfa.atnStartState.GetRuleIndex()
78 |
79 | ruleNames := recognizer.GetRuleNames()
80 | if ruleIndex < 0 || ruleIndex >= len(ruleNames) {
81 | return strconv.Itoa(decision)
82 | }
83 | ruleName := ruleNames[ruleIndex]
84 | if ruleName == "" {
85 | return strconv.Itoa(decision)
86 | }
87 | return strconv.Itoa(decision) + " (" + ruleName + ")"
88 | }
89 |
90 | // Computes the set of conflicting or ambiguous alternatives from a
91 | // configuration set, if that information was not already provided by the
92 | // parser.
93 | //
94 | // @param ReportedAlts The set of conflicting or ambiguous alternatives, as
95 | // Reported by the parser.
96 | // @param configs The conflicting or ambiguous configuration set.
97 | // @return Returns {@code ReportedAlts} if it is not {@code nil}, otherwise
98 | // returns the set of alternatives represented in {@code configs}.
99 | func (d *DiagnosticErrorListener) getConflictingAlts(ReportedAlts *BitSet, set ATNConfigSet) *BitSet {
100 | if ReportedAlts != nil {
101 | return ReportedAlts
102 | }
103 | result := NewBitSet()
104 | for _, c := range set.GetItems() {
105 | result.add(c.GetAlt())
106 | }
107 |
108 | return result
109 | }
110 |
--------------------------------------------------------------------------------
/lib/turso/vendor/github.com/antlr/antlr4/runtime/Go/antlr/v4/error_listener.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2012-2022 The ANTLR Project. All rights reserved.
2 | // Use of this file is governed by the BSD 3-clause license that
3 | // can be found in the LICENSE.txt file in the project root.
4 |
5 | package antlr
6 |
7 | import (
8 | "fmt"
9 | "os"
10 | "strconv"
11 | )
12 |
13 | // Provides an empty default implementation of {@link ANTLRErrorListener}. The
14 | // default implementation of each method does nothing, but can be overridden as
15 | // necessary.
16 |
17 | type ErrorListener interface {
18 | SyntaxError(recognizer Recognizer, offendingSymbol interface{}, line, column int, msg string, e RecognitionException)
19 | ReportAmbiguity(recognizer Parser, dfa *DFA, startIndex, stopIndex int, exact bool, ambigAlts *BitSet, configs ATNConfigSet)
20 | ReportAttemptingFullContext(recognizer Parser, dfa *DFA, startIndex, stopIndex int, conflictingAlts *BitSet, configs ATNConfigSet)
21 | ReportContextSensitivity(recognizer Parser, dfa *DFA, startIndex, stopIndex, prediction int, configs ATNConfigSet)
22 | }
23 |
24 | type DefaultErrorListener struct {
25 | }
26 |
27 | func NewDefaultErrorListener() *DefaultErrorListener {
28 | return new(DefaultErrorListener)
29 | }
30 |
31 | func (d *DefaultErrorListener) SyntaxError(recognizer Recognizer, offendingSymbol interface{}, line, column int, msg string, e RecognitionException) {
32 | }
33 |
34 | func (d *DefaultErrorListener) ReportAmbiguity(recognizer Parser, dfa *DFA, startIndex, stopIndex int, exact bool, ambigAlts *BitSet, configs ATNConfigSet) {
35 | }
36 |
37 | func (d *DefaultErrorListener) ReportAttemptingFullContext(recognizer Parser, dfa *DFA, startIndex, stopIndex int, conflictingAlts *BitSet, configs ATNConfigSet) {
38 | }
39 |
40 | func (d *DefaultErrorListener) ReportContextSensitivity(recognizer Parser, dfa *DFA, startIndex, stopIndex, prediction int, configs ATNConfigSet) {
41 | }
42 |
43 | type ConsoleErrorListener struct {
44 | *DefaultErrorListener
45 | }
46 |
47 | func NewConsoleErrorListener() *ConsoleErrorListener {
48 | return new(ConsoleErrorListener)
49 | }
50 |
51 | // Provides a default instance of {@link ConsoleErrorListener}.
52 | var ConsoleErrorListenerINSTANCE = NewConsoleErrorListener()
53 |
54 | // {@inheritDoc}
55 | //
56 | //
57 | // This implementation prints messages to {@link System//err} containing the
58 | // values of {@code line}, {@code charPositionInLine}, and {@code msg} using
59 | // the following format.
60 | //
61 | //
62 | // line line:charPositionInLinemsg
63 | //
64 | func (c *ConsoleErrorListener) SyntaxError(recognizer Recognizer, offendingSymbol interface{}, line, column int, msg string, e RecognitionException) {
65 | fmt.Fprintln(os.Stderr, "line "+strconv.Itoa(line)+":"+strconv.Itoa(column)+" "+msg)
66 | }
67 |
68 | type ProxyErrorListener struct {
69 | *DefaultErrorListener
70 | delegates []ErrorListener
71 | }
72 |
73 | func NewProxyErrorListener(delegates []ErrorListener) *ProxyErrorListener {
74 | if delegates == nil {
75 | panic("delegates is not provided")
76 | }
77 | l := new(ProxyErrorListener)
78 | l.delegates = delegates
79 | return l
80 | }
81 |
82 | func (p *ProxyErrorListener) SyntaxError(recognizer Recognizer, offendingSymbol interface{}, line, column int, msg string, e RecognitionException) {
83 | for _, d := range p.delegates {
84 | d.SyntaxError(recognizer, offendingSymbol, line, column, msg, e)
85 | }
86 | }
87 |
88 | func (p *ProxyErrorListener) ReportAmbiguity(recognizer Parser, dfa *DFA, startIndex, stopIndex int, exact bool, ambigAlts *BitSet, configs ATNConfigSet) {
89 | for _, d := range p.delegates {
90 | d.ReportAmbiguity(recognizer, dfa, startIndex, stopIndex, exact, ambigAlts, configs)
91 | }
92 | }
93 |
94 | func (p *ProxyErrorListener) ReportAttemptingFullContext(recognizer Parser, dfa *DFA, startIndex, stopIndex int, conflictingAlts *BitSet, configs ATNConfigSet) {
95 | for _, d := range p.delegates {
96 | d.ReportAttemptingFullContext(recognizer, dfa, startIndex, stopIndex, conflictingAlts, configs)
97 | }
98 | }
99 |
100 | func (p *ProxyErrorListener) ReportContextSensitivity(recognizer Parser, dfa *DFA, startIndex, stopIndex, prediction int, configs ATNConfigSet) {
101 | for _, d := range p.delegates {
102 | d.ReportContextSensitivity(recognizer, dfa, startIndex, stopIndex, prediction, configs)
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/lib/turso/vendor/github.com/antlr/antlr4/runtime/Go/antlr/v4/file_stream.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2012-2022 The ANTLR Project. All rights reserved.
2 | // Use of this file is governed by the BSD 3-clause license that
3 | // can be found in the LICENSE.txt file in the project root.
4 |
5 | package antlr
6 |
7 | import (
8 | "bytes"
9 | "io"
10 | "os"
11 | )
12 |
13 | // This is an InputStream that is loaded from a file all at once
14 | // when you construct the object.
15 |
16 | type FileStream struct {
17 | *InputStream
18 |
19 | filename string
20 | }
21 |
22 | func NewFileStream(fileName string) (*FileStream, error) {
23 |
24 | buf := bytes.NewBuffer(nil)
25 |
26 | f, err := os.Open(fileName)
27 | if err != nil {
28 | return nil, err
29 | }
30 | defer f.Close()
31 | _, err = io.Copy(buf, f)
32 | if err != nil {
33 | return nil, err
34 | }
35 |
36 | fs := new(FileStream)
37 |
38 | fs.filename = fileName
39 | s := string(buf.Bytes())
40 |
41 | fs.InputStream = NewInputStream(s)
42 |
43 | return fs, nil
44 |
45 | }
46 |
47 | func (f *FileStream) GetSourceName() string {
48 | return f.filename
49 | }
50 |
--------------------------------------------------------------------------------
/lib/turso/vendor/github.com/antlr/antlr4/runtime/Go/antlr/v4/input_stream.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2012-2022 The ANTLR Project. All rights reserved.
2 | // Use of this file is governed by the BSD 3-clause license that
3 | // can be found in the LICENSE.txt file in the project root.
4 |
5 | package antlr
6 |
7 | type InputStream struct {
8 | name string
9 | index int
10 | data []rune
11 | size int
12 | }
13 |
14 | func NewInputStream(data string) *InputStream {
15 |
16 | is := new(InputStream)
17 |
18 | is.name = ""
19 | is.index = 0
20 | is.data = []rune(data)
21 | is.size = len(is.data) // number of runes
22 |
23 | return is
24 | }
25 |
26 | func (is *InputStream) reset() {
27 | is.index = 0
28 | }
29 |
30 | func (is *InputStream) Consume() {
31 | if is.index >= is.size {
32 | // assert is.LA(1) == TokenEOF
33 | panic("cannot consume EOF")
34 | }
35 | is.index++
36 | }
37 |
38 | func (is *InputStream) LA(offset int) int {
39 |
40 | if offset == 0 {
41 | return 0 // nil
42 | }
43 | if offset < 0 {
44 | offset++ // e.g., translate LA(-1) to use offset=0
45 | }
46 | pos := is.index + offset - 1
47 |
48 | if pos < 0 || pos >= is.size { // invalid
49 | return TokenEOF
50 | }
51 |
52 | return int(is.data[pos])
53 | }
54 |
55 | func (is *InputStream) LT(offset int) int {
56 | return is.LA(offset)
57 | }
58 |
59 | func (is *InputStream) Index() int {
60 | return is.index
61 | }
62 |
63 | func (is *InputStream) Size() int {
64 | return is.size
65 | }
66 |
67 | // mark/release do nothing we have entire buffer
68 | func (is *InputStream) Mark() int {
69 | return -1
70 | }
71 |
72 | func (is *InputStream) Release(marker int) {
73 | }
74 |
75 | func (is *InputStream) Seek(index int) {
76 | if index <= is.index {
77 | is.index = index // just jump don't update stream state (line,...)
78 | return
79 | }
80 | // seek forward
81 | is.index = intMin(index, is.size)
82 | }
83 |
84 | func (is *InputStream) GetText(start int, stop int) string {
85 | if stop >= is.size {
86 | stop = is.size - 1
87 | }
88 | if start >= is.size {
89 | return ""
90 | }
91 |
92 | return string(is.data[start : stop+1])
93 | }
94 |
95 | func (is *InputStream) GetTextFromTokens(start, stop Token) string {
96 | if start != nil && stop != nil {
97 | return is.GetTextFromInterval(NewInterval(start.GetTokenIndex(), stop.GetTokenIndex()))
98 | }
99 |
100 | return ""
101 | }
102 |
103 | func (is *InputStream) GetTextFromInterval(i *Interval) string {
104 | return is.GetText(i.Start, i.Stop)
105 | }
106 |
107 | func (*InputStream) GetSourceName() string {
108 | return "Obtained from string"
109 | }
110 |
111 | func (is *InputStream) String() string {
112 | return string(is.data)
113 | }
114 |
--------------------------------------------------------------------------------
/lib/turso/vendor/github.com/antlr/antlr4/runtime/Go/antlr/v4/int_stream.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2012-2022 The ANTLR Project. All rights reserved.
2 | // Use of this file is governed by the BSD 3-clause license that
3 | // can be found in the LICENSE.txt file in the project root.
4 |
5 | package antlr
6 |
7 | type IntStream interface {
8 | Consume()
9 | LA(int) int
10 | Mark() int
11 | Release(marker int)
12 | Index() int
13 | Seek(index int)
14 | Size() int
15 | GetSourceName() string
16 | }
17 |
--------------------------------------------------------------------------------
/lib/turso/vendor/github.com/antlr/antlr4/runtime/Go/antlr/v4/rule_context.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2012-2022 The ANTLR Project. All rights reserved.
2 | // Use of this file is governed by the BSD 3-clause license that
3 | // can be found in the LICENSE.txt file in the project root.
4 |
5 | package antlr
6 |
7 | // A rule context is a record of a single rule invocation. It knows
8 | // which context invoked it, if any. If there is no parent context, then
9 | // naturally the invoking state is not valid. The parent link
10 | // provides a chain upwards from the current rule invocation to the root
11 | // of the invocation tree, forming a stack. We actually carry no
12 | // information about the rule associated with b context (except
13 | // when parsing). We keep only the state number of the invoking state from
14 | // the ATN submachine that invoked b. Contrast b with the s
15 | // pointer inside ParserRuleContext that tracks the current state
16 | // being "executed" for the current rule.
17 | //
18 | // The parent contexts are useful for computing lookahead sets and
19 | // getting error information.
20 | //
21 | // These objects are used during parsing and prediction.
22 | // For the special case of parsers, we use the subclass
23 | // ParserRuleContext.
24 | //
25 | // @see ParserRuleContext
26 | //
27 |
28 | type RuleContext interface {
29 | RuleNode
30 |
31 | GetInvokingState() int
32 | SetInvokingState(int)
33 |
34 | GetRuleIndex() int
35 | IsEmpty() bool
36 |
37 | GetAltNumber() int
38 | SetAltNumber(altNumber int)
39 |
40 | String([]string, RuleContext) string
41 | }
42 |
43 | type BaseRuleContext struct {
44 | parentCtx RuleContext
45 | invokingState int
46 | RuleIndex int
47 | }
48 |
49 | func NewBaseRuleContext(parent RuleContext, invokingState int) *BaseRuleContext {
50 |
51 | rn := new(BaseRuleContext)
52 |
53 | // What context invoked b rule?
54 | rn.parentCtx = parent
55 |
56 | // What state invoked the rule associated with b context?
57 | // The "return address" is the followState of invokingState
58 | // If parent is nil, b should be -1.
59 | if parent == nil {
60 | rn.invokingState = -1
61 | } else {
62 | rn.invokingState = invokingState
63 | }
64 |
65 | return rn
66 | }
67 |
68 | func (b *BaseRuleContext) GetBaseRuleContext() *BaseRuleContext {
69 | return b
70 | }
71 |
72 | func (b *BaseRuleContext) SetParent(v Tree) {
73 | if v == nil {
74 | b.parentCtx = nil
75 | } else {
76 | b.parentCtx = v.(RuleContext)
77 | }
78 | }
79 |
80 | func (b *BaseRuleContext) GetInvokingState() int {
81 | return b.invokingState
82 | }
83 |
84 | func (b *BaseRuleContext) SetInvokingState(t int) {
85 | b.invokingState = t
86 | }
87 |
88 | func (b *BaseRuleContext) GetRuleIndex() int {
89 | return b.RuleIndex
90 | }
91 |
92 | func (b *BaseRuleContext) GetAltNumber() int {
93 | return ATNInvalidAltNumber
94 | }
95 |
96 | func (b *BaseRuleContext) SetAltNumber(altNumber int) {}
97 |
98 | // A context is empty if there is no invoking state meaning nobody call
99 | // current context.
100 | func (b *BaseRuleContext) IsEmpty() bool {
101 | return b.invokingState == -1
102 | }
103 |
104 | // Return the combined text of all child nodes. This method only considers
105 | // tokens which have been added to the parse tree.
106 | //
107 | // Since tokens on hidden channels (e.g. whitespace or comments) are not
108 | // added to the parse trees, they will not appear in the output of b
109 | // method.
110 | //
111 |
112 | func (b *BaseRuleContext) GetParent() Tree {
113 | return b.parentCtx
114 | }
115 |
--------------------------------------------------------------------------------
/lib/turso/vendor/github.com/antlr/antlr4/runtime/Go/antlr/v4/token.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2012-2022 The ANTLR Project. All rights reserved.
2 | // Use of this file is governed by the BSD 3-clause license that
3 | // can be found in the LICENSE.txt file in the project root.
4 |
5 | package antlr
6 |
7 | import (
8 | "strconv"
9 | "strings"
10 | )
11 |
12 | type TokenSourceCharStreamPair struct {
13 | tokenSource TokenSource
14 | charStream CharStream
15 | }
16 |
17 | // A token has properties: text, type, line, character position in the line
18 | // (so we can ignore tabs), token channel, index, and source from which
19 | // we obtained this token.
20 |
21 | type Token interface {
22 | GetSource() *TokenSourceCharStreamPair
23 | GetTokenType() int
24 | GetChannel() int
25 | GetStart() int
26 | GetStop() int
27 | GetLine() int
28 | GetColumn() int
29 |
30 | GetText() string
31 | SetText(s string)
32 |
33 | GetTokenIndex() int
34 | SetTokenIndex(v int)
35 |
36 | GetTokenSource() TokenSource
37 | GetInputStream() CharStream
38 | }
39 |
40 | type BaseToken struct {
41 | source *TokenSourceCharStreamPair
42 | tokenType int // token type of the token
43 | channel int // The parser ignores everything not on DEFAULT_CHANNEL
44 | start int // optional return -1 if not implemented.
45 | stop int // optional return -1 if not implemented.
46 | tokenIndex int // from 0..n-1 of the token object in the input stream
47 | line int // line=1..n of the 1st character
48 | column int // beginning of the line at which it occurs, 0..n-1
49 | text string // text of the token.
50 | readOnly bool
51 | }
52 |
53 | const (
54 | TokenInvalidType = 0
55 |
56 | // During lookahead operations, this "token" signifies we hit rule end ATN state
57 | // and did not follow it despite needing to.
58 | TokenEpsilon = -2
59 |
60 | TokenMinUserTokenType = 1
61 |
62 | TokenEOF = -1
63 |
64 | // All tokens go to the parser (unless Skip() is called in that rule)
65 | // on a particular "channel". The parser tunes to a particular channel
66 | // so that whitespace etc... can go to the parser on a "hidden" channel.
67 |
68 | TokenDefaultChannel = 0
69 |
70 | // Anything on different channel than DEFAULT_CHANNEL is not parsed
71 | // by parser.
72 |
73 | TokenHiddenChannel = 1
74 | )
75 |
76 | func (b *BaseToken) GetChannel() int {
77 | return b.channel
78 | }
79 |
80 | func (b *BaseToken) GetStart() int {
81 | return b.start
82 | }
83 |
84 | func (b *BaseToken) GetStop() int {
85 | return b.stop
86 | }
87 |
88 | func (b *BaseToken) GetLine() int {
89 | return b.line
90 | }
91 |
92 | func (b *BaseToken) GetColumn() int {
93 | return b.column
94 | }
95 |
96 | func (b *BaseToken) GetTokenType() int {
97 | return b.tokenType
98 | }
99 |
100 | func (b *BaseToken) GetSource() *TokenSourceCharStreamPair {
101 | return b.source
102 | }
103 |
104 | func (b *BaseToken) GetTokenIndex() int {
105 | return b.tokenIndex
106 | }
107 |
108 | func (b *BaseToken) SetTokenIndex(v int) {
109 | b.tokenIndex = v
110 | }
111 |
112 | func (b *BaseToken) GetTokenSource() TokenSource {
113 | return b.source.tokenSource
114 | }
115 |
116 | func (b *BaseToken) GetInputStream() CharStream {
117 | return b.source.charStream
118 | }
119 |
120 | type CommonToken struct {
121 | *BaseToken
122 | }
123 |
124 | func NewCommonToken(source *TokenSourceCharStreamPair, tokenType, channel, start, stop int) *CommonToken {
125 |
126 | t := new(CommonToken)
127 |
128 | t.BaseToken = new(BaseToken)
129 |
130 | t.source = source
131 | t.tokenType = tokenType
132 | t.channel = channel
133 | t.start = start
134 | t.stop = stop
135 | t.tokenIndex = -1
136 | if t.source.tokenSource != nil {
137 | t.line = source.tokenSource.GetLine()
138 | t.column = source.tokenSource.GetCharPositionInLine()
139 | } else {
140 | t.column = -1
141 | }
142 | return t
143 | }
144 |
145 | // An empty {@link Pair} which is used as the default value of
146 | // {@link //source} for tokens that do not have a source.
147 |
148 | //CommonToken.EMPTY_SOURCE = [ nil, nil ]
149 |
150 | // Constructs a New{@link CommonToken} as a copy of another {@link Token}.
151 | //
152 | //
153 | // If {@code oldToken} is also a {@link CommonToken} instance, the newly
154 | // constructed token will share a reference to the {@link //text} field and
155 | // the {@link Pair} stored in {@link //source}. Otherwise, {@link //text} will
156 | // be assigned the result of calling {@link //GetText}, and {@link //source}
157 | // will be constructed from the result of {@link Token//GetTokenSource} and
158 | // {@link Token//GetInputStream}.
159 | //
160 | // @param oldToken The token to copy.
161 | func (c *CommonToken) clone() *CommonToken {
162 | t := NewCommonToken(c.source, c.tokenType, c.channel, c.start, c.stop)
163 | t.tokenIndex = c.GetTokenIndex()
164 | t.line = c.GetLine()
165 | t.column = c.GetColumn()
166 | t.text = c.GetText()
167 | return t
168 | }
169 |
170 | func (c *CommonToken) GetText() string {
171 | if c.text != "" {
172 | return c.text
173 | }
174 | input := c.GetInputStream()
175 | if input == nil {
176 | return ""
177 | }
178 | n := input.Size()
179 | if c.start < n && c.stop < n {
180 | return input.GetTextFromInterval(NewInterval(c.start, c.stop))
181 | }
182 | return ""
183 | }
184 |
185 | func (c *CommonToken) SetText(text string) {
186 | c.text = text
187 | }
188 |
189 | func (c *CommonToken) String() string {
190 | txt := c.GetText()
191 | if txt != "" {
192 | txt = strings.Replace(txt, "\n", "\\n", -1)
193 | txt = strings.Replace(txt, "\r", "\\r", -1)
194 | txt = strings.Replace(txt, "\t", "\\t", -1)
195 | } else {
196 | txt = ""
197 | }
198 |
199 | var ch string
200 | if c.channel > 0 {
201 | ch = ",channel=" + strconv.Itoa(c.channel)
202 | } else {
203 | ch = ""
204 | }
205 |
206 | return "[@" + strconv.Itoa(c.tokenIndex) + "," + strconv.Itoa(c.start) + ":" + strconv.Itoa(c.stop) + "='" +
207 | txt + "',<" + strconv.Itoa(c.tokenType) + ">" +
208 | ch + "," + strconv.Itoa(c.line) + ":" + strconv.Itoa(c.column) + "]"
209 | }
210 |
--------------------------------------------------------------------------------
/lib/turso/vendor/github.com/antlr/antlr4/runtime/Go/antlr/v4/token_source.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2012-2022 The ANTLR Project. All rights reserved.
2 | // Use of this file is governed by the BSD 3-clause license that
3 | // can be found in the LICENSE.txt file in the project root.
4 |
5 | package antlr
6 |
7 | type TokenSource interface {
8 | NextToken() Token
9 | Skip()
10 | More()
11 | GetLine() int
12 | GetCharPositionInLine() int
13 | GetInputStream() CharStream
14 | GetSourceName() string
15 | setTokenFactory(factory TokenFactory)
16 | GetTokenFactory() TokenFactory
17 | }
18 |
--------------------------------------------------------------------------------
/lib/turso/vendor/github.com/antlr/antlr4/runtime/Go/antlr/v4/token_stream.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2012-2022 The ANTLR Project. All rights reserved.
2 | // Use of this file is governed by the BSD 3-clause license that
3 | // can be found in the LICENSE.txt file in the project root.
4 |
5 | package antlr
6 |
7 | type TokenStream interface {
8 | IntStream
9 |
10 | LT(k int) Token
11 |
12 | Get(index int) Token
13 | GetTokenSource() TokenSource
14 | SetTokenSource(TokenSource)
15 |
16 | GetAllText() string
17 | GetTextFromInterval(*Interval) string
18 | GetTextFromRuleContext(RuleContext) string
19 | GetTextFromTokens(Token, Token) string
20 | }
21 |
--------------------------------------------------------------------------------
/lib/turso/vendor/github.com/antlr/antlr4/runtime/Go/antlr/v4/trace_listener.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2012-2022 The ANTLR Project. All rights reserved.
2 | // Use of this file is governed by the BSD 3-clause license that
3 | // can be found in the LICENSE.txt file in the project root.
4 |
5 | package antlr
6 |
7 | import "fmt"
8 |
9 | type TraceListener struct {
10 | parser *BaseParser
11 | }
12 |
13 | func NewTraceListener(parser *BaseParser) *TraceListener {
14 | tl := new(TraceListener)
15 | tl.parser = parser
16 | return tl
17 | }
18 |
19 | func (t *TraceListener) VisitErrorNode(_ ErrorNode) {
20 | }
21 |
22 | func (t *TraceListener) EnterEveryRule(ctx ParserRuleContext) {
23 | fmt.Println("enter " + t.parser.GetRuleNames()[ctx.GetRuleIndex()] + ", LT(1)=" + t.parser.input.LT(1).GetText())
24 | }
25 |
26 | func (t *TraceListener) VisitTerminal(node TerminalNode) {
27 | fmt.Println("consume " + fmt.Sprint(node.GetSymbol()) + " rule " + t.parser.GetRuleNames()[t.parser.ctx.GetRuleIndex()])
28 | }
29 |
30 | func (t *TraceListener) ExitEveryRule(ctx ParserRuleContext) {
31 | fmt.Println("exit " + t.parser.GetRuleNames()[ctx.GetRuleIndex()] + ", LT(1)=" + t.parser.input.LT(1).GetText())
32 | }
33 |
--------------------------------------------------------------------------------
/lib/turso/vendor/github.com/antlr/antlr4/runtime/Go/antlr/v4/trees.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2012-2022 The ANTLR Project. All rights reserved.
2 | // Use of this file is governed by the BSD 3-clause license that
3 | // can be found in the LICENSE.txt file in the project root.
4 |
5 | package antlr
6 |
7 | import "fmt"
8 |
9 | /** A set of utility routines useful for all kinds of ANTLR trees. */
10 |
11 | // Print out a whole tree in LISP form. {@link //getNodeText} is used on the
12 | //
13 | // node payloads to get the text for the nodes. Detect
14 | // parse trees and extract data appropriately.
15 | func TreesStringTree(tree Tree, ruleNames []string, recog Recognizer) string {
16 |
17 | if recog != nil {
18 | ruleNames = recog.GetRuleNames()
19 | }
20 |
21 | s := TreesGetNodeText(tree, ruleNames, nil)
22 |
23 | s = EscapeWhitespace(s, false)
24 | c := tree.GetChildCount()
25 | if c == 0 {
26 | return s
27 | }
28 | res := "(" + s + " "
29 | if c > 0 {
30 | s = TreesStringTree(tree.GetChild(0), ruleNames, nil)
31 | res += s
32 | }
33 | for i := 1; i < c; i++ {
34 | s = TreesStringTree(tree.GetChild(i), ruleNames, nil)
35 | res += (" " + s)
36 | }
37 | res += ")"
38 | return res
39 | }
40 |
41 | func TreesGetNodeText(t Tree, ruleNames []string, recog Parser) string {
42 | if recog != nil {
43 | ruleNames = recog.GetRuleNames()
44 | }
45 |
46 | if ruleNames != nil {
47 | switch t2 := t.(type) {
48 | case RuleNode:
49 | t3 := t2.GetRuleContext()
50 | altNumber := t3.GetAltNumber()
51 |
52 | if altNumber != ATNInvalidAltNumber {
53 | return fmt.Sprintf("%s:%d", ruleNames[t3.GetRuleIndex()], altNumber)
54 | }
55 | return ruleNames[t3.GetRuleIndex()]
56 | case ErrorNode:
57 | return fmt.Sprint(t2)
58 | case TerminalNode:
59 | if t2.GetSymbol() != nil {
60 | return t2.GetSymbol().GetText()
61 | }
62 | }
63 | }
64 |
65 | // no recog for rule names
66 | payload := t.GetPayload()
67 | if p2, ok := payload.(Token); ok {
68 | return p2.GetText()
69 | }
70 |
71 | return fmt.Sprint(t.GetPayload())
72 | }
73 |
74 | // Return ordered list of all children of this node
75 | func TreesGetChildren(t Tree) []Tree {
76 | list := make([]Tree, 0)
77 | for i := 0; i < t.GetChildCount(); i++ {
78 | list = append(list, t.GetChild(i))
79 | }
80 | return list
81 | }
82 |
83 | // Return a list of all ancestors of this node. The first node of
84 | //
85 | // list is the root and the last is the parent of this node.
86 | func TreesgetAncestors(t Tree) []Tree {
87 | ancestors := make([]Tree, 0)
88 | t = t.GetParent()
89 | for t != nil {
90 | f := []Tree{t}
91 | ancestors = append(f, ancestors...)
92 | t = t.GetParent()
93 | }
94 | return ancestors
95 | }
96 |
97 | func TreesFindAllTokenNodes(t ParseTree, ttype int) []ParseTree {
98 | return TreesfindAllNodes(t, ttype, true)
99 | }
100 |
101 | func TreesfindAllRuleNodes(t ParseTree, ruleIndex int) []ParseTree {
102 | return TreesfindAllNodes(t, ruleIndex, false)
103 | }
104 |
105 | func TreesfindAllNodes(t ParseTree, index int, findTokens bool) []ParseTree {
106 | nodes := make([]ParseTree, 0)
107 | treesFindAllNodes(t, index, findTokens, &nodes)
108 | return nodes
109 | }
110 |
111 | func treesFindAllNodes(t ParseTree, index int, findTokens bool, nodes *[]ParseTree) {
112 | // check this node (the root) first
113 |
114 | t2, ok := t.(TerminalNode)
115 | t3, ok2 := t.(ParserRuleContext)
116 |
117 | if findTokens && ok {
118 | if t2.GetSymbol().GetTokenType() == index {
119 | *nodes = append(*nodes, t2)
120 | }
121 | } else if !findTokens && ok2 {
122 | if t3.GetRuleIndex() == index {
123 | *nodes = append(*nodes, t3)
124 | }
125 | }
126 | // check children
127 | for i := 0; i < t.GetChildCount(); i++ {
128 | treesFindAllNodes(t.GetChild(i).(ParseTree), index, findTokens, nodes)
129 | }
130 | }
131 |
132 | func TreesDescendants(t ParseTree) []ParseTree {
133 | nodes := []ParseTree{t}
134 | for i := 0; i < t.GetChildCount(); i++ {
135 | nodes = append(nodes, TreesDescendants(t.GetChild(i).(ParseTree))...)
136 | }
137 | return nodes
138 | }
139 |
--------------------------------------------------------------------------------
/lib/turso/vendor/github.com/antlr/antlr4/runtime/Go/antlr/v4/utils_set.go:
--------------------------------------------------------------------------------
1 | package antlr
2 |
3 | import "math"
4 |
5 | const (
6 | _initalCapacity = 16
7 | _initalBucketCapacity = 8
8 | _loadFactor = 0.75
9 | )
10 |
11 | type Set interface {
12 | Add(value interface{}) (added interface{})
13 | Len() int
14 | Get(value interface{}) (found interface{})
15 | Contains(value interface{}) bool
16 | Values() []interface{}
17 | Each(f func(interface{}) bool)
18 | }
19 |
20 | type array2DHashSet struct {
21 | buckets [][]Collectable[any]
22 | hashcodeFunction func(interface{}) int
23 | equalsFunction func(Collectable[any], Collectable[any]) bool
24 |
25 | n int // How many elements in set
26 | threshold int // when to expand
27 |
28 | currentPrime int // jump by 4 primes each expand or whatever
29 | initialBucketCapacity int
30 | }
31 |
32 | func (as *array2DHashSet) Each(f func(interface{}) bool) {
33 | if as.Len() < 1 {
34 | return
35 | }
36 |
37 | for _, bucket := range as.buckets {
38 | for _, o := range bucket {
39 | if o == nil {
40 | break
41 | }
42 | if !f(o) {
43 | return
44 | }
45 | }
46 | }
47 | }
48 |
49 | func (as *array2DHashSet) Values() []interface{} {
50 | if as.Len() < 1 {
51 | return nil
52 | }
53 |
54 | values := make([]interface{}, 0, as.Len())
55 | as.Each(func(i interface{}) bool {
56 | values = append(values, i)
57 | return true
58 | })
59 | return values
60 | }
61 |
62 | func (as *array2DHashSet) Contains(value Collectable[any]) bool {
63 | return as.Get(value) != nil
64 | }
65 |
66 | func (as *array2DHashSet) Add(value Collectable[any]) interface{} {
67 | if as.n > as.threshold {
68 | as.expand()
69 | }
70 | return as.innerAdd(value)
71 | }
72 |
73 | func (as *array2DHashSet) expand() {
74 | old := as.buckets
75 |
76 | as.currentPrime += 4
77 |
78 | var (
79 | newCapacity = len(as.buckets) << 1
80 | newTable = as.createBuckets(newCapacity)
81 | newBucketLengths = make([]int, len(newTable))
82 | )
83 |
84 | as.buckets = newTable
85 | as.threshold = int(float64(newCapacity) * _loadFactor)
86 |
87 | for _, bucket := range old {
88 | if bucket == nil {
89 | continue
90 | }
91 |
92 | for _, o := range bucket {
93 | if o == nil {
94 | break
95 | }
96 |
97 | b := as.getBuckets(o)
98 | bucketLength := newBucketLengths[b]
99 | var newBucket []Collectable[any]
100 | if bucketLength == 0 {
101 | // new bucket
102 | newBucket = as.createBucket(as.initialBucketCapacity)
103 | newTable[b] = newBucket
104 | } else {
105 | newBucket = newTable[b]
106 | if bucketLength == len(newBucket) {
107 | // expand
108 | newBucketCopy := make([]Collectable[any], len(newBucket)<<1)
109 | copy(newBucketCopy[:bucketLength], newBucket)
110 | newBucket = newBucketCopy
111 | newTable[b] = newBucket
112 | }
113 | }
114 |
115 | newBucket[bucketLength] = o
116 | newBucketLengths[b]++
117 | }
118 | }
119 | }
120 |
121 | func (as *array2DHashSet) Len() int {
122 | return as.n
123 | }
124 |
125 | func (as *array2DHashSet) Get(o Collectable[any]) interface{} {
126 | if o == nil {
127 | return nil
128 | }
129 |
130 | b := as.getBuckets(o)
131 | bucket := as.buckets[b]
132 | if bucket == nil { // no bucket
133 | return nil
134 | }
135 |
136 | for _, e := range bucket {
137 | if e == nil {
138 | return nil // empty slot; not there
139 | }
140 | if as.equalsFunction(e, o) {
141 | return e
142 | }
143 | }
144 |
145 | return nil
146 | }
147 |
148 | func (as *array2DHashSet) innerAdd(o Collectable[any]) interface{} {
149 | b := as.getBuckets(o)
150 |
151 | bucket := as.buckets[b]
152 |
153 | // new bucket
154 | if bucket == nil {
155 | bucket = as.createBucket(as.initialBucketCapacity)
156 | bucket[0] = o
157 |
158 | as.buckets[b] = bucket
159 | as.n++
160 | return o
161 | }
162 |
163 | // look for it in bucket
164 | for i := 0; i < len(bucket); i++ {
165 | existing := bucket[i]
166 | if existing == nil { // empty slot; not there, add.
167 | bucket[i] = o
168 | as.n++
169 | return o
170 | }
171 |
172 | if as.equalsFunction(existing, o) { // found existing, quit
173 | return existing
174 | }
175 | }
176 |
177 | // full bucket, expand and add to end
178 | oldLength := len(bucket)
179 | bucketCopy := make([]Collectable[any], oldLength<<1)
180 | copy(bucketCopy[:oldLength], bucket)
181 | bucket = bucketCopy
182 | as.buckets[b] = bucket
183 | bucket[oldLength] = o
184 | as.n++
185 | return o
186 | }
187 |
188 | func (as *array2DHashSet) getBuckets(value Collectable[any]) int {
189 | hash := as.hashcodeFunction(value)
190 | return hash & (len(as.buckets) - 1)
191 | }
192 |
193 | func (as *array2DHashSet) createBuckets(cap int) [][]Collectable[any] {
194 | return make([][]Collectable[any], cap)
195 | }
196 |
197 | func (as *array2DHashSet) createBucket(cap int) []Collectable[any] {
198 | return make([]Collectable[any], cap)
199 | }
200 |
201 | func newArray2DHashSetWithCap(
202 | hashcodeFunction func(interface{}) int,
203 | equalsFunction func(Collectable[any], Collectable[any]) bool,
204 | initCap int,
205 | initBucketCap int,
206 | ) *array2DHashSet {
207 | if hashcodeFunction == nil {
208 | hashcodeFunction = standardHashFunction
209 | }
210 |
211 | if equalsFunction == nil {
212 | equalsFunction = standardEqualsFunction
213 | }
214 |
215 | ret := &array2DHashSet{
216 | hashcodeFunction: hashcodeFunction,
217 | equalsFunction: equalsFunction,
218 |
219 | n: 0,
220 | threshold: int(math.Floor(_initalCapacity * _loadFactor)),
221 |
222 | currentPrime: 1,
223 | initialBucketCapacity: initBucketCap,
224 | }
225 |
226 | ret.buckets = ret.createBuckets(initCap)
227 | return ret
228 | }
229 |
230 | func newArray2DHashSet(
231 | hashcodeFunction func(interface{}) int,
232 | equalsFunction func(Collectable[any], Collectable[any]) bool,
233 | ) *array2DHashSet {
234 | return newArray2DHashSetWithCap(hashcodeFunction, equalsFunction, _initalCapacity, _initalBucketCapacity)
235 | }
236 |
--------------------------------------------------------------------------------
/lib/turso/vendor/github.com/klauspost/compress/flate/regmask_amd64.go:
--------------------------------------------------------------------------------
1 | package flate
2 |
3 | const (
4 | // Masks for shifts with register sizes of the shift value.
5 | // This can be used to work around the x86 design of shifting by mod register size.
6 | // It can be used when a variable shift is always smaller than the register size.
7 |
8 | // reg8SizeMaskX - shift value is 8 bits, shifted is X
9 | reg8SizeMask8 = 7
10 | reg8SizeMask16 = 15
11 | reg8SizeMask32 = 31
12 | reg8SizeMask64 = 63
13 |
14 | // reg16SizeMaskX - shift value is 16 bits, shifted is X
15 | reg16SizeMask8 = reg8SizeMask8
16 | reg16SizeMask16 = reg8SizeMask16
17 | reg16SizeMask32 = reg8SizeMask32
18 | reg16SizeMask64 = reg8SizeMask64
19 |
20 | // reg32SizeMaskX - shift value is 32 bits, shifted is X
21 | reg32SizeMask8 = reg8SizeMask8
22 | reg32SizeMask16 = reg8SizeMask16
23 | reg32SizeMask32 = reg8SizeMask32
24 | reg32SizeMask64 = reg8SizeMask64
25 |
26 | // reg64SizeMaskX - shift value is 64 bits, shifted is X
27 | reg64SizeMask8 = reg8SizeMask8
28 | reg64SizeMask16 = reg8SizeMask16
29 | reg64SizeMask32 = reg8SizeMask32
30 | reg64SizeMask64 = reg8SizeMask64
31 |
32 | // regSizeMaskUintX - shift value is uint, shifted is X
33 | regSizeMaskUint8 = reg8SizeMask8
34 | regSizeMaskUint16 = reg8SizeMask16
35 | regSizeMaskUint32 = reg8SizeMask32
36 | regSizeMaskUint64 = reg8SizeMask64
37 | )
38 |
--------------------------------------------------------------------------------
/lib/turso/vendor/github.com/klauspost/compress/flate/regmask_other.go:
--------------------------------------------------------------------------------
1 | //go:build !amd64
2 | // +build !amd64
3 |
4 | package flate
5 |
6 | const (
7 | // Masks for shifts with register sizes of the shift value.
8 | // This can be used to work around the x86 design of shifting by mod register size.
9 | // It can be used when a variable shift is always smaller than the register size.
10 |
11 | // reg8SizeMaskX - shift value is 8 bits, shifted is X
12 | reg8SizeMask8 = 0xff
13 | reg8SizeMask16 = 0xff
14 | reg8SizeMask32 = 0xff
15 | reg8SizeMask64 = 0xff
16 |
17 | // reg16SizeMaskX - shift value is 16 bits, shifted is X
18 | reg16SizeMask8 = 0xffff
19 | reg16SizeMask16 = 0xffff
20 | reg16SizeMask32 = 0xffff
21 | reg16SizeMask64 = 0xffff
22 |
23 | // reg32SizeMaskX - shift value is 32 bits, shifted is X
24 | reg32SizeMask8 = 0xffffffff
25 | reg32SizeMask16 = 0xffffffff
26 | reg32SizeMask32 = 0xffffffff
27 | reg32SizeMask64 = 0xffffffff
28 |
29 | // reg64SizeMaskX - shift value is 64 bits, shifted is X
30 | reg64SizeMask8 = 0xffffffffffffffff
31 | reg64SizeMask16 = 0xffffffffffffffff
32 | reg64SizeMask32 = 0xffffffffffffffff
33 | reg64SizeMask64 = 0xffffffffffffffff
34 |
35 | // regSizeMaskUintX - shift value is uint, shifted is X
36 | regSizeMaskUint8 = ^uint(0)
37 | regSizeMaskUint16 = ^uint(0)
38 | regSizeMaskUint32 = ^uint(0)
39 | regSizeMaskUint64 = ^uint(0)
40 | )
41 |
--------------------------------------------------------------------------------
/lib/turso/vendor/github.com/libsql/sqlite-antlr4-parser/sqliteparserutils/utils.go:
--------------------------------------------------------------------------------
1 | package sqliteparserutils
2 |
3 | import (
4 | "github.com/antlr/antlr4/runtime/Go/antlr/v4"
5 | "github.com/libsql/sqlite-antlr4-parser/sqliteparser"
6 | )
7 |
8 | // TODO: Shell test begin transaction on shell
9 |
10 | type SplitStatementExtraInfo struct {
11 | IncompleteCreateTriggerStatement bool
12 | IncompleteMultilineComment bool
13 | LastTokenType int
14 | }
15 |
16 | func SplitStatement(statement string) (stmts []string, extraInfo SplitStatementExtraInfo) {
17 | tokenStream := createTokenStream(statement)
18 |
19 | stmtIntervals := make([]*antlr.Interval, 0)
20 | currentIntervalStart := -1
21 | insideCreateTriggerStmt := false
22 | insideMultilineComment := false
23 |
24 | var previousToken antlr.Token
25 | var currentToken antlr.Token
26 | for currentToken = tokenStream.LT(1); currentToken.GetTokenType() != antlr.TokenEOF; currentToken = tokenStream.LT(1) {
27 | // We break loop here because we're sure multiline comment didn't finished, otherwise lexer would have just ignored
28 | // it
29 | if atIncompleteMultilineCommentStart(tokenStream) {
30 | insideMultilineComment = true
31 | break
32 | }
33 |
34 | if currentIntervalStart == -1 {
35 | if currentToken.GetTokenType() == sqliteparser.SQLiteLexerSCOL {
36 | previousToken = currentToken
37 | tokenStream.Consume()
38 | continue
39 | }
40 | currentIntervalStart = currentToken.GetTokenIndex()
41 |
42 | if atCreateTriggerStart(tokenStream) {
43 | insideCreateTriggerStmt = true
44 | previousToken = currentToken
45 | tokenStream.Consume()
46 | continue
47 | }
48 |
49 | }
50 |
51 | if insideCreateTriggerStmt {
52 | if currentToken.GetTokenType() == sqliteparser.SQLiteLexerEND_ {
53 | insideCreateTriggerStmt = false
54 | }
55 | } else if currentToken.GetTokenType() == sqliteparser.SQLiteLexerSCOL {
56 | stmtIntervals = append(stmtIntervals, antlr.NewInterval(currentIntervalStart, previousToken.GetTokenIndex()))
57 | currentIntervalStart = -1
58 | }
59 |
60 | previousToken = currentToken
61 | tokenStream.Consume()
62 | }
63 |
64 | if currentIntervalStart != -1 && previousToken != nil {
65 | stmtIntervals = append(stmtIntervals, antlr.NewInterval(currentIntervalStart, previousToken.GetTokenIndex()))
66 | }
67 |
68 | stmts = make([]string, 0)
69 | for _, stmtInterval := range stmtIntervals {
70 | stmts = append(stmts, tokenStream.GetTextFromInterval(stmtInterval))
71 | }
72 |
73 | lastTokenType := antlr.TokenInvalidType
74 | if previousToken != nil {
75 | lastTokenType = previousToken.GetTokenType()
76 | }
77 | return stmts, SplitStatementExtraInfo{IncompleteCreateTriggerStatement: insideCreateTriggerStmt, IncompleteMultilineComment: insideMultilineComment, LastTokenType: lastTokenType}
78 | }
79 |
80 | func atCreateTriggerStart(tokenStream antlr.TokenStream) bool {
81 | if tokenStream.LT(1).GetTokenType() != sqliteparser.SQLiteLexerCREATE_ {
82 | return false
83 | }
84 |
85 | if tokenStream.LT(2).GetTokenType() == sqliteparser.SQLiteLexerTRIGGER_ {
86 | return true
87 | }
88 |
89 | if tokenStream.LT(2).GetTokenType() == sqliteparser.SQLiteLexerTEMP_ || tokenStream.LT(2).GetTokenType() == sqliteparser.SQLiteLexerTEMPORARY_ &&
90 | tokenStream.LT(3).GetTokenType() == sqliteparser.SQLiteLexerTRIGGER_ {
91 | return true
92 | }
93 |
94 | return false
95 |
96 | }
97 |
98 | // Note: Only starts for incomplete multiline comments will be detected cause lexer automatically ignores complete
99 | // multiline comments
100 | func atIncompleteMultilineCommentStart(tokenStream antlr.TokenStream) bool {
101 | if tokenStream.LT(1).GetTokenType() != sqliteparser.SQLiteLexerDIV {
102 | return false
103 | }
104 |
105 | if tokenStream.LT(2).GetTokenType() == sqliteparser.SQLiteLexerSTAR {
106 | return true
107 | }
108 |
109 | return false
110 | }
111 |
112 | func createTokenStream(statement string) *antlr.CommonTokenStream {
113 | statementStream := antlr.NewInputStream(statement)
114 |
115 | lexer := sqliteparser.NewSQLiteLexer(statementStream)
116 | return antlr.NewCommonTokenStream(lexer, 0)
117 | }
118 |
--------------------------------------------------------------------------------
/lib/turso/vendor/github.com/tursodatabase/libsql-client-go/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 libSQL
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/lib/turso/vendor/github.com/tursodatabase/libsql-client-go/libsql/internal/hrana/batch.go:
--------------------------------------------------------------------------------
1 | package hrana
2 |
3 | type Batch struct {
4 | Steps []BatchStep `json:"steps"`
5 | ReplicationIndex *uint64 `json:"replication_index,omitempty"`
6 | }
7 |
8 | type BatchStep struct {
9 | Stmt Stmt `json:"stmt"`
10 | Condition *BatchCondition `json:"condition,omitempty"`
11 | }
12 |
13 | type BatchCondition struct {
14 | Type string `json:"type"`
15 | Step *int32 `json:"step,omitempty"`
16 | Cond *BatchCondition `json:"cond,omitempty"`
17 | Conds []BatchCondition `json:"conds,omitempty"`
18 | }
19 |
20 | func (b *Batch) Add(stmt Stmt) {
21 | b.Steps = append(b.Steps, BatchStep{Stmt: stmt})
22 | }
23 |
--------------------------------------------------------------------------------
/lib/turso/vendor/github.com/tursodatabase/libsql-client-go/libsql/internal/hrana/batch_result.go:
--------------------------------------------------------------------------------
1 | package hrana
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "strconv"
7 | )
8 |
9 | type BatchResult struct {
10 | StepResults []*StmtResult `json:"step_results"`
11 | StepErrors []*Error `json:"step_errors"`
12 | ReplicationIndex *uint64 `json:"replication_index"`
13 | }
14 |
15 | func (b *BatchResult) UnmarshalJSON(data []byte) error {
16 | type Alias BatchResult
17 | aux := &struct {
18 | ReplicationIndex interface{} `json:"replication_index,omitempty"`
19 | *Alias
20 | }{
21 | Alias: (*Alias)(b),
22 | }
23 |
24 | if err := json.Unmarshal(data, &aux); err != nil {
25 | return err
26 | }
27 |
28 | if aux.ReplicationIndex == nil {
29 | return nil
30 | }
31 |
32 | switch v := aux.ReplicationIndex.(type) {
33 | case float64:
34 | repIndex := uint64(v)
35 | b.ReplicationIndex = &repIndex
36 | case string:
37 | if v == "" {
38 | return nil
39 | }
40 | repIndex, err := strconv.ParseUint(v, 10, 64)
41 | if err != nil {
42 | return err
43 | }
44 | b.ReplicationIndex = &repIndex
45 | default:
46 | return fmt.Errorf("invalid type for replication index: %T", v)
47 | }
48 | return nil
49 | }
50 |
--------------------------------------------------------------------------------
/lib/turso/vendor/github.com/tursodatabase/libsql-client-go/libsql/internal/hrana/pipeline_request.go:
--------------------------------------------------------------------------------
1 | package hrana
2 |
3 | type PipelineRequest struct {
4 | Baton string `json:"baton,omitempty"`
5 | Requests []StreamRequest `json:"requests"`
6 | }
7 |
8 | func (pr *PipelineRequest) Add(request StreamRequest) {
9 | pr.Requests = append(pr.Requests, request)
10 | }
11 |
--------------------------------------------------------------------------------
/lib/turso/vendor/github.com/tursodatabase/libsql-client-go/libsql/internal/hrana/pipeline_response.go:
--------------------------------------------------------------------------------
1 | package hrana
2 |
3 | type PipelineResponse struct {
4 | Baton string `json:"baton,omitempty"`
5 | BaseUrl string `json:"base_url,omitempty"`
6 | Results []StreamResult `json:"results"`
7 | }
8 |
--------------------------------------------------------------------------------
/lib/turso/vendor/github.com/tursodatabase/libsql-client-go/libsql/internal/hrana/stmt.go:
--------------------------------------------------------------------------------
1 | package hrana
2 |
3 | import (
4 | "github.com/tursodatabase/libsql-client-go/libsql/internal/http/shared"
5 | )
6 |
7 | type Stmt struct {
8 | Sql *string `json:"sql,omitempty"`
9 | SqlId *int32 `json:"sql_id,omitempty"`
10 | Args []Value `json:"args,omitempty"`
11 | NamedArgs []NamedArg `json:"named_args,omitempty"`
12 | WantRows bool `json:"want_rows"`
13 | ReplicationIndex *uint64 `json:"replication_index,omitempty"`
14 | }
15 |
16 | type NamedArg struct {
17 | Name string `json:"name"`
18 | Value Value `json:"value"`
19 | }
20 |
21 | func (s *Stmt) AddArgs(params shared.Params) error {
22 | if len(params.Named()) > 0 {
23 | return s.AddNamedArgs(params.Named())
24 | } else {
25 | return s.AddPositionalArgs(params.Positional())
26 | }
27 | }
28 |
29 | func (s *Stmt) AddPositionalArgs(args []any) error {
30 | argValues := make([]Value, len(args))
31 | for idx := range args {
32 | var err error
33 | if argValues[idx], err = ToValue(args[idx]); err != nil {
34 | return err
35 | }
36 | }
37 | s.Args = argValues
38 | return nil
39 | }
40 |
41 | func (s *Stmt) AddNamedArgs(args map[string]any) error {
42 | argValues := make([]NamedArg, len(args))
43 | idx := 0
44 | for key, value := range args {
45 | var err error
46 | var v Value
47 | if v, err = ToValue(value); err != nil {
48 | return err
49 | }
50 | argValues[idx] = NamedArg{
51 | Name: key,
52 | Value: v,
53 | }
54 | idx++
55 | }
56 | s.NamedArgs = argValues
57 | return nil
58 | }
59 |
--------------------------------------------------------------------------------
/lib/turso/vendor/github.com/tursodatabase/libsql-client-go/libsql/internal/hrana/stmt_result.go:
--------------------------------------------------------------------------------
1 | package hrana
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "strconv"
7 | )
8 |
9 | type Column struct {
10 | Name *string `json:"name"`
11 | Type *string `json:"decltype"`
12 | }
13 |
14 | type StmtResult struct {
15 | Cols []Column `json:"cols"`
16 | Rows [][]Value `json:"rows"`
17 | AffectedRowCount int32 `json:"affected_row_count"`
18 | LastInsertRowId *string `json:"last_insert_rowid"`
19 | ReplicationIndex *uint64 `json:"replication_index"`
20 | }
21 |
22 | func (r *StmtResult) GetLastInsertRowId() int64 {
23 | if r.LastInsertRowId != nil {
24 | if integer, err := strconv.ParseInt(*r.LastInsertRowId, 10, 64); err == nil {
25 | return integer
26 | }
27 | }
28 | return 0
29 | }
30 |
31 | func (r *StmtResult) UnmarshalJSON(data []byte) error {
32 | type Alias StmtResult
33 | aux := &struct {
34 | ReplicationIndex interface{} `json:"replication_index,omitempty"`
35 | *Alias
36 | }{
37 | Alias: (*Alias)(r),
38 | }
39 |
40 | if err := json.Unmarshal(data, &aux); err != nil {
41 | return err
42 | }
43 |
44 | if aux.ReplicationIndex == nil {
45 | return nil
46 | }
47 |
48 | switch v := aux.ReplicationIndex.(type) {
49 | case float64:
50 | repIndex := uint64(v)
51 | r.ReplicationIndex = &repIndex
52 | case string:
53 | if v == "" {
54 | return nil
55 | }
56 | repIndex, err := strconv.ParseUint(v, 10, 64)
57 | if err != nil {
58 | return err
59 | }
60 | r.ReplicationIndex = &repIndex
61 | default:
62 | return fmt.Errorf("invalid type for replication index: %T", v)
63 | }
64 | return nil
65 | }
66 |
--------------------------------------------------------------------------------
/lib/turso/vendor/github.com/tursodatabase/libsql-client-go/libsql/internal/hrana/stream_request.go:
--------------------------------------------------------------------------------
1 | package hrana
2 |
3 | import (
4 | "github.com/tursodatabase/libsql-client-go/libsql/internal/http/shared"
5 | )
6 |
7 | type StreamRequest struct {
8 | Type string `json:"type"`
9 | Stmt *Stmt `json:"stmt,omitempty"`
10 | Batch *Batch `json:"batch,omitempty"`
11 | Sql *string `json:"sql,omitempty"`
12 | SqlId *int32 `json:"sql_id,omitempty"`
13 | }
14 |
15 | func CloseStream() StreamRequest {
16 | return StreamRequest{Type: "close"}
17 | }
18 |
19 | func ExecuteStream(sql string, params shared.Params, wantRows bool) (*StreamRequest, error) {
20 | stmt := &Stmt{
21 | Sql: &sql,
22 | WantRows: wantRows,
23 | }
24 | if err := stmt.AddArgs(params); err != nil {
25 | return nil, err
26 | }
27 | return &StreamRequest{Type: "execute", Stmt: stmt}, nil
28 | }
29 |
30 | func ExecuteStoredStream(sqlId int32, params shared.Params, wantRows bool) (*StreamRequest, error) {
31 | stmt := &Stmt{
32 | SqlId: &sqlId,
33 | WantRows: wantRows,
34 | }
35 | if err := stmt.AddArgs(params); err != nil {
36 | return nil, err
37 | }
38 | return &StreamRequest{Type: "execute", Stmt: stmt}, nil
39 | }
40 |
41 | func BatchStream(sqls []string, params []shared.Params, wantRows bool) (*StreamRequest, error) {
42 | batch := &Batch{}
43 | for idx, sql := range sqls {
44 | s := sql
45 | stmt := &Stmt{
46 | Sql: &s,
47 | WantRows: wantRows,
48 | }
49 | if err := stmt.AddArgs(params[idx]); err != nil {
50 | return nil, err
51 | }
52 | batch.Add(*stmt)
53 | }
54 | return &StreamRequest{Type: "batch", Batch: batch}, nil
55 | }
56 |
57 | func StoreSqlStream(sql string, sqlId int32) StreamRequest {
58 | return StreamRequest{Type: "store_sql", Sql: &sql, SqlId: &sqlId}
59 | }
60 |
61 | func CloseStoredSqlStream(sqlId int32) StreamRequest {
62 | return StreamRequest{Type: "close_sql", SqlId: &sqlId}
63 | }
64 |
--------------------------------------------------------------------------------
/lib/turso/vendor/github.com/tursodatabase/libsql-client-go/libsql/internal/hrana/stream_result.go:
--------------------------------------------------------------------------------
1 | package hrana
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "fmt"
7 | )
8 |
9 | type StreamResult struct {
10 | Type string `json:"type"`
11 | Response *StreamResponse `json:"response,omitempty"`
12 | Error *Error `json:"error,omitempty"`
13 | }
14 |
15 | type StreamResponse struct {
16 | Type string `json:"type"`
17 | Result json.RawMessage `json:"result,omitempty"`
18 | }
19 |
20 | func (r *StreamResponse) ExecuteResult() (*StmtResult, error) {
21 | if r.Type != "execute" {
22 | return nil, fmt.Errorf("invalid response type: %s", r.Type)
23 | }
24 |
25 | var res StmtResult
26 | if err := json.Unmarshal(r.Result, &res); err != nil {
27 | return nil, err
28 | }
29 | return &res, nil
30 | }
31 |
32 | func (r *StreamResponse) BatchResult() (*BatchResult, error) {
33 | if r.Type != "batch" {
34 | return nil, fmt.Errorf("invalid response type: %s", r.Type)
35 | }
36 |
37 | var res BatchResult
38 | if err := json.Unmarshal(r.Result, &res); err != nil {
39 | return nil, err
40 | }
41 | for _, e := range res.StepErrors {
42 | if e != nil {
43 | return nil, errors.New(e.Message)
44 | }
45 | }
46 | return &res, nil
47 | }
48 |
49 | type Error struct {
50 | Message string `json:"message"`
51 | Code *string `json:"code,omitempty"`
52 | }
53 |
--------------------------------------------------------------------------------
/lib/turso/vendor/github.com/tursodatabase/libsql-client-go/libsql/internal/hrana/value.go:
--------------------------------------------------------------------------------
1 | package hrana
2 |
3 | import (
4 | "encoding/base64"
5 | "fmt"
6 | "strconv"
7 | "strings"
8 | "time"
9 | )
10 |
11 | type Value struct {
12 | Type string `json:"type"`
13 | Value any `json:"value,omitempty"`
14 | Base64 string `json:"base64,omitempty"`
15 | }
16 |
17 | func (v Value) ToValue(columnType *string) any {
18 | if v.Type == "blob" {
19 | bytes, err := base64.StdEncoding.WithPadding(base64.NoPadding).DecodeString(v.Base64)
20 | if err != nil {
21 | return nil
22 | }
23 | return bytes
24 | } else if v.Type == "integer" {
25 | integer, err := strconv.ParseInt(v.Value.(string), 10, 64)
26 | if err != nil {
27 | return nil
28 | }
29 | return integer
30 | } else if columnType != nil {
31 | if (strings.ToLower(*columnType) == "timestamp" || strings.ToLower(*columnType) == "datetime") && v.Type == "text" {
32 | for _, format := range []string{
33 | "2006-01-02 15:04:05.999999999-07:00",
34 | "2006-01-02T15:04:05.999999999-07:00",
35 | "2006-01-02 15:04:05.999999999",
36 | "2006-01-02T15:04:05.999999999",
37 | "2006-01-02 15:04:05",
38 | "2006-01-02T15:04:05",
39 | "2006-01-02 15:04",
40 | "2006-01-02T15:04",
41 | "2006-01-02",
42 | } {
43 | if t, err := time.ParseInLocation(format, v.Value.(string), time.UTC); err == nil {
44 | return t
45 | }
46 | }
47 | }
48 | }
49 |
50 | return v.Value
51 | }
52 |
53 | func ToValue(v any) (Value, error) {
54 | var res Value
55 | if v == nil {
56 | res.Type = "null"
57 | } else if integer, ok := v.(int64); ok {
58 | res.Type = "integer"
59 | res.Value = strconv.FormatInt(integer, 10)
60 | } else if integer, ok := v.(int); ok {
61 | res.Type = "integer"
62 | res.Value = strconv.FormatInt(int64(integer), 10)
63 | } else if text, ok := v.(string); ok {
64 | res.Type = "text"
65 | res.Value = text
66 | } else if blob, ok := v.([]byte); ok {
67 | res.Type = "blob"
68 | res.Base64 = base64.StdEncoding.WithPadding(base64.NoPadding).EncodeToString(blob)
69 | } else if float, ok := v.(float64); ok {
70 | res.Type = "float"
71 | res.Value = float
72 | } else if t, ok := v.(time.Time); ok {
73 | res.Type = "text"
74 | res.Value = t.Format("2006-01-02 15:04:05.999999999-07:00")
75 | } else if t, ok := v.(bool); ok {
76 | res.Type = "integer"
77 | res.Value = "0"
78 | if t {
79 | res.Value = "1"
80 | }
81 | } else {
82 | return res, fmt.Errorf("unsupported value type: %s", v)
83 | }
84 | return res, nil
85 | }
86 |
--------------------------------------------------------------------------------
/lib/turso/vendor/github.com/tursodatabase/libsql-client-go/libsql/internal/http/driver.go:
--------------------------------------------------------------------------------
1 | package http
2 |
3 | import (
4 | "database/sql/driver"
5 |
6 | "github.com/tursodatabase/libsql-client-go/libsql/internal/http/hranaV2"
7 | )
8 |
9 | func Connect(url, jwt, host string) driver.Conn {
10 | return hranaV2.Connect(url, jwt, host)
11 | }
12 |
--------------------------------------------------------------------------------
/lib/turso/vendor/github.com/tursodatabase/libsql-client-go/libsql/internal/http/shared/result.go:
--------------------------------------------------------------------------------
1 | package shared
2 |
3 | type result struct {
4 | id int64
5 | changes int64
6 | }
7 |
8 | func NewResult(id, changes int64) *result {
9 | return &result{id: id, changes: changes}
10 | }
11 |
12 | func (r *result) LastInsertId() (int64, error) {
13 | return r.id, nil
14 | }
15 |
16 | func (r *result) RowsAffected() (int64, error) {
17 | return r.changes, nil
18 | }
19 |
--------------------------------------------------------------------------------
/lib/turso/vendor/github.com/tursodatabase/libsql-client-go/libsql/internal/http/shared/rows.go:
--------------------------------------------------------------------------------
1 | package shared
2 |
3 | import (
4 | "database/sql/driver"
5 | "fmt"
6 | "io"
7 | )
8 |
9 | type rowsProvider interface {
10 | SetsCount() int
11 | RowsCount(setIdx int) int
12 | Columns(setIdx int) []string
13 | FieldValue(setIdx, rowIdx int, columnIdx int) driver.Value
14 | Error(setIdx int) string
15 | HasResult(setIdx int) bool
16 | }
17 |
18 | func NewRows(result rowsProvider) driver.Rows {
19 | return &rows{result: result}
20 | }
21 |
22 | type rows struct {
23 | result rowsProvider
24 | currentResultSetIndex int
25 | currentRowIdx int
26 | }
27 |
28 | func (r *rows) Columns() []string {
29 | return r.result.Columns(r.currentResultSetIndex)
30 | }
31 |
32 | func (r *rows) Close() error {
33 | return nil
34 | }
35 |
36 | func (r *rows) Next(dest []driver.Value) error {
37 | if r.currentRowIdx == r.result.RowsCount(r.currentResultSetIndex) {
38 | return io.EOF
39 | }
40 | count := len(r.result.Columns(r.currentResultSetIndex))
41 | for idx := 0; idx < count; idx++ {
42 | dest[idx] = r.result.FieldValue(r.currentResultSetIndex, r.currentRowIdx, idx)
43 | }
44 | r.currentRowIdx++
45 | return nil
46 | }
47 |
48 | func (r *rows) HasNextResultSet() bool {
49 | return r.currentResultSetIndex < r.result.SetsCount()-1
50 | }
51 |
52 | func (r *rows) NextResultSet() error {
53 | if !r.HasNextResultSet() {
54 | return io.EOF
55 | }
56 |
57 | r.currentResultSetIndex++
58 | r.currentRowIdx = 0
59 |
60 | errStr := r.result.Error(r.currentResultSetIndex)
61 | if errStr != "" {
62 | return fmt.Errorf("failed to execute statement\n%s", errStr)
63 | }
64 | if !r.result.HasResult(r.currentResultSetIndex) {
65 | return fmt.Errorf("no results for statement")
66 | }
67 |
68 | return nil
69 | }
70 |
--------------------------------------------------------------------------------
/lib/turso/vendor/github.com/tursodatabase/libsql-client-go/libsql/internal/ws/driver.go:
--------------------------------------------------------------------------------
1 | package ws
2 |
3 | import (
4 | "context"
5 | "database/sql/driver"
6 | "io"
7 | "sort"
8 | )
9 |
10 | type result struct {
11 | id int64
12 | changes int64
13 | }
14 |
15 | func (r *result) LastInsertId() (int64, error) {
16 | return r.id, nil
17 | }
18 |
19 | func (r *result) RowsAffected() (int64, error) {
20 | return r.changes, nil
21 | }
22 |
23 | type rows struct {
24 | res *execResponse
25 | currentRowIdx int
26 | }
27 |
28 | func (r *rows) Columns() []string {
29 | return r.res.columns()
30 | }
31 |
32 | func (r *rows) Close() error {
33 | return nil
34 | }
35 |
36 | func (r *rows) Next(dest []driver.Value) error {
37 | if r.currentRowIdx == r.res.rowsCount() {
38 | return io.EOF
39 | }
40 | count := r.res.rowLen(r.currentRowIdx)
41 | for idx := 0; idx < count; idx++ {
42 | v, err := r.res.value(r.currentRowIdx, idx)
43 | if err != nil {
44 | return err
45 | }
46 | dest[idx] = v
47 | }
48 | r.currentRowIdx++
49 | return nil
50 | }
51 |
52 | type conn struct {
53 | ws *websocketConn
54 | }
55 |
56 | func Connect(url string, jwt string) (*conn, error) {
57 | c, err := connect(url, jwt)
58 | if err != nil {
59 | return nil, err
60 | }
61 | return &conn{c}, nil
62 | }
63 |
64 | type stmt struct {
65 | c *conn
66 | query string
67 | }
68 |
69 | func (s stmt) Close() error {
70 | return nil
71 | }
72 |
73 | func (s stmt) NumInput() int {
74 | return -1
75 | }
76 |
77 | func convertToNamed(args []driver.Value) []driver.NamedValue {
78 | if len(args) == 0 {
79 | return nil
80 | }
81 | result := []driver.NamedValue{}
82 | for idx := range args {
83 | result = append(result, driver.NamedValue{Ordinal: idx, Value: args[idx]})
84 | }
85 | return result
86 | }
87 |
88 | func (s stmt) Exec(args []driver.Value) (driver.Result, error) {
89 | return s.ExecContext(context.Background(), convertToNamed(args))
90 | }
91 |
92 | func (s stmt) Query(args []driver.Value) (driver.Rows, error) {
93 | return s.QueryContext(context.Background(), convertToNamed(args))
94 | }
95 |
96 | func (s stmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) {
97 | return s.c.ExecContext(ctx, s.query, args)
98 | }
99 |
100 | func (s stmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) {
101 | return s.c.QueryContext(ctx, s.query, args)
102 | }
103 |
104 | func (c *conn) Ping() error {
105 | return c.PingContext(context.Background())
106 | }
107 |
108 | func (c *conn) PingContext(ctx context.Context) error {
109 | _, err := c.ws.exec(ctx, "SELECT 1", params{}, false)
110 | return err
111 | }
112 |
113 | func (c *conn) Prepare(query string) (driver.Stmt, error) {
114 | return c.PrepareContext(context.Background(), query)
115 | }
116 |
117 | func (c *conn) PrepareContext(_ context.Context, query string) (driver.Stmt, error) {
118 | return stmt{c, query}, nil
119 | }
120 |
121 | func (c *conn) Close() error {
122 | return c.ws.Close()
123 | }
124 |
125 | type tx struct {
126 | c *conn
127 | }
128 |
129 | func (t tx) Commit() error {
130 | _, err := t.c.ExecContext(context.Background(), "COMMIT", nil)
131 | if err != nil {
132 | return err
133 | }
134 | return nil
135 | }
136 |
137 | func (t tx) Rollback() error {
138 | _, err := t.c.ExecContext(context.Background(), "ROLLBACK", nil)
139 | if err != nil {
140 | return err
141 | }
142 | return nil
143 | }
144 |
145 | func (c *conn) Begin() (driver.Tx, error) {
146 | return c.BeginTx(context.Background(), driver.TxOptions{})
147 | }
148 |
149 | func (c *conn) BeginTx(ctx context.Context, _ driver.TxOptions) (driver.Tx, error) {
150 | _, err := c.ExecContext(ctx, "BEGIN", nil)
151 | if err != nil {
152 | return tx{nil}, err
153 | }
154 | return tx{c}, nil
155 | }
156 |
157 | func convertArgs(args []driver.NamedValue) params {
158 | if len(args) == 0 {
159 | return params{}
160 | }
161 | positionalArgs := [](*driver.NamedValue){}
162 | namedArgs := []namedParam{}
163 | for idx := range args {
164 | if len(args[idx].Name) > 0 {
165 | namedArgs = append(namedArgs, namedParam{args[idx].Name, args[idx].Value})
166 | } else {
167 | positionalArgs = append(positionalArgs, &args[idx])
168 | }
169 | }
170 | sort.Slice(positionalArgs, func(i, j int) bool {
171 | return positionalArgs[i].Ordinal < positionalArgs[j].Ordinal
172 | })
173 | posArgs := [](any){}
174 | for idx := range positionalArgs {
175 | posArgs = append(posArgs, positionalArgs[idx].Value)
176 | }
177 | return params{PositinalArgs: posArgs, NamedArgs: namedArgs}
178 | }
179 |
180 | func (c *conn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) {
181 | res, err := c.ws.exec(ctx, query, convertArgs(args), false)
182 | if err != nil {
183 | return nil, err
184 | }
185 | return &result{res.lastInsertId(), res.affectedRowCount()}, nil
186 | }
187 |
188 | func (c *conn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {
189 | res, err := c.ws.exec(ctx, query, convertArgs(args), true)
190 | if err != nil {
191 | return nil, err
192 | }
193 | return &rows{res, 0}, nil
194 | }
195 |
--------------------------------------------------------------------------------
/lib/turso/vendor/golang.org/x/exp/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2009 The Go Authors. All rights reserved.
2 |
3 | Redistribution and use in source and binary forms, with or without
4 | modification, are permitted provided that the following conditions are
5 | met:
6 |
7 | * Redistributions of source code must retain the above copyright
8 | notice, this list of conditions and the following disclaimer.
9 | * Redistributions in binary form must reproduce the above
10 | copyright notice, this list of conditions and the following disclaimer
11 | in the documentation and/or other materials provided with the
12 | distribution.
13 | * Neither the name of Google Inc. nor the names of its
14 | contributors may be used to endorse or promote products derived from
15 | this software without specific prior written permission.
16 |
17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
--------------------------------------------------------------------------------
/lib/turso/vendor/golang.org/x/exp/PATENTS:
--------------------------------------------------------------------------------
1 | Additional IP Rights Grant (Patents)
2 |
3 | "This implementation" means the copyrightable works distributed by
4 | Google as part of the Go project.
5 |
6 | Google hereby grants to You a perpetual, worldwide, non-exclusive,
7 | no-charge, royalty-free, irrevocable (except as stated in this section)
8 | patent license to make, have made, use, offer to sell, sell, import,
9 | transfer and otherwise run, modify and propagate the contents of this
10 | implementation of Go, where such license applies only to those patent
11 | claims, both currently owned or controlled by Google and acquired in
12 | the future, licensable by Google that are necessarily infringed by this
13 | implementation of Go. This grant does not include claims that would be
14 | infringed only as a consequence of further modification of this
15 | implementation. If you or your agent or exclusive licensee institute or
16 | order or agree to the institution of patent litigation against any
17 | entity (including a cross-claim or counterclaim in a lawsuit) alleging
18 | that this implementation of Go or any code incorporated within this
19 | implementation of Go constitutes direct or contributory patent
20 | infringement, or inducement of patent infringement, then any patent
21 | rights granted to you under this License for this implementation of Go
22 | shall terminate as of the date such litigation is filed.
23 |
--------------------------------------------------------------------------------
/lib/turso/vendor/golang.org/x/exp/constraints/constraints.go:
--------------------------------------------------------------------------------
1 | // Copyright 2021 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | // Package constraints defines a set of useful constraints to be used
6 | // with type parameters.
7 | package constraints
8 |
9 | // Signed is a constraint that permits any signed integer type.
10 | // If future releases of Go add new predeclared signed integer types,
11 | // this constraint will be modified to include them.
12 | type Signed interface {
13 | ~int | ~int8 | ~int16 | ~int32 | ~int64
14 | }
15 |
16 | // Unsigned is a constraint that permits any unsigned integer type.
17 | // If future releases of Go add new predeclared unsigned integer types,
18 | // this constraint will be modified to include them.
19 | type Unsigned interface {
20 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
21 | }
22 |
23 | // Integer is a constraint that permits any integer type.
24 | // If future releases of Go add new predeclared integer types,
25 | // this constraint will be modified to include them.
26 | type Integer interface {
27 | Signed | Unsigned
28 | }
29 |
30 | // Float is a constraint that permits any floating-point type.
31 | // If future releases of Go add new predeclared floating-point types,
32 | // this constraint will be modified to include them.
33 | type Float interface {
34 | ~float32 | ~float64
35 | }
36 |
37 | // Complex is a constraint that permits any complex numeric type.
38 | // If future releases of Go add new predeclared complex numeric types,
39 | // this constraint will be modified to include them.
40 | type Complex interface {
41 | ~complex64 | ~complex128
42 | }
43 |
44 | // Ordered is a constraint that permits any ordered type: any type
45 | // that supports the operators < <= >= >.
46 | // If future releases of Go add new ordered types,
47 | // this constraint will be modified to include them.
48 | type Ordered interface {
49 | Integer | Float | ~string
50 | }
51 |
--------------------------------------------------------------------------------
/lib/turso/vendor/golang.org/x/exp/slices/sort.go:
--------------------------------------------------------------------------------
1 | // Copyright 2022 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | package slices
6 |
7 | import (
8 | "math/bits"
9 |
10 | "golang.org/x/exp/constraints"
11 | )
12 |
13 | // Sort sorts a slice of any ordered type in ascending order.
14 | // Sort may fail to sort correctly when sorting slices of floating-point
15 | // numbers containing Not-a-number (NaN) values.
16 | // Use slices.SortFunc(x, func(a, b float64) bool {return a < b || (math.IsNaN(a) && !math.IsNaN(b))})
17 | // instead if the input may contain NaNs.
18 | func Sort[E constraints.Ordered](x []E) {
19 | n := len(x)
20 | pdqsortOrdered(x, 0, n, bits.Len(uint(n)))
21 | }
22 |
23 | // SortFunc sorts the slice x in ascending order as determined by the less function.
24 | // This sort is not guaranteed to be stable.
25 | //
26 | // SortFunc requires that less is a strict weak ordering.
27 | // See https://en.wikipedia.org/wiki/Weak_ordering#Strict_weak_orderings.
28 | func SortFunc[E any](x []E, less func(a, b E) bool) {
29 | n := len(x)
30 | pdqsortLessFunc(x, 0, n, bits.Len(uint(n)), less)
31 | }
32 |
33 | // SortStable sorts the slice x while keeping the original order of equal
34 | // elements, using less to compare elements.
35 | func SortStableFunc[E any](x []E, less func(a, b E) bool) {
36 | stableLessFunc(x, len(x), less)
37 | }
38 |
39 | // IsSorted reports whether x is sorted in ascending order.
40 | func IsSorted[E constraints.Ordered](x []E) bool {
41 | for i := len(x) - 1; i > 0; i-- {
42 | if x[i] < x[i-1] {
43 | return false
44 | }
45 | }
46 | return true
47 | }
48 |
49 | // IsSortedFunc reports whether x is sorted in ascending order, with less as the
50 | // comparison function.
51 | func IsSortedFunc[E any](x []E, less func(a, b E) bool) bool {
52 | for i := len(x) - 1; i > 0; i-- {
53 | if less(x[i], x[i-1]) {
54 | return false
55 | }
56 | }
57 | return true
58 | }
59 |
60 | // BinarySearch searches for target in a sorted slice and returns the position
61 | // where target is found, or the position where target would appear in the
62 | // sort order; it also returns a bool saying whether the target is really found
63 | // in the slice. The slice must be sorted in increasing order.
64 | func BinarySearch[E constraints.Ordered](x []E, target E) (int, bool) {
65 | // search returns the leftmost position where f returns true, or len(x) if f
66 | // returns false for all x. This is the insertion position for target in x,
67 | // and could point to an element that's either == target or not.
68 | pos := search(len(x), func(i int) bool { return x[i] >= target })
69 | if pos >= len(x) || x[pos] != target {
70 | return pos, false
71 | } else {
72 | return pos, true
73 | }
74 | }
75 |
76 | // BinarySearchFunc works like BinarySearch, but uses a custom comparison
77 | // function. The slice must be sorted in increasing order, where "increasing" is
78 | // defined by cmp. cmp(a, b) is expected to return an integer comparing the two
79 | // parameters: 0 if a == b, a negative number if a < b and a positive number if
80 | // a > b.
81 | func BinarySearchFunc[E any](x []E, target E, cmp func(E, E) int) (int, bool) {
82 | pos := search(len(x), func(i int) bool { return cmp(x[i], target) >= 0 })
83 | if pos >= len(x) || cmp(x[pos], target) != 0 {
84 | return pos, false
85 | } else {
86 | return pos, true
87 | }
88 | }
89 |
90 | func search(n int, f func(int) bool) int {
91 | // Define f(-1) == false and f(n) == true.
92 | // Invariant: f(i-1) == false, f(j) == true.
93 | i, j := 0, n
94 | for i < j {
95 | h := int(uint(i+j) >> 1) // avoid overflow when computing h
96 | // i ≤ h < j
97 | if !f(h) {
98 | i = h + 1 // preserves f(i-1) == false
99 | } else {
100 | j = h // preserves f(j) == true
101 | }
102 | }
103 | // i == j, f(i-1) == false, and f(j) (= f(i)) == true => answer is i.
104 | return i
105 | }
106 |
107 | type sortedHint int // hint for pdqsort when choosing the pivot
108 |
109 | const (
110 | unknownHint sortedHint = iota
111 | increasingHint
112 | decreasingHint
113 | )
114 |
115 | // xorshift paper: https://www.jstatsoft.org/article/view/v008i14/xorshift.pdf
116 | type xorshift uint64
117 |
118 | func (r *xorshift) Next() uint64 {
119 | *r ^= *r << 13
120 | *r ^= *r >> 17
121 | *r ^= *r << 5
122 | return uint64(*r)
123 | }
124 |
125 | func nextPowerOfTwo(length int) uint {
126 | return 1 << bits.Len(uint(length))
127 | }
128 |
--------------------------------------------------------------------------------
/lib/turso/vendor/modules.txt:
--------------------------------------------------------------------------------
1 | # github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230512164433-5d1fd1a340c9
2 | ## explicit; go 1.18
3 | github.com/antlr/antlr4/runtime/Go/antlr/v4
4 | # github.com/klauspost/compress v1.15.15
5 | ## explicit; go 1.17
6 | github.com/klauspost/compress/flate
7 | # github.com/libsql/sqlite-antlr4-parser v0.0.0-20230802215326-5cb5bb604475
8 | ## explicit; go 1.19
9 | github.com/libsql/sqlite-antlr4-parser/sqliteparser
10 | github.com/libsql/sqlite-antlr4-parser/sqliteparserutils
11 | # github.com/tursodatabase/libsql-client-go v0.0.0-20240220085343-4ae0eb9d0898
12 | ## explicit; go 1.20
13 | github.com/tursodatabase/libsql-client-go/libsql
14 | github.com/tursodatabase/libsql-client-go/libsql/internal/hrana
15 | github.com/tursodatabase/libsql-client-go/libsql/internal/http
16 | github.com/tursodatabase/libsql-client-go/libsql/internal/http/hranaV2
17 | github.com/tursodatabase/libsql-client-go/libsql/internal/http/shared
18 | github.com/tursodatabase/libsql-client-go/libsql/internal/ws
19 | # golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e
20 | ## explicit; go 1.18
21 | golang.org/x/exp/constraints
22 | golang.org/x/exp/slices
23 | # nhooyr.io/websocket v1.8.7
24 | ## explicit; go 1.13
25 | nhooyr.io/websocket
26 | nhooyr.io/websocket/internal/bpool
27 | nhooyr.io/websocket/internal/errd
28 | nhooyr.io/websocket/internal/wsjs
29 | nhooyr.io/websocket/internal/xsync
30 | nhooyr.io/websocket/wsjson
31 |
--------------------------------------------------------------------------------
/lib/turso/vendor/nhooyr.io/websocket/.gitignore:
--------------------------------------------------------------------------------
1 | websocket.test
2 |
--------------------------------------------------------------------------------
/lib/turso/vendor/nhooyr.io/websocket/LICENSE.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Anmol Sethi
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/lib/turso/vendor/nhooyr.io/websocket/README.md:
--------------------------------------------------------------------------------
1 | # websocket
2 |
3 | [](https://pkg.go.dev/nhooyr.io/websocket)
4 | [](https://nhooyrio-websocket-coverage.netlify.app)
5 |
6 | websocket is a minimal and idiomatic WebSocket library for Go.
7 |
8 | ## Install
9 |
10 | ```bash
11 | go get nhooyr.io/websocket
12 | ```
13 |
14 | ## Highlights
15 |
16 | - Minimal and idiomatic API
17 | - First class [context.Context](https://blog.golang.org/context) support
18 | - Fully passes the WebSocket [autobahn-testsuite](https://github.com/crossbario/autobahn-testsuite)
19 | - [Single dependency](https://pkg.go.dev/nhooyr.io/websocket?tab=imports)
20 | - JSON and protobuf helpers in the [wsjson](https://pkg.go.dev/nhooyr.io/websocket/wsjson) and [wspb](https://pkg.go.dev/nhooyr.io/websocket/wspb) subpackages
21 | - Zero alloc reads and writes
22 | - Concurrent writes
23 | - [Close handshake](https://pkg.go.dev/nhooyr.io/websocket#Conn.Close)
24 | - [net.Conn](https://pkg.go.dev/nhooyr.io/websocket#NetConn) wrapper
25 | - [Ping pong](https://pkg.go.dev/nhooyr.io/websocket#Conn.Ping) API
26 | - [RFC 7692](https://tools.ietf.org/html/rfc7692) permessage-deflate compression
27 | - Compile to [Wasm](https://pkg.go.dev/nhooyr.io/websocket#hdr-Wasm)
28 |
29 | ## Roadmap
30 |
31 | - [ ] HTTP/2 [#4](https://github.com/nhooyr/websocket/issues/4)
32 |
33 | ## Examples
34 |
35 | For a production quality example that demonstrates the complete API, see the
36 | [echo example](./examples/echo).
37 |
38 | For a full stack example, see the [chat example](./examples/chat).
39 |
40 | ### Server
41 |
42 | ```go
43 | http.HandlerFunc(func (w http.ResponseWriter, r *http.Request) {
44 | c, err := websocket.Accept(w, r, nil)
45 | if err != nil {
46 | // ...
47 | }
48 | defer c.Close(websocket.StatusInternalError, "the sky is falling")
49 |
50 | ctx, cancel := context.WithTimeout(r.Context(), time.Second*10)
51 | defer cancel()
52 |
53 | var v interface{}
54 | err = wsjson.Read(ctx, c, &v)
55 | if err != nil {
56 | // ...
57 | }
58 |
59 | log.Printf("received: %v", v)
60 |
61 | c.Close(websocket.StatusNormalClosure, "")
62 | })
63 | ```
64 |
65 | ### Client
66 |
67 | ```go
68 | ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
69 | defer cancel()
70 |
71 | c, _, err := websocket.Dial(ctx, "ws://localhost:8080", nil)
72 | if err != nil {
73 | // ...
74 | }
75 | defer c.Close(websocket.StatusInternalError, "the sky is falling")
76 |
77 | err = wsjson.Write(ctx, c, "hi")
78 | if err != nil {
79 | // ...
80 | }
81 |
82 | c.Close(websocket.StatusNormalClosure, "")
83 | ```
84 |
85 | ## Comparison
86 |
87 | ### gorilla/websocket
88 |
89 | Advantages of [gorilla/websocket](https://github.com/gorilla/websocket):
90 |
91 | - Mature and widely used
92 | - [Prepared writes](https://pkg.go.dev/github.com/gorilla/websocket#PreparedMessage)
93 | - Configurable [buffer sizes](https://pkg.go.dev/github.com/gorilla/websocket#hdr-Buffers)
94 |
95 | Advantages of nhooyr.io/websocket:
96 |
97 | - Minimal and idiomatic API
98 | - Compare godoc of [nhooyr.io/websocket](https://pkg.go.dev/nhooyr.io/websocket) with [gorilla/websocket](https://pkg.go.dev/github.com/gorilla/websocket) side by side.
99 | - [net.Conn](https://pkg.go.dev/nhooyr.io/websocket#NetConn) wrapper
100 | - Zero alloc reads and writes ([gorilla/websocket#535](https://github.com/gorilla/websocket/issues/535))
101 | - Full [context.Context](https://blog.golang.org/context) support
102 | - Dial uses [net/http.Client](https://golang.org/pkg/net/http/#Client)
103 | - Will enable easy HTTP/2 support in the future
104 | - Gorilla writes directly to a net.Conn and so duplicates features of net/http.Client.
105 | - Concurrent writes
106 | - Close handshake ([gorilla/websocket#448](https://github.com/gorilla/websocket/issues/448))
107 | - Idiomatic [ping pong](https://pkg.go.dev/nhooyr.io/websocket#Conn.Ping) API
108 | - Gorilla requires registering a pong callback before sending a Ping
109 | - Can target Wasm ([gorilla/websocket#432](https://github.com/gorilla/websocket/issues/432))
110 | - Transparent message buffer reuse with [wsjson](https://pkg.go.dev/nhooyr.io/websocket/wsjson) and [wspb](https://pkg.go.dev/nhooyr.io/websocket/wspb) subpackages
111 | - [1.75x](https://github.com/nhooyr/websocket/releases/tag/v1.7.4) faster WebSocket masking implementation in pure Go
112 | - Gorilla's implementation is slower and uses [unsafe](https://golang.org/pkg/unsafe/).
113 | - Full [permessage-deflate](https://tools.ietf.org/html/rfc7692) compression extension support
114 | - Gorilla only supports no context takeover mode
115 | - We use [klauspost/compress](https://github.com/klauspost/compress) for much lower memory usage ([gorilla/websocket#203](https://github.com/gorilla/websocket/issues/203))
116 | - [CloseRead](https://pkg.go.dev/nhooyr.io/websocket#Conn.CloseRead) helper ([gorilla/websocket#492](https://github.com/gorilla/websocket/issues/492))
117 | - Actively maintained ([gorilla/websocket#370](https://github.com/gorilla/websocket/issues/370))
118 |
119 | #### golang.org/x/net/websocket
120 |
121 | [golang.org/x/net/websocket](https://pkg.go.dev/golang.org/x/net/websocket) is deprecated.
122 | See [golang/go/issues/18152](https://github.com/golang/go/issues/18152).
123 |
124 | The [net.Conn](https://pkg.go.dev/nhooyr.io/websocket#NetConn) can help in transitioning
125 | to nhooyr.io/websocket.
126 |
127 | #### gobwas/ws
128 |
129 | [gobwas/ws](https://github.com/gobwas/ws) has an extremely flexible API that allows it to be used
130 | in an event driven style for performance. See the author's [blog post](https://medium.freecodecamp.org/million-websockets-and-go-cc58418460bb).
131 |
132 | However when writing idiomatic Go, nhooyr.io/websocket will be faster and easier to use.
133 |
--------------------------------------------------------------------------------
/lib/turso/vendor/nhooyr.io/websocket/accept_js.go:
--------------------------------------------------------------------------------
1 | package websocket
2 |
3 | import (
4 | "errors"
5 | "net/http"
6 | )
7 |
8 | // AcceptOptions represents Accept's options.
9 | type AcceptOptions struct {
10 | Subprotocols []string
11 | InsecureSkipVerify bool
12 | OriginPatterns []string
13 | CompressionMode CompressionMode
14 | CompressionThreshold int
15 | }
16 |
17 | // Accept is stubbed out for Wasm.
18 | func Accept(w http.ResponseWriter, r *http.Request, opts *AcceptOptions) (*Conn, error) {
19 | return nil, errors.New("unimplemented")
20 | }
21 |
--------------------------------------------------------------------------------
/lib/turso/vendor/nhooyr.io/websocket/close.go:
--------------------------------------------------------------------------------
1 | package websocket
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | )
7 |
8 | // StatusCode represents a WebSocket status code.
9 | // https://tools.ietf.org/html/rfc6455#section-7.4
10 | type StatusCode int
11 |
12 | // https://www.iana.org/assignments/websocket/websocket.xhtml#close-code-number
13 | //
14 | // These are only the status codes defined by the protocol.
15 | //
16 | // You can define custom codes in the 3000-4999 range.
17 | // The 3000-3999 range is reserved for use by libraries, frameworks and applications.
18 | // The 4000-4999 range is reserved for private use.
19 | const (
20 | StatusNormalClosure StatusCode = 1000
21 | StatusGoingAway StatusCode = 1001
22 | StatusProtocolError StatusCode = 1002
23 | StatusUnsupportedData StatusCode = 1003
24 |
25 | // 1004 is reserved and so unexported.
26 | statusReserved StatusCode = 1004
27 |
28 | // StatusNoStatusRcvd cannot be sent in a close message.
29 | // It is reserved for when a close message is received without
30 | // a status code.
31 | StatusNoStatusRcvd StatusCode = 1005
32 |
33 | // StatusAbnormalClosure is exported for use only with Wasm.
34 | // In non Wasm Go, the returned error will indicate whether the
35 | // connection was closed abnormally.
36 | StatusAbnormalClosure StatusCode = 1006
37 |
38 | StatusInvalidFramePayloadData StatusCode = 1007
39 | StatusPolicyViolation StatusCode = 1008
40 | StatusMessageTooBig StatusCode = 1009
41 | StatusMandatoryExtension StatusCode = 1010
42 | StatusInternalError StatusCode = 1011
43 | StatusServiceRestart StatusCode = 1012
44 | StatusTryAgainLater StatusCode = 1013
45 | StatusBadGateway StatusCode = 1014
46 |
47 | // StatusTLSHandshake is only exported for use with Wasm.
48 | // In non Wasm Go, the returned error will indicate whether there was
49 | // a TLS handshake failure.
50 | StatusTLSHandshake StatusCode = 1015
51 | )
52 |
53 | // CloseError is returned when the connection is closed with a status and reason.
54 | //
55 | // Use Go 1.13's errors.As to check for this error.
56 | // Also see the CloseStatus helper.
57 | type CloseError struct {
58 | Code StatusCode
59 | Reason string
60 | }
61 |
62 | func (ce CloseError) Error() string {
63 | return fmt.Sprintf("status = %v and reason = %q", ce.Code, ce.Reason)
64 | }
65 |
66 | // CloseStatus is a convenience wrapper around Go 1.13's errors.As to grab
67 | // the status code from a CloseError.
68 | //
69 | // -1 will be returned if the passed error is nil or not a CloseError.
70 | func CloseStatus(err error) StatusCode {
71 | var ce CloseError
72 | if errors.As(err, &ce) {
73 | return ce.Code
74 | }
75 | return -1
76 | }
77 |
--------------------------------------------------------------------------------
/lib/turso/vendor/nhooyr.io/websocket/close_notjs.go:
--------------------------------------------------------------------------------
1 | // +build !js
2 |
3 | package websocket
4 |
5 | import (
6 | "context"
7 | "encoding/binary"
8 | "errors"
9 | "fmt"
10 | "log"
11 | "time"
12 |
13 | "nhooyr.io/websocket/internal/errd"
14 | )
15 |
16 | // Close performs the WebSocket close handshake with the given status code and reason.
17 | //
18 | // It will write a WebSocket close frame with a timeout of 5s and then wait 5s for
19 | // the peer to send a close frame.
20 | // All data messages received from the peer during the close handshake will be discarded.
21 | //
22 | // The connection can only be closed once. Additional calls to Close
23 | // are no-ops.
24 | //
25 | // The maximum length of reason must be 125 bytes. Avoid
26 | // sending a dynamic reason.
27 | //
28 | // Close will unblock all goroutines interacting with the connection once
29 | // complete.
30 | func (c *Conn) Close(code StatusCode, reason string) error {
31 | return c.closeHandshake(code, reason)
32 | }
33 |
34 | func (c *Conn) closeHandshake(code StatusCode, reason string) (err error) {
35 | defer errd.Wrap(&err, "failed to close WebSocket")
36 |
37 | writeErr := c.writeClose(code, reason)
38 | closeHandshakeErr := c.waitCloseHandshake()
39 |
40 | if writeErr != nil {
41 | return writeErr
42 | }
43 |
44 | if CloseStatus(closeHandshakeErr) == -1 {
45 | return closeHandshakeErr
46 | }
47 |
48 | return nil
49 | }
50 |
51 | var errAlreadyWroteClose = errors.New("already wrote close")
52 |
53 | func (c *Conn) writeClose(code StatusCode, reason string) error {
54 | c.closeMu.Lock()
55 | wroteClose := c.wroteClose
56 | c.wroteClose = true
57 | c.closeMu.Unlock()
58 | if wroteClose {
59 | return errAlreadyWroteClose
60 | }
61 |
62 | ce := CloseError{
63 | Code: code,
64 | Reason: reason,
65 | }
66 |
67 | var p []byte
68 | var marshalErr error
69 | if ce.Code != StatusNoStatusRcvd {
70 | p, marshalErr = ce.bytes()
71 | if marshalErr != nil {
72 | log.Printf("websocket: %v", marshalErr)
73 | }
74 | }
75 |
76 | writeErr := c.writeControl(context.Background(), opClose, p)
77 | if CloseStatus(writeErr) != -1 {
78 | // Not a real error if it's due to a close frame being received.
79 | writeErr = nil
80 | }
81 |
82 | // We do this after in case there was an error writing the close frame.
83 | c.setCloseErr(fmt.Errorf("sent close frame: %w", ce))
84 |
85 | if marshalErr != nil {
86 | return marshalErr
87 | }
88 | return writeErr
89 | }
90 |
91 | func (c *Conn) waitCloseHandshake() error {
92 | defer c.close(nil)
93 |
94 | ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
95 | defer cancel()
96 |
97 | err := c.readMu.lock(ctx)
98 | if err != nil {
99 | return err
100 | }
101 | defer c.readMu.unlock()
102 |
103 | if c.readCloseFrameErr != nil {
104 | return c.readCloseFrameErr
105 | }
106 |
107 | for {
108 | h, err := c.readLoop(ctx)
109 | if err != nil {
110 | return err
111 | }
112 |
113 | for i := int64(0); i < h.payloadLength; i++ {
114 | _, err := c.br.ReadByte()
115 | if err != nil {
116 | return err
117 | }
118 | }
119 | }
120 | }
121 |
122 | func parseClosePayload(p []byte) (CloseError, error) {
123 | if len(p) == 0 {
124 | return CloseError{
125 | Code: StatusNoStatusRcvd,
126 | }, nil
127 | }
128 |
129 | if len(p) < 2 {
130 | return CloseError{}, fmt.Errorf("close payload %q too small, cannot even contain the 2 byte status code", p)
131 | }
132 |
133 | ce := CloseError{
134 | Code: StatusCode(binary.BigEndian.Uint16(p)),
135 | Reason: string(p[2:]),
136 | }
137 |
138 | if !validWireCloseCode(ce.Code) {
139 | return CloseError{}, fmt.Errorf("invalid status code %v", ce.Code)
140 | }
141 |
142 | return ce, nil
143 | }
144 |
145 | // See http://www.iana.org/assignments/websocket/websocket.xhtml#close-code-number
146 | // and https://tools.ietf.org/html/rfc6455#section-7.4.1
147 | func validWireCloseCode(code StatusCode) bool {
148 | switch code {
149 | case statusReserved, StatusNoStatusRcvd, StatusAbnormalClosure, StatusTLSHandshake:
150 | return false
151 | }
152 |
153 | if code >= StatusNormalClosure && code <= StatusBadGateway {
154 | return true
155 | }
156 | if code >= 3000 && code <= 4999 {
157 | return true
158 | }
159 |
160 | return false
161 | }
162 |
163 | func (ce CloseError) bytes() ([]byte, error) {
164 | p, err := ce.bytesErr()
165 | if err != nil {
166 | err = fmt.Errorf("failed to marshal close frame: %w", err)
167 | ce = CloseError{
168 | Code: StatusInternalError,
169 | }
170 | p, _ = ce.bytesErr()
171 | }
172 | return p, err
173 | }
174 |
175 | const maxCloseReason = maxControlPayload - 2
176 |
177 | func (ce CloseError) bytesErr() ([]byte, error) {
178 | if len(ce.Reason) > maxCloseReason {
179 | return nil, fmt.Errorf("reason string max is %v but got %q with length %v", maxCloseReason, ce.Reason, len(ce.Reason))
180 | }
181 |
182 | if !validWireCloseCode(ce.Code) {
183 | return nil, fmt.Errorf("status code %v cannot be set", ce.Code)
184 | }
185 |
186 | buf := make([]byte, 2+len(ce.Reason))
187 | binary.BigEndian.PutUint16(buf, uint16(ce.Code))
188 | copy(buf[2:], ce.Reason)
189 | return buf, nil
190 | }
191 |
192 | func (c *Conn) setCloseErr(err error) {
193 | c.closeMu.Lock()
194 | c.setCloseErrLocked(err)
195 | c.closeMu.Unlock()
196 | }
197 |
198 | func (c *Conn) setCloseErrLocked(err error) {
199 | if c.closeErr == nil {
200 | c.closeErr = fmt.Errorf("WebSocket closed: %w", err)
201 | }
202 | }
203 |
204 | func (c *Conn) isClosed() bool {
205 | select {
206 | case <-c.closed:
207 | return true
208 | default:
209 | return false
210 | }
211 | }
212 |
--------------------------------------------------------------------------------
/lib/turso/vendor/nhooyr.io/websocket/compress.go:
--------------------------------------------------------------------------------
1 | package websocket
2 |
3 | // CompressionMode represents the modes available to the deflate extension.
4 | // See https://tools.ietf.org/html/rfc7692
5 | //
6 | // A compatibility layer is implemented for the older deflate-frame extension used
7 | // by safari. See https://tools.ietf.org/html/draft-tyoshino-hybi-websocket-perframe-deflate-06
8 | // It will work the same in every way except that we cannot signal to the peer we
9 | // want to use no context takeover on our side, we can only signal that they should.
10 | // It is however currently disabled due to Safari bugs. See https://github.com/nhooyr/websocket/issues/218
11 | type CompressionMode int
12 |
13 | const (
14 | // CompressionNoContextTakeover grabs a new flate.Reader and flate.Writer as needed
15 | // for every message. This applies to both server and client side.
16 | //
17 | // This means less efficient compression as the sliding window from previous messages
18 | // will not be used but the memory overhead will be lower if the connections
19 | // are long lived and seldom used.
20 | //
21 | // The message will only be compressed if greater than 512 bytes.
22 | CompressionNoContextTakeover CompressionMode = iota
23 |
24 | // CompressionContextTakeover uses a flate.Reader and flate.Writer per connection.
25 | // This enables reusing the sliding window from previous messages.
26 | // As most WebSocket protocols are repetitive, this can be very efficient.
27 | // It carries an overhead of 8 kB for every connection compared to CompressionNoContextTakeover.
28 | //
29 | // If the peer negotiates NoContextTakeover on the client or server side, it will be
30 | // used instead as this is required by the RFC.
31 | CompressionContextTakeover
32 |
33 | // CompressionDisabled disables the deflate extension.
34 | //
35 | // Use this if you are using a predominantly binary protocol with very
36 | // little duplication in between messages or CPU and memory are more
37 | // important than bandwidth.
38 | CompressionDisabled
39 | )
40 |
--------------------------------------------------------------------------------
/lib/turso/vendor/nhooyr.io/websocket/compress_notjs.go:
--------------------------------------------------------------------------------
1 | // +build !js
2 |
3 | package websocket
4 |
5 | import (
6 | "io"
7 | "net/http"
8 | "sync"
9 |
10 | "github.com/klauspost/compress/flate"
11 | )
12 |
13 | func (m CompressionMode) opts() *compressionOptions {
14 | return &compressionOptions{
15 | clientNoContextTakeover: m == CompressionNoContextTakeover,
16 | serverNoContextTakeover: m == CompressionNoContextTakeover,
17 | }
18 | }
19 |
20 | type compressionOptions struct {
21 | clientNoContextTakeover bool
22 | serverNoContextTakeover bool
23 | }
24 |
25 | func (copts *compressionOptions) setHeader(h http.Header) {
26 | s := "permessage-deflate"
27 | if copts.clientNoContextTakeover {
28 | s += "; client_no_context_takeover"
29 | }
30 | if copts.serverNoContextTakeover {
31 | s += "; server_no_context_takeover"
32 | }
33 | h.Set("Sec-WebSocket-Extensions", s)
34 | }
35 |
36 | // These bytes are required to get flate.Reader to return.
37 | // They are removed when sending to avoid the overhead as
38 | // WebSocket framing tell's when the message has ended but then
39 | // we need to add them back otherwise flate.Reader keeps
40 | // trying to return more bytes.
41 | const deflateMessageTail = "\x00\x00\xff\xff"
42 |
43 | type trimLastFourBytesWriter struct {
44 | w io.Writer
45 | tail []byte
46 | }
47 |
48 | func (tw *trimLastFourBytesWriter) reset() {
49 | if tw != nil && tw.tail != nil {
50 | tw.tail = tw.tail[:0]
51 | }
52 | }
53 |
54 | func (tw *trimLastFourBytesWriter) Write(p []byte) (int, error) {
55 | if tw.tail == nil {
56 | tw.tail = make([]byte, 0, 4)
57 | }
58 |
59 | extra := len(tw.tail) + len(p) - 4
60 |
61 | if extra <= 0 {
62 | tw.tail = append(tw.tail, p...)
63 | return len(p), nil
64 | }
65 |
66 | // Now we need to write as many extra bytes as we can from the previous tail.
67 | if extra > len(tw.tail) {
68 | extra = len(tw.tail)
69 | }
70 | if extra > 0 {
71 | _, err := tw.w.Write(tw.tail[:extra])
72 | if err != nil {
73 | return 0, err
74 | }
75 |
76 | // Shift remaining bytes in tail over.
77 | n := copy(tw.tail, tw.tail[extra:])
78 | tw.tail = tw.tail[:n]
79 | }
80 |
81 | // If p is less than or equal to 4 bytes,
82 | // all of it is is part of the tail.
83 | if len(p) <= 4 {
84 | tw.tail = append(tw.tail, p...)
85 | return len(p), nil
86 | }
87 |
88 | // Otherwise, only the last 4 bytes are.
89 | tw.tail = append(tw.tail, p[len(p)-4:]...)
90 |
91 | p = p[:len(p)-4]
92 | n, err := tw.w.Write(p)
93 | return n + 4, err
94 | }
95 |
96 | var flateReaderPool sync.Pool
97 |
98 | func getFlateReader(r io.Reader, dict []byte) io.Reader {
99 | fr, ok := flateReaderPool.Get().(io.Reader)
100 | if !ok {
101 | return flate.NewReaderDict(r, dict)
102 | }
103 | fr.(flate.Resetter).Reset(r, dict)
104 | return fr
105 | }
106 |
107 | func putFlateReader(fr io.Reader) {
108 | flateReaderPool.Put(fr)
109 | }
110 |
111 | type slidingWindow struct {
112 | buf []byte
113 | }
114 |
115 | var swPoolMu sync.RWMutex
116 | var swPool = map[int]*sync.Pool{}
117 |
118 | func slidingWindowPool(n int) *sync.Pool {
119 | swPoolMu.RLock()
120 | p, ok := swPool[n]
121 | swPoolMu.RUnlock()
122 | if ok {
123 | return p
124 | }
125 |
126 | p = &sync.Pool{}
127 |
128 | swPoolMu.Lock()
129 | swPool[n] = p
130 | swPoolMu.Unlock()
131 |
132 | return p
133 | }
134 |
135 | func (sw *slidingWindow) init(n int) {
136 | if sw.buf != nil {
137 | return
138 | }
139 |
140 | if n == 0 {
141 | n = 32768
142 | }
143 |
144 | p := slidingWindowPool(n)
145 | buf, ok := p.Get().([]byte)
146 | if ok {
147 | sw.buf = buf[:0]
148 | } else {
149 | sw.buf = make([]byte, 0, n)
150 | }
151 | }
152 |
153 | func (sw *slidingWindow) close() {
154 | if sw.buf == nil {
155 | return
156 | }
157 |
158 | swPoolMu.Lock()
159 | swPool[cap(sw.buf)].Put(sw.buf)
160 | swPoolMu.Unlock()
161 | sw.buf = nil
162 | }
163 |
164 | func (sw *slidingWindow) write(p []byte) {
165 | if len(p) >= cap(sw.buf) {
166 | sw.buf = sw.buf[:cap(sw.buf)]
167 | p = p[len(p)-cap(sw.buf):]
168 | copy(sw.buf, p)
169 | return
170 | }
171 |
172 | left := cap(sw.buf) - len(sw.buf)
173 | if left < len(p) {
174 | // We need to shift spaceNeeded bytes from the end to make room for p at the end.
175 | spaceNeeded := len(p) - left
176 | copy(sw.buf, sw.buf[spaceNeeded:])
177 | sw.buf = sw.buf[:len(sw.buf)-spaceNeeded]
178 | }
179 |
180 | sw.buf = append(sw.buf, p...)
181 | }
182 |
--------------------------------------------------------------------------------
/lib/turso/vendor/nhooyr.io/websocket/conn.go:
--------------------------------------------------------------------------------
1 | package websocket
2 |
3 | // MessageType represents the type of a WebSocket message.
4 | // See https://tools.ietf.org/html/rfc6455#section-5.6
5 | type MessageType int
6 |
7 | // MessageType constants.
8 | const (
9 | // MessageText is for UTF-8 encoded text messages like JSON.
10 | MessageText MessageType = iota + 1
11 | // MessageBinary is for binary messages like protobufs.
12 | MessageBinary
13 | )
14 |
--------------------------------------------------------------------------------
/lib/turso/vendor/nhooyr.io/websocket/doc.go:
--------------------------------------------------------------------------------
1 | // +build !js
2 |
3 | // Package websocket implements the RFC 6455 WebSocket protocol.
4 | //
5 | // https://tools.ietf.org/html/rfc6455
6 | //
7 | // Use Dial to dial a WebSocket server.
8 | //
9 | // Use Accept to accept a WebSocket client.
10 | //
11 | // Conn represents the resulting WebSocket connection.
12 | //
13 | // The examples are the best way to understand how to correctly use the library.
14 | //
15 | // The wsjson and wspb subpackages contain helpers for JSON and protobuf messages.
16 | //
17 | // More documentation at https://nhooyr.io/websocket.
18 | //
19 | // Wasm
20 | //
21 | // The client side supports compiling to Wasm.
22 | // It wraps the WebSocket browser API.
23 | //
24 | // See https://developer.mozilla.org/en-US/docs/Web/API/WebSocket
25 | //
26 | // Some important caveats to be aware of:
27 | //
28 | // - Accept always errors out
29 | // - Conn.Ping is no-op
30 | // - HTTPClient, HTTPHeader and CompressionMode in DialOptions are no-op
31 | // - *http.Response from Dial is &http.Response{} with a 101 status code on success
32 | package websocket // import "nhooyr.io/websocket"
33 |
--------------------------------------------------------------------------------
/lib/turso/vendor/nhooyr.io/websocket/internal/bpool/bpool.go:
--------------------------------------------------------------------------------
1 | package bpool
2 |
3 | import (
4 | "bytes"
5 | "sync"
6 | )
7 |
8 | var bpool sync.Pool
9 |
10 | // Get returns a buffer from the pool or creates a new one if
11 | // the pool is empty.
12 | func Get() *bytes.Buffer {
13 | b := bpool.Get()
14 | if b == nil {
15 | return &bytes.Buffer{}
16 | }
17 | return b.(*bytes.Buffer)
18 | }
19 |
20 | // Put returns a buffer into the pool.
21 | func Put(b *bytes.Buffer) {
22 | b.Reset()
23 | bpool.Put(b)
24 | }
25 |
--------------------------------------------------------------------------------
/lib/turso/vendor/nhooyr.io/websocket/internal/errd/wrap.go:
--------------------------------------------------------------------------------
1 | package errd
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | // Wrap wraps err with fmt.Errorf if err is non nil.
8 | // Intended for use with defer and a named error return.
9 | // Inspired by https://github.com/golang/go/issues/32676.
10 | func Wrap(err *error, f string, v ...interface{}) {
11 | if *err != nil {
12 | *err = fmt.Errorf(f+": %w", append(v, *err)...)
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/lib/turso/vendor/nhooyr.io/websocket/internal/wsjs/wsjs_js.go:
--------------------------------------------------------------------------------
1 | // +build js
2 |
3 | // Package wsjs implements typed access to the browser javascript WebSocket API.
4 | //
5 | // https://developer.mozilla.org/en-US/docs/Web/API/WebSocket
6 | package wsjs
7 |
8 | import (
9 | "syscall/js"
10 | )
11 |
12 | func handleJSError(err *error, onErr func()) {
13 | r := recover()
14 |
15 | if jsErr, ok := r.(js.Error); ok {
16 | *err = jsErr
17 |
18 | if onErr != nil {
19 | onErr()
20 | }
21 | return
22 | }
23 |
24 | if r != nil {
25 | panic(r)
26 | }
27 | }
28 |
29 | // New is a wrapper around the javascript WebSocket constructor.
30 | func New(url string, protocols []string) (c WebSocket, err error) {
31 | defer handleJSError(&err, func() {
32 | c = WebSocket{}
33 | })
34 |
35 | jsProtocols := make([]interface{}, len(protocols))
36 | for i, p := range protocols {
37 | jsProtocols[i] = p
38 | }
39 |
40 | c = WebSocket{
41 | v: js.Global().Get("WebSocket").New(url, jsProtocols),
42 | }
43 |
44 | c.setBinaryType("arraybuffer")
45 |
46 | return c, nil
47 | }
48 |
49 | // WebSocket is a wrapper around a javascript WebSocket object.
50 | type WebSocket struct {
51 | v js.Value
52 | }
53 |
54 | func (c WebSocket) setBinaryType(typ string) {
55 | c.v.Set("binaryType", string(typ))
56 | }
57 |
58 | func (c WebSocket) addEventListener(eventType string, fn func(e js.Value)) func() {
59 | f := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
60 | fn(args[0])
61 | return nil
62 | })
63 | c.v.Call("addEventListener", eventType, f)
64 |
65 | return func() {
66 | c.v.Call("removeEventListener", eventType, f)
67 | f.Release()
68 | }
69 | }
70 |
71 | // CloseEvent is the type passed to a WebSocket close handler.
72 | type CloseEvent struct {
73 | Code uint16
74 | Reason string
75 | WasClean bool
76 | }
77 |
78 | // OnClose registers a function to be called when the WebSocket is closed.
79 | func (c WebSocket) OnClose(fn func(CloseEvent)) (remove func()) {
80 | return c.addEventListener("close", func(e js.Value) {
81 | ce := CloseEvent{
82 | Code: uint16(e.Get("code").Int()),
83 | Reason: e.Get("reason").String(),
84 | WasClean: e.Get("wasClean").Bool(),
85 | }
86 | fn(ce)
87 | })
88 | }
89 |
90 | // OnError registers a function to be called when there is an error
91 | // with the WebSocket.
92 | func (c WebSocket) OnError(fn func(e js.Value)) (remove func()) {
93 | return c.addEventListener("error", fn)
94 | }
95 |
96 | // MessageEvent is the type passed to a message handler.
97 | type MessageEvent struct {
98 | // string or []byte.
99 | Data interface{}
100 |
101 | // There are more fields to the interface but we don't use them.
102 | // See https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent
103 | }
104 |
105 | // OnMessage registers a function to be called when the WebSocket receives a message.
106 | func (c WebSocket) OnMessage(fn func(m MessageEvent)) (remove func()) {
107 | return c.addEventListener("message", func(e js.Value) {
108 | var data interface{}
109 |
110 | arrayBuffer := e.Get("data")
111 | if arrayBuffer.Type() == js.TypeString {
112 | data = arrayBuffer.String()
113 | } else {
114 | data = extractArrayBuffer(arrayBuffer)
115 | }
116 |
117 | me := MessageEvent{
118 | Data: data,
119 | }
120 | fn(me)
121 |
122 | return
123 | })
124 | }
125 |
126 | // Subprotocol returns the WebSocket subprotocol in use.
127 | func (c WebSocket) Subprotocol() string {
128 | return c.v.Get("protocol").String()
129 | }
130 |
131 | // OnOpen registers a function to be called when the WebSocket is opened.
132 | func (c WebSocket) OnOpen(fn func(e js.Value)) (remove func()) {
133 | return c.addEventListener("open", fn)
134 | }
135 |
136 | // Close closes the WebSocket with the given code and reason.
137 | func (c WebSocket) Close(code int, reason string) (err error) {
138 | defer handleJSError(&err, nil)
139 | c.v.Call("close", code, reason)
140 | return err
141 | }
142 |
143 | // SendText sends the given string as a text message
144 | // on the WebSocket.
145 | func (c WebSocket) SendText(v string) (err error) {
146 | defer handleJSError(&err, nil)
147 | c.v.Call("send", v)
148 | return err
149 | }
150 |
151 | // SendBytes sends the given message as a binary message
152 | // on the WebSocket.
153 | func (c WebSocket) SendBytes(v []byte) (err error) {
154 | defer handleJSError(&err, nil)
155 | c.v.Call("send", uint8Array(v))
156 | return err
157 | }
158 |
159 | func extractArrayBuffer(arrayBuffer js.Value) []byte {
160 | uint8Array := js.Global().Get("Uint8Array").New(arrayBuffer)
161 | dst := make([]byte, uint8Array.Length())
162 | js.CopyBytesToGo(dst, uint8Array)
163 | return dst
164 | }
165 |
166 | func uint8Array(src []byte) js.Value {
167 | uint8Array := js.Global().Get("Uint8Array").New(len(src))
168 | js.CopyBytesToJS(uint8Array, src)
169 | return uint8Array
170 | }
171 |
--------------------------------------------------------------------------------
/lib/turso/vendor/nhooyr.io/websocket/internal/xsync/go.go:
--------------------------------------------------------------------------------
1 | package xsync
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | // Go allows running a function in another goroutine
8 | // and waiting for its error.
9 | func Go(fn func() error) <-chan error {
10 | errs := make(chan error, 1)
11 | go func() {
12 | defer func() {
13 | r := recover()
14 | if r != nil {
15 | select {
16 | case errs <- fmt.Errorf("panic in go fn: %v", r):
17 | default:
18 | }
19 | }
20 | }()
21 | errs <- fn()
22 | }()
23 |
24 | return errs
25 | }
26 |
--------------------------------------------------------------------------------
/lib/turso/vendor/nhooyr.io/websocket/internal/xsync/int64.go:
--------------------------------------------------------------------------------
1 | package xsync
2 |
3 | import (
4 | "sync/atomic"
5 | )
6 |
7 | // Int64 represents an atomic int64.
8 | type Int64 struct {
9 | // We do not use atomic.Load/StoreInt64 since it does not
10 | // work on 32 bit computers but we need 64 bit integers.
11 | i atomic.Value
12 | }
13 |
14 | // Load loads the int64.
15 | func (v *Int64) Load() int64 {
16 | i, _ := v.i.Load().(int64)
17 | return i
18 | }
19 |
20 | // Store stores the int64.
21 | func (v *Int64) Store(i int64) {
22 | v.i.Store(i)
23 | }
24 |
--------------------------------------------------------------------------------
/lib/turso/vendor/nhooyr.io/websocket/netconn.go:
--------------------------------------------------------------------------------
1 | package websocket
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "io"
7 | "math"
8 | "net"
9 | "sync"
10 | "time"
11 | )
12 |
13 | // NetConn converts a *websocket.Conn into a net.Conn.
14 | //
15 | // It's for tunneling arbitrary protocols over WebSockets.
16 | // Few users of the library will need this but it's tricky to implement
17 | // correctly and so provided in the library.
18 | // See https://github.com/nhooyr/websocket/issues/100.
19 | //
20 | // Every Write to the net.Conn will correspond to a message write of
21 | // the given type on *websocket.Conn.
22 | //
23 | // The passed ctx bounds the lifetime of the net.Conn. If cancelled,
24 | // all reads and writes on the net.Conn will be cancelled.
25 | //
26 | // If a message is read that is not of the correct type, the connection
27 | // will be closed with StatusUnsupportedData and an error will be returned.
28 | //
29 | // Close will close the *websocket.Conn with StatusNormalClosure.
30 | //
31 | // When a deadline is hit, the connection will be closed. This is
32 | // different from most net.Conn implementations where only the
33 | // reading/writing goroutines are interrupted but the connection is kept alive.
34 | //
35 | // The Addr methods will return a mock net.Addr that returns "websocket" for Network
36 | // and "websocket/unknown-addr" for String.
37 | //
38 | // A received StatusNormalClosure or StatusGoingAway close frame will be translated to
39 | // io.EOF when reading.
40 | func NetConn(ctx context.Context, c *Conn, msgType MessageType) net.Conn {
41 | nc := &netConn{
42 | c: c,
43 | msgType: msgType,
44 | }
45 |
46 | var cancel context.CancelFunc
47 | nc.writeContext, cancel = context.WithCancel(ctx)
48 | nc.writeTimer = time.AfterFunc(math.MaxInt64, cancel)
49 | if !nc.writeTimer.Stop() {
50 | <-nc.writeTimer.C
51 | }
52 |
53 | nc.readContext, cancel = context.WithCancel(ctx)
54 | nc.readTimer = time.AfterFunc(math.MaxInt64, cancel)
55 | if !nc.readTimer.Stop() {
56 | <-nc.readTimer.C
57 | }
58 |
59 | return nc
60 | }
61 |
62 | type netConn struct {
63 | c *Conn
64 | msgType MessageType
65 |
66 | writeTimer *time.Timer
67 | writeContext context.Context
68 |
69 | readTimer *time.Timer
70 | readContext context.Context
71 |
72 | readMu sync.Mutex
73 | eofed bool
74 | reader io.Reader
75 | }
76 |
77 | var _ net.Conn = &netConn{}
78 |
79 | func (c *netConn) Close() error {
80 | return c.c.Close(StatusNormalClosure, "")
81 | }
82 |
83 | func (c *netConn) Write(p []byte) (int, error) {
84 | err := c.c.Write(c.writeContext, c.msgType, p)
85 | if err != nil {
86 | return 0, err
87 | }
88 | return len(p), nil
89 | }
90 |
91 | func (c *netConn) Read(p []byte) (int, error) {
92 | c.readMu.Lock()
93 | defer c.readMu.Unlock()
94 |
95 | if c.eofed {
96 | return 0, io.EOF
97 | }
98 |
99 | if c.reader == nil {
100 | typ, r, err := c.c.Reader(c.readContext)
101 | if err != nil {
102 | switch CloseStatus(err) {
103 | case StatusNormalClosure, StatusGoingAway:
104 | c.eofed = true
105 | return 0, io.EOF
106 | }
107 | return 0, err
108 | }
109 | if typ != c.msgType {
110 | err := fmt.Errorf("unexpected frame type read (expected %v): %v", c.msgType, typ)
111 | c.c.Close(StatusUnsupportedData, err.Error())
112 | return 0, err
113 | }
114 | c.reader = r
115 | }
116 |
117 | n, err := c.reader.Read(p)
118 | if err == io.EOF {
119 | c.reader = nil
120 | err = nil
121 | }
122 | return n, err
123 | }
124 |
125 | type websocketAddr struct {
126 | }
127 |
128 | func (a websocketAddr) Network() string {
129 | return "websocket"
130 | }
131 |
132 | func (a websocketAddr) String() string {
133 | return "websocket/unknown-addr"
134 | }
135 |
136 | func (c *netConn) RemoteAddr() net.Addr {
137 | return websocketAddr{}
138 | }
139 |
140 | func (c *netConn) LocalAddr() net.Addr {
141 | return websocketAddr{}
142 | }
143 |
144 | func (c *netConn) SetDeadline(t time.Time) error {
145 | c.SetWriteDeadline(t)
146 | c.SetReadDeadline(t)
147 | return nil
148 | }
149 |
150 | func (c *netConn) SetWriteDeadline(t time.Time) error {
151 | if t.IsZero() {
152 | c.writeTimer.Stop()
153 | } else {
154 | c.writeTimer.Reset(t.Sub(time.Now()))
155 | }
156 | return nil
157 | }
158 |
159 | func (c *netConn) SetReadDeadline(t time.Time) error {
160 | if t.IsZero() {
161 | c.readTimer.Stop()
162 | } else {
163 | c.readTimer.Reset(t.Sub(time.Now()))
164 | }
165 | return nil
166 | }
167 |
--------------------------------------------------------------------------------
/lib/turso/vendor/nhooyr.io/websocket/stringer.go:
--------------------------------------------------------------------------------
1 | // Code generated by "stringer -type=opcode,MessageType,StatusCode -output=stringer.go"; DO NOT EDIT.
2 |
3 | package websocket
4 |
5 | import "strconv"
6 |
7 | func _() {
8 | // An "invalid array index" compiler error signifies that the constant values have changed.
9 | // Re-run the stringer command to generate them again.
10 | var x [1]struct{}
11 | _ = x[opContinuation-0]
12 | _ = x[opText-1]
13 | _ = x[opBinary-2]
14 | _ = x[opClose-8]
15 | _ = x[opPing-9]
16 | _ = x[opPong-10]
17 | }
18 |
19 | const (
20 | _opcode_name_0 = "opContinuationopTextopBinary"
21 | _opcode_name_1 = "opCloseopPingopPong"
22 | )
23 |
24 | var (
25 | _opcode_index_0 = [...]uint8{0, 14, 20, 28}
26 | _opcode_index_1 = [...]uint8{0, 7, 13, 19}
27 | )
28 |
29 | func (i opcode) String() string {
30 | switch {
31 | case 0 <= i && i <= 2:
32 | return _opcode_name_0[_opcode_index_0[i]:_opcode_index_0[i+1]]
33 | case 8 <= i && i <= 10:
34 | i -= 8
35 | return _opcode_name_1[_opcode_index_1[i]:_opcode_index_1[i+1]]
36 | default:
37 | return "opcode(" + strconv.FormatInt(int64(i), 10) + ")"
38 | }
39 | }
40 | func _() {
41 | // An "invalid array index" compiler error signifies that the constant values have changed.
42 | // Re-run the stringer command to generate them again.
43 | var x [1]struct{}
44 | _ = x[MessageText-1]
45 | _ = x[MessageBinary-2]
46 | }
47 |
48 | const _MessageType_name = "MessageTextMessageBinary"
49 |
50 | var _MessageType_index = [...]uint8{0, 11, 24}
51 |
52 | func (i MessageType) String() string {
53 | i -= 1
54 | if i < 0 || i >= MessageType(len(_MessageType_index)-1) {
55 | return "MessageType(" + strconv.FormatInt(int64(i+1), 10) + ")"
56 | }
57 | return _MessageType_name[_MessageType_index[i]:_MessageType_index[i+1]]
58 | }
59 | func _() {
60 | // An "invalid array index" compiler error signifies that the constant values have changed.
61 | // Re-run the stringer command to generate them again.
62 | var x [1]struct{}
63 | _ = x[StatusNormalClosure-1000]
64 | _ = x[StatusGoingAway-1001]
65 | _ = x[StatusProtocolError-1002]
66 | _ = x[StatusUnsupportedData-1003]
67 | _ = x[statusReserved-1004]
68 | _ = x[StatusNoStatusRcvd-1005]
69 | _ = x[StatusAbnormalClosure-1006]
70 | _ = x[StatusInvalidFramePayloadData-1007]
71 | _ = x[StatusPolicyViolation-1008]
72 | _ = x[StatusMessageTooBig-1009]
73 | _ = x[StatusMandatoryExtension-1010]
74 | _ = x[StatusInternalError-1011]
75 | _ = x[StatusServiceRestart-1012]
76 | _ = x[StatusTryAgainLater-1013]
77 | _ = x[StatusBadGateway-1014]
78 | _ = x[StatusTLSHandshake-1015]
79 | }
80 |
81 | const _StatusCode_name = "StatusNormalClosureStatusGoingAwayStatusProtocolErrorStatusUnsupportedDatastatusReservedStatusNoStatusRcvdStatusAbnormalClosureStatusInvalidFramePayloadDataStatusPolicyViolationStatusMessageTooBigStatusMandatoryExtensionStatusInternalErrorStatusServiceRestartStatusTryAgainLaterStatusBadGatewayStatusTLSHandshake"
82 |
83 | var _StatusCode_index = [...]uint16{0, 19, 34, 53, 74, 88, 106, 127, 156, 177, 196, 220, 239, 259, 278, 294, 312}
84 |
85 | func (i StatusCode) String() string {
86 | i -= 1000
87 | if i < 0 || i >= StatusCode(len(_StatusCode_index)-1) {
88 | return "StatusCode(" + strconv.FormatInt(int64(i+1000), 10) + ")"
89 | }
90 | return _StatusCode_name[_StatusCode_index[i]:_StatusCode_index[i+1]]
91 | }
92 |
--------------------------------------------------------------------------------
/lib/turso/vendor/nhooyr.io/websocket/wsjson/wsjson.go:
--------------------------------------------------------------------------------
1 | // Package wsjson provides helpers for reading and writing JSON messages.
2 | package wsjson // import "nhooyr.io/websocket/wsjson"
3 |
4 | import (
5 | "context"
6 | "encoding/json"
7 | "fmt"
8 |
9 | "nhooyr.io/websocket"
10 | "nhooyr.io/websocket/internal/bpool"
11 | "nhooyr.io/websocket/internal/errd"
12 | )
13 |
14 | // Read reads a JSON message from c into v.
15 | // It will reuse buffers in between calls to avoid allocations.
16 | func Read(ctx context.Context, c *websocket.Conn, v interface{}) error {
17 | return read(ctx, c, v)
18 | }
19 |
20 | func read(ctx context.Context, c *websocket.Conn, v interface{}) (err error) {
21 | defer errd.Wrap(&err, "failed to read JSON message")
22 |
23 | _, r, err := c.Reader(ctx)
24 | if err != nil {
25 | return err
26 | }
27 |
28 | b := bpool.Get()
29 | defer bpool.Put(b)
30 |
31 | _, err = b.ReadFrom(r)
32 | if err != nil {
33 | return err
34 | }
35 |
36 | err = json.Unmarshal(b.Bytes(), v)
37 | if err != nil {
38 | c.Close(websocket.StatusInvalidFramePayloadData, "failed to unmarshal JSON")
39 | return fmt.Errorf("failed to unmarshal JSON: %w", err)
40 | }
41 |
42 | return nil
43 | }
44 |
45 | // Write writes the JSON message v to c.
46 | // It will reuse buffers in between calls to avoid allocations.
47 | func Write(ctx context.Context, c *websocket.Conn, v interface{}) error {
48 | return write(ctx, c, v)
49 | }
50 |
51 | func write(ctx context.Context, c *websocket.Conn, v interface{}) (err error) {
52 | defer errd.Wrap(&err, "failed to write JSON message")
53 |
54 | w, err := c.Writer(ctx, websocket.MessageText)
55 | if err != nil {
56 | return err
57 | }
58 |
59 | // json.Marshal cannot reuse buffers between calls as it has to return
60 | // a copy of the byte slice but Encoder does as it directly writes to w.
61 | err = json.NewEncoder(w).Encode(v)
62 | if err != nil {
63 | return fmt.Errorf("failed to marshal JSON: %w", err)
64 | }
65 |
66 | return w.Close()
67 | }
68 |
--------------------------------------------------------------------------------
/nix/default.nix:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ethanthoma/ocaml-webserver/9a5736548d4bc7b3cfc052c949a61f455276f2b2/nix/default.nix
--------------------------------------------------------------------------------
/nix/docker.nix:
--------------------------------------------------------------------------------
1 | { pkgs, package, derivation }:
2 |
3 | let
4 | baseImage = pkgs.dockerTools.pullImage {
5 | imageName = "ghcr.io/tursodatabase/libsql-server";
6 | imageDigest = "sha256:253207a90cdae061ce8702625011c62ff6b5725119199b2d079de06ae8001700";
7 | sha256 = "8+nYYweux3KKtkip4GMtsZ/TmiYKueFCj3TQrUQ3GVM=";
8 | };
9 | in pkgs.dockerTools.buildImage {
10 | name = package;
11 | tag = "latest";
12 | created = "now";
13 | fromImage = baseImage;
14 | copyToRoot = pkgs.buildEnv {
15 | name = "webserver";
16 | paths = [
17 | derivation
18 | pkgs.bash
19 | pkgs.getconf
20 | pkgs.turso-cli
21 | ];
22 | pathsToLink = [ "/bin" "/public" "/blogs" ];
23 | };
24 | config = {
25 | Cmd = [
26 | "${derivation}/bin/${package}"
27 | ];
28 | Env = [
29 | "TURSO_INSTALL_SKIP_SIGNUP=true"
30 | ];
31 | ExposedPorts = {
32 | "3000/tcp" = {};
33 | };
34 | };
35 | }
36 |
--------------------------------------------------------------------------------
/nix/shell.nix:
--------------------------------------------------------------------------------
1 | {
2 | pkgs ? let
3 | nixpkgs = fetchTarball "https://github.com/NixOS/nixpkgs/tarball/nixpkgs-unstable";
4 | in import nixpkgs {},
5 | devDeps
6 | }:
7 |
8 | pkgs.mkShell {
9 | packages = with pkgs; [
10 | ocaml
11 | ocamlformat
12 | ocamlPackages.findlib
13 | ocamlPackages.ocamlformat-rpc-lib
14 | ocamlPackages.utop
15 | opam
16 | turso-cli
17 | pkg-config
18 | openssl
19 | ] ++ devDeps;
20 |
21 | shellHook = ''
22 | turso config set token "$TURSO_TOKEN"
23 | eval $(opam env --switch=website)
24 | '';
25 | }
26 |
--------------------------------------------------------------------------------
/public/favicon/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ethanthoma/ocaml-webserver/9a5736548d4bc7b3cfc052c949a61f455276f2b2/public/favicon/android-chrome-192x192.png
--------------------------------------------------------------------------------
/public/favicon/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ethanthoma/ocaml-webserver/9a5736548d4bc7b3cfc052c949a61f455276f2b2/public/favicon/android-chrome-512x512.png
--------------------------------------------------------------------------------
/public/favicon/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ethanthoma/ocaml-webserver/9a5736548d4bc7b3cfc052c949a61f455276f2b2/public/favicon/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/favicon/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ethanthoma/ocaml-webserver/9a5736548d4bc7b3cfc052c949a61f455276f2b2/public/favicon/favicon-16x16.png
--------------------------------------------------------------------------------
/public/favicon/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ethanthoma/ocaml-webserver/9a5736548d4bc7b3cfc052c949a61f455276f2b2/public/favicon/favicon-32x32.png
--------------------------------------------------------------------------------
/public/favicon/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ethanthoma/ocaml-webserver/9a5736548d4bc7b3cfc052c949a61f455276f2b2/public/favicon/favicon.ico
--------------------------------------------------------------------------------
/public/favicon/site.webmanifest:
--------------------------------------------------------------------------------
1 | {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
--------------------------------------------------------------------------------
/public/fonts/fonts.css:
--------------------------------------------------------------------------------
1 | /* latin */
2 | @font-face {
3 | font-family: 'Montserrat';
4 | font-style: normal;
5 | font-weight: 100 900;
6 | font-display: swap;
7 | src: url('./montserrat-latin.woff2') format('woff2');
8 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
9 | }
10 | /* latin */
11 | @font-face {
12 | font-family: 'Montserrat';
13 | font-style: italic;
14 | font-weight: 100 900;
15 | font-display: swap;
16 | src: url('./montserrat-latin-italic.woff2') format('woff2');
17 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
18 | }
19 | /* latin */
20 | @font-face {
21 | font-family: 'Inter';
22 | font-style: normal;
23 | font-weight: 100 900;
24 | font-display: swap;
25 | src: url('./inter-latin.woff2') format('woff2');
26 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
27 | }
28 |
--------------------------------------------------------------------------------
/public/fonts/inter-latin.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ethanthoma/ocaml-webserver/9a5736548d4bc7b3cfc052c949a61f455276f2b2/public/fonts/inter-latin.woff2
--------------------------------------------------------------------------------
/public/fonts/montserrat-latin-italic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ethanthoma/ocaml-webserver/9a5736548d4bc7b3cfc052c949a61f455276f2b2/public/fonts/montserrat-latin-italic.woff2
--------------------------------------------------------------------------------
/public/fonts/montserrat-latin.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ethanthoma/ocaml-webserver/9a5736548d4bc7b3cfc052c949a61f455276f2b2/public/fonts/montserrat-latin.woff2
--------------------------------------------------------------------------------
/public/js/antiThePrimeagen@0.0.2.js:
--------------------------------------------------------------------------------
1 | /* The point of this script is to stop ThePrimeagen from hightlighting how he
2 | * wants. His twitter is here: https://twitter.com/ThePrimeagen
3 | */
4 |
5 | // doesnt work across element tags.
6 | // TODO: get it to only take effect for text in blog-content and work acorss nodes
7 | let antiThePrimeagenEnabled = false;
8 |
9 | function mouseSelection(element) {
10 | const selection = window.getSelection();
11 | if (!selection.isCollapsed) {
12 | const wordRange = document.createRange();
13 |
14 | let startNode = selection.anchorNode, startOffset = selection.anchorOffset;
15 | let endNode = selection.focusNode, endOffset = selection.focusOffset;
16 |
17 | if (selection.anchorNode === selection.focusNode) {
18 | if (selection.anchorOffset > selection.focusOffset) {
19 | [startOffset, endOffset] = [endOffset, startOffset];
20 | }
21 | } else if (selection.anchorNode.compareDocumentPosition(selection.focusNode) & Node.DOCUMENT_POSITION_FOLLOWING) {
22 | [startNode, endNode] = [endNode, startNode];
23 | [startOffset, endOffset] = [endOffset, startOffset];
24 | }
25 |
26 | let textContent = startNode.textContent;
27 | while (startOffset > 0 && /\S/.test(textContent[startOffset - 1])) {
28 | startOffset--;
29 | }
30 |
31 | textContent = endNode.textContent;
32 | while (endOffset < textContent.length && /\S/.test(textContent[endOffset])) {
33 | endOffset++;
34 | }
35 |
36 | wordRange.setStart(startNode, startOffset);
37 | wordRange.setEnd(endNode, endOffset);
38 |
39 | selection.removeAllRanges();
40 | selection.addRange(wordRange);
41 | }
42 | }
43 |
44 | document.addEventListener('htmx:load', function(evt) {
45 | let els = document.getElementsByClassName('blog-content');
46 |
47 | [...els].forEach((element, index, array) => {
48 | if (antiThePrimeagenEnabled) {
49 | element.addEventListener('mouseup', function(e) {
50 | mouseSelection(element);
51 | console.log("antiThePrimeagen: activated.");
52 | });
53 | }
54 | });
55 | });
56 |
--------------------------------------------------------------------------------
/public/seo/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Allow: /
3 |
4 | Sitemap: https://www.ethanthoma.com/sitemap.xml
5 |
--------------------------------------------------------------------------------
/public/seo/sitemap.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | http://www.ethanthoma.com
6 |
7 |
8 | http://www.ethanthoma.com/explore
9 |
10 |
11 | http://www.ethanthoma.com/blogs/how_not_to_build_a_website.md
12 |
13 |
14 |
--------------------------------------------------------------------------------
/public/styles/abstract/index.css:
--------------------------------------------------------------------------------
1 | @import "variables.css";
2 |
--------------------------------------------------------------------------------
/public/styles/base/base.css:
--------------------------------------------------------------------------------
1 | html {
2 | scroll-behavior: smooth;
3 | }
4 |
5 | body {
6 | display: flex;
7 | flex-direction: column;
8 | max-width: 100dvw;
9 |
10 | background: var(--clr-base);
11 | color: var(--clr-text);
12 | font-family: var(--ff-body);
13 | font-size: var(--fs-300);
14 | line-height: 1.4;
15 | letter-spacing: 0.05em;
16 |
17 | overflow-x: hidden;
18 |
19 | &>* {
20 | margin-inline: auto;
21 | width: 100%;
22 | }
23 | }
24 |
25 | h1,
26 | h2,
27 | h3 {
28 | font-family: var(--ff-heading);
29 | letter-spacing: -0.01em;
30 | line-height: 1.2;
31 | font-weight: 600;
32 | }
33 |
34 | h1 {
35 | font-size: var(--fs-700);
36 | }
37 |
38 | h2 {
39 | font-size: var(--fs-500);
40 | }
41 |
42 | h3 {
43 | font-size: var(--fs-400);
44 | }
45 |
46 | a {
47 | color: var(--clr-iris);
48 | border-radius: var(--space-3xs);
49 | cursor: pointer;
50 |
51 | &:hover,
52 | &:focus,
53 | &:focus-visible {
54 | color: var(--clr-gold);
55 |
56 | transition: color var(--animate-t) var(--animate-f);
57 | }
58 |
59 | &:focus-visible {
60 | outline-offset: 2px;
61 | outline: var(--clr-foam) solid 2px;
62 | }
63 | }
64 |
65 | ::-webkit-scrollbar {
66 | width: 15px;
67 | background: transparent;
68 | }
69 |
70 | ::-webkit-scrollbar-track {
71 | background: var(--clr-base);
72 | box-shadow: inset 0 0 0 1px var(--clr-high);
73 | border-radius: var(--space-3xs);
74 | }
75 |
76 | ::-webkit-scrollbar-thumb {
77 | background-color: var(--clr-muted);
78 | border-radius: var(--space-3xs);
79 | border: 3px solid transparent;
80 |
81 | background-clip: content-box;
82 | }
83 |
84 | ::-webkit-scrollbar-thumb:hover {
85 | background-color: var(--clr-subtle);
86 | }
87 |
--------------------------------------------------------------------------------
/public/styles/base/index.css:
--------------------------------------------------------------------------------
1 | @import "reset.css";
2 | @import "base.css";
3 | @import "util.css";
4 |
--------------------------------------------------------------------------------
/public/styles/base/reset.css:
--------------------------------------------------------------------------------
1 | /* Remove default margin */
2 | * {
3 | margin: 0;
4 | padding: 0;
5 | border: 0;
6 | font: inherit;
7 | font-size: 100%;
8 | vertical-align: baseline;
9 | text-rendering: optimizeLegibility;
10 | -webkit-font-smoothing: antialiased;
11 | text-size-adjust: none;
12 | }
13 |
14 | /* Box sizing rules */
15 | *,
16 | *::before,
17 | *::after {
18 | box-sizing: border-box;
19 | }
20 |
21 | /* Remove default margin */
22 | body,
23 | h1,
24 | h2,
25 | h3,
26 | h4,
27 | p,
28 | figure,
29 | blockquote,
30 | dl,
31 | dd {
32 | margin-block-end: 0;
33 | }
34 |
35 | /* Remove list styles on ul, ol elements with a list role, which suggests default styling will be removed */
36 | ul[role='list'],
37 | ol[role='list'] {
38 | list-style: none;
39 | }
40 |
41 | /* Set core root defaults */
42 | html:focus-within {
43 | scroll-behavior: smooth;
44 | }
45 |
46 | html {
47 | height: 100%;
48 | }
49 |
50 | /* Set core body defaults */
51 | body {
52 | min-height: 100%;
53 | width: 100vw;
54 | max-width: 100%;
55 | box-sizing: border-box;
56 | position: relative;
57 | text-rendering: optimizeSpeed;
58 | line-height: 1.5;
59 | }
60 |
61 | /* A elements that don't have a class get default styles */
62 | a:not([class]) {
63 | text-decoration-skip-ink: auto;
64 | }
65 |
66 | /* Make images easier to work with */
67 | img,
68 | picture,
69 | svg {
70 | max-width: 100%;
71 | display: block;
72 | }
73 |
74 | footer,
75 | header,
76 | nav,
77 | section,
78 | main {
79 | display: block;
80 | }
81 |
82 | blockquote,
83 | q {
84 | quotes: none;
85 | }
86 |
87 | blockquote:before,
88 | blockquote:after,
89 | q:before,
90 | q:after {
91 | content: '';
92 | content: none;
93 | }
94 |
95 | table {
96 | border-collapse: collapse;
97 | border-spacing: 0;
98 | }
99 |
100 | input {
101 | -webkit-appearance: none;
102 | appearance: none;
103 | border-radius: 0;
104 | }
105 |
106 | /* Remove all animations, transitions and smooth scroll for people that prefer not to see them */
107 | @media (prefers-reduced-motion: reduce) {
108 | html:focus-within {
109 | scroll-behavior: auto;
110 | }
111 |
112 | *,
113 | *::before,
114 | *::after {
115 | animation-duration: 0.01ms !important;
116 | animation-iteration-count: 1 !important;
117 | transition-duration: 0.01ms !important;
118 | scroll-behavior: auto !important;
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/public/styles/base/util.css:
--------------------------------------------------------------------------------
1 | .hidden {
2 | border-width: 0 !important;
3 | clip: rect(0 0 0 0);
4 | clip-path: inset(50%);
5 | height: 0px;
6 | overflow: hidden;
7 | position: absolute;
8 | white-space: nowrap;
9 | width: 0px;
10 | }
11 |
12 | .italic,
13 | em {
14 | font-style: italic;
15 | }
16 |
17 | .bold,
18 | strong {
19 | font-weight: 600;
20 | font-style: normal;
21 | }
22 |
23 | .outline {
24 | transition: outline var(--animate-t) var(--animate-f);
25 |
26 | &:hover,
27 | &:focus,
28 | &:focus-visible {
29 | outline-offset: 2px;
30 | outline: var(--clr-foam) solid 2px;
31 | }
32 | }
33 |
34 | .border {
35 | transition:
36 | background-color var(--animate-t) var(--animate-f),
37 | box-shadow var(--animate-t) var(--animate-f);
38 |
39 | &:hover,
40 | &:focus,
41 | &:focus-visible {
42 | outline: unset;
43 |
44 | background-color: var(--clr-muted);
45 | box-shadow: inset 0 0 0 2px var(--clr-foam);
46 | }
47 | }
48 |
49 | .animate-fade-in {
50 | --animate-f: ease-in;
51 |
52 | opacity: 1;
53 | transition: opacity var(--animate-t) var(--animate-f);
54 |
55 | &.htmx-added {
56 | opacity: 0;
57 | }
58 | }
59 |
60 | @keyframes fadeInUp {
61 | from {
62 | opacity: 0;
63 | transform: translateY(20px);
64 | }
65 |
66 | to {
67 | opacity: 1;
68 | transform: translateY(0);
69 | }
70 | }
71 |
72 | .staggered-load {
73 | &>.staggered-load-child {
74 | opacity: 0;
75 | animation: fadeInUp 0.5s ease forwards;
76 | transform: translateY(20px);
77 | }
78 |
79 | &>.staggered-load-child:nth-child(1) {
80 | animation-delay: 0.15s;
81 | }
82 |
83 | &>.staggered-load-child:nth-child(2) {
84 | animation-delay: 0.3s;
85 | }
86 |
87 | &>.staggered-load-child:nth-child(3) {
88 | animation-delay: 0.45s;
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/public/styles/components/call_to_action.css:
--------------------------------------------------------------------------------
1 | .call-to-action {
2 | width: fit-content;
3 |
4 | text-align: center;
5 | display: inline-block;
6 | text-decoration: none;
7 |
8 | padding-block: var(--space-s);
9 | padding-inline: var(--space-l);
10 |
11 | color: var(--clr-base);
12 | background: var(--clr-foam);
13 |
14 | border-radius: var(--space-xs);
15 |
16 | box-shadow: var(--shadow-low);
17 |
18 | a {
19 | transition: unset;
20 | }
21 |
22 | &:hover,
23 | &:focus,
24 | &:focus-visible {
25 | outline: unset;
26 |
27 | transition:
28 | color var(--animate-t) var(--animate-f),
29 | background var(--animate-t) var(--animate-f),
30 | box-shadow var(--animate-t) var(--animate-f);
31 |
32 | color: var(--clr-text);
33 | background: var(--clr-surface);
34 | box-shadow:
35 | inset 0 0 0 2px var(--clr-foam),
36 | var(--shadow-low);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/public/styles/components/card.css:
--------------------------------------------------------------------------------
1 | .card {
2 | --animate-t: 0.2s;
3 | --animate-f: cubic-bezier(0, 0, .2, 1);
4 | --clr-bg: var(--clr-surface);
5 | --clr-fg: var(--clr-text);
6 |
7 | border-radius: var(--space-s);
8 |
9 | padding: var(--space-m);
10 |
11 | background-color: var(--clr-bg);
12 | color: var(--clr-fg);
13 | position: relative;
14 |
15 | transition: scale var(--animate-t) var(--animate-f);
16 |
17 | &:hover,
18 | &:focus,
19 | &:focus-visible,
20 | &:focus-within {
21 | --clr-bg: var(--clr-muted);
22 | }
23 |
24 | & a {
25 | color: inherit;
26 | display: flex;
27 | flex-direction: column;
28 | height: 100%;
29 | text-decoration: none;
30 |
31 | &:hover,
32 | &:focus,
33 | &:focus-visible {
34 | color: inherit;
35 | outline: unset;
36 | }
37 |
38 | & p {
39 | margin-top: var(--space-xs);
40 | }
41 |
42 | & div {
43 | margin-block-start: auto;
44 | padding-block-start: var(--space-m);
45 |
46 | color: var(--clr-subtle);
47 | }
48 |
49 | &::after {
50 | content: '';
51 | position: absolute;
52 | inset: 0;
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/public/styles/components/fancy.css:
--------------------------------------------------------------------------------
1 | .fancy {
2 | white-space: nowrap;
3 |
4 | /* bg gradient */
5 | background: linear-gradient(to right,
6 | var(--clr-rose),
7 | var(--clr-iris),
8 | var(--clr-foam),
9 | var(--clr-iris),
10 | var(--clr-rose));
11 | background-size: 200%;
12 | -webkit-background-clip: text;
13 | background-clip: text;
14 | -webkit-text-fill-color: transparent;
15 |
16 | /* animation */
17 | animation: background-pan 3s linear infinite;
18 | }
19 |
20 | @keyframes background-pan {
21 | from {
22 | background-position: 0% center;
23 | }
24 |
25 | to {
26 | background-position: -200% center;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/public/styles/components/footer.css:
--------------------------------------------------------------------------------
1 | body>footer {
2 | align-items: center;
3 | display: flex;
4 | flex-direction: column;
5 |
6 | --_col: color-mix(in srgb, color-mix(in srgb, var(--clr-text), var(--clr-base) 50%), black 60%);
7 |
8 | margin-block-start: auto;
9 | padding-block: var(--space-xl);
10 |
11 | background: color-mix(in srgb, var(--_col), black 60%);
12 | border-block-start: 2px solid var(--clr-high);
13 | color: color-mix(in srgb, var(--_col), white 60%);
14 | font-size: var(--fs-100);
15 | text-align: center;
16 |
17 | &>*+* {
18 | margin-block-start: var(--space-s);
19 | }
20 |
21 | & #nav {
22 | list-style: none;
23 | display: flex;
24 | gap: var(--space-s);
25 | }
26 |
27 | & #contact {
28 | list-style: none;
29 | display: flex;
30 | gap: var(--space-xs-s);
31 | align-items: center;
32 | justify-content: center;
33 |
34 | & a {
35 | display: flex;
36 | border-radius: var(--space-3xs);
37 |
38 | & .icon {
39 | height: 100%;
40 | aspect-ratio: 1;
41 | transition: scale var(--animate-t) var(--animate-f);
42 | font-size: var(--fs-400);
43 | }
44 |
45 | &:hover,
46 | &:focus,
47 | &:focus-visible {
48 | & .icon {
49 | scale: 0.7;
50 | }
51 | }
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/public/styles/components/header.css:
--------------------------------------------------------------------------------
1 | body>header {
2 | display: flex;
3 |
4 | flex-wrap: wrap;
5 | gap: var(--space-s-m);
6 | padding-block: var(--space-s-m);
7 | padding-inline: var(--padding-inline-l);
8 |
9 | font-weight: 400;
10 | font-family: var(--ff-heading);
11 | font-size: var(--fs-300);
12 |
13 | background: var(--clr-base);
14 | position: sticky;
15 | top: 0;
16 | z-index: 2;
17 |
18 | &>* {
19 | --clr-bg-parent: var(--clr-base);
20 | }
21 |
22 | & a {
23 | color: inherit;
24 | display: flex;
25 | align-items: center;
26 | justify-content: center;
27 | text-decoration: none;
28 |
29 | &:hover,
30 | &:focus,
31 | &:focus-visible {
32 | color: var(--clr-gold);
33 | }
34 | }
35 |
36 | & #left {
37 | display: flex;
38 | flex: 1;
39 |
40 | gap: var(--space-xs-s);
41 | align-items: center;
42 | justify-content: space-between;
43 |
44 | & .icon {
45 | height: 100%;
46 | width: unset;
47 | aspect-ratio: 1;
48 | transition: scale var(--animate-t) var(--animate-f);
49 | }
50 | }
51 |
52 | & nav {
53 | display: flex;
54 | gap: var(--space-s-l);
55 | flex: 2;
56 | justify-content: flex-end;
57 | align-items: center;
58 |
59 | & li {
60 | display: flex;
61 | align-items: center;
62 | justify-content: center;
63 | list-style: none;
64 |
65 | & a {
66 | border-radius: var(--space-3xs);
67 |
68 | &:hover,
69 | &:focus,
70 | &:focus-visible {
71 | & .icon {
72 | scale: 0.7;
73 | }
74 | }
75 | }
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/public/styles/components/hero.css:
--------------------------------------------------------------------------------
1 | .hero {
2 | display: grid;
3 |
4 | gap: var(--space-s-l);
5 |
6 | align-items: center;
7 | }
8 |
9 | .hero .text {
10 | display: flex;
11 |
12 | flex-direction: column;
13 |
14 | justify-content: center;
15 | margin-block-end: var(--space-s-l);
16 |
17 | &>*+* {
18 | margin-block-start: var(--space-xs-s);
19 | }
20 |
21 | & .subtitle {
22 | text-transform: uppercase;
23 | font-size: var(--fs-100);
24 | color: var(--clr-pine);
25 | font-weight: 700;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/public/styles/components/icon.css:
--------------------------------------------------------------------------------
1 | .icon {
2 | --animate-t: 0.5s;
3 | --animate-f: ease;
4 |
5 | display: inline-block;
6 |
7 | color: var(--clr-text);
8 |
9 | font-size: inherit;
10 |
11 | width: 1em;
12 | height: 1em;
13 |
14 | contain: strict;
15 |
16 | box-sizing: content-box !important;
17 |
18 | & .content {
19 | color: inherit;
20 |
21 | height: inherit;
22 | width: inherit;
23 |
24 | stroke: currentColor;
25 | font-size: inherit;
26 | fill: currentColor;
27 |
28 | & * {
29 | display: block;
30 | font-size: inherit;
31 |
32 | height: 100%;
33 | width: 100%;
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/public/styles/components/index.css:
--------------------------------------------------------------------------------
1 | @import "call_to_action.css";
2 | @import "card.css";
3 | @import "fancy.css";
4 | @import "footer.css";
5 | @import "header.css";
6 | @import "icon.css";
7 | @import "logo.css";
8 | @import "search.css";
9 | @import "tags.css";
10 |
--------------------------------------------------------------------------------
/public/styles/components/logo.css:
--------------------------------------------------------------------------------
1 | .logo {
2 | border-radius: var(--space-3xs);
3 | height: var(--space-l);
4 |
5 | background-color: var(--clr-bg);
6 | color: var(--clr-fg);
7 | box-shadow:
8 | inset 0 0 0 2px var(--clr-bg),
9 | inset 0 0 0 4px var(--clr-fg);
10 |
11 | & .icon {
12 | color: var(--clr-fg);
13 | stroke-width: 60px;
14 | stroke: currentColor;
15 | scale: 1.4;
16 | transition: unset;
17 | }
18 |
19 | &:hover,
20 | &:focus,
21 | &:focus-visible {
22 | outline-offset: -2px;
23 | outline: var(--clr-foam) solid 2px;
24 | }
25 | }
26 |
27 | /* bg should always be light, fg always dark */
28 | @media (prefers-color-scheme: light) {
29 | .logo {
30 | --clr-bg: var(--clr-bg-parent, var(--clr-base));
31 | --clr-fg: var(--clr-text);
32 |
33 | box-shadow: inset 0 0 0 2px var(--clr-fg);
34 | }
35 | }
36 |
37 | @media (prefers-color-scheme: dark) {
38 | .logo {
39 | --clr-bg: var(--clr-text);
40 | --clr-fg: var(--clr-bg-parent, var(--clr-base));
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/public/styles/components/search.css:
--------------------------------------------------------------------------------
1 | #search {
2 | --width: calc(8 * var(--space-l));
3 | --height: var(--space-l);
4 |
5 | font-size: var(--fs-200);
6 |
7 | & #search-bar {
8 | display: flex;
9 | flex-direction: column;
10 | align-items: center;
11 |
12 | & input {
13 | width: var(--width);
14 | height: var(--height);
15 |
16 | border-radius: var(--space-3xs);
17 |
18 | padding-block: var(--space-3xs);
19 | padding-inline: var(--space-xs);
20 |
21 | background: var(--clr-text);
22 | color: var(--clr-base);
23 |
24 | &::placeholder {
25 | color: var(--clr-base);
26 | opacity: 1;
27 | }
28 | }
29 | }
30 |
31 | & #search-results {
32 | width: var(--width);
33 | }
34 | }
35 |
36 | #search-results-content {
37 | --max-count: 5;
38 |
39 | z-index: 1;
40 |
41 | width: calc(1 * var(--width));
42 | max-height: calc(var(--max-count) * var(--height));
43 |
44 | overflow-x: hidden;
45 | overflow-y: auto;
46 |
47 | position: absolute;
48 | transform: translateY(var(--height));
49 |
50 | border-radius: var(--space-3xs);
51 |
52 | background: var(--clr-surface);
53 | box-shadow: inset 0 0 0 1px var(--clr-high);
54 |
55 | & li {
56 | display: block;
57 | width: 100%;
58 | position: relative;
59 |
60 | & button {
61 | cursor: pointer;
62 | width: inherit;
63 | height: var(--height);
64 | text-overflow: ellipsis;
65 | overflow: hidden;
66 | white-space: nowrap;
67 |
68 | padding-block: var(--space-3xs);
69 | padding-inline: var(--space-xs);
70 |
71 | text-align: start;
72 | color: inherit;
73 | background: inherit;
74 | border-radius: var(--space-3xs);
75 |
76 | &:hover,
77 | &:focus,
78 | &:focus-visible {
79 | background: var(--clr-overlay);
80 | }
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/public/styles/components/tags.css:
--------------------------------------------------------------------------------
1 | .tags {
2 | margin-block-start: var(--space-s);
3 | list-style: none;
4 | display: flex;
5 | flex-wrap: wrap;
6 | gap: var(--space-2xs);
7 |
8 | & .tag {
9 | font-size: var(--fs-200);
10 | background-color: var(--clr-rose);
11 | color: var(--_clr);
12 | border-radius: var(--space-s);
13 | padding-block: var(--space-3xs);
14 | padding-inline: var(--space-xs);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/public/styles/main.css:
--------------------------------------------------------------------------------
1 | @import "abstract/index.css";
2 | @import "base/index.css" layer(base);
3 | @import "components/index.css" layer(components);
4 | @import "pages/index.css" layer(pages);
5 |
--------------------------------------------------------------------------------
/public/styles/pages/blog.css:
--------------------------------------------------------------------------------
1 | .blog-content {
2 | background: var(--clr-surface);
3 |
4 | border-radius: var(--space-3xs-2xs);
5 |
6 | padding-block: var(--space-m-l);
7 | padding-inline: var(--space-xs-xl);
8 |
9 | margin-block: var(--space-m-l);
10 | margin-inline: max(calc((100% - var(--max-width)) / 2), var(--space-xs-m));
11 |
12 | /* margin spacing */
13 | &>*+* {
14 | margin-block-start: var(--space-m-l);
15 | }
16 |
17 | &>button {
18 | margin-block-start: var(--space-l-xl);
19 | }
20 |
21 | & .date {
22 | margin-block-start: var(--space-3xs-2xs);
23 | text-align: center;
24 | font-family: var(--ff-heading);
25 | font-size: var(--fs-200);
26 | }
27 |
28 | & h1 {
29 | margin-block-start: var(--space-3xs-2xs);
30 | text-align: center;
31 | }
32 |
33 | & h1:after {
34 | --_width: 60;
35 | --_transx: calc((100 / (2 * var(--_width))) - 0.5);
36 |
37 | width: calc(var(--_width) * 1%);
38 | transform: translateX(calc(var(--_transx) * 100%));
39 |
40 | content: ' ';
41 | margin-block-start: var(--space-m-l);
42 | display: block;
43 | border: 1px solid var(--clr-text);
44 | border-radius: var(--space-3xs);
45 | }
46 |
47 | & h2,
48 | & h3 {
49 | margin-block-start: var(--space-l-xl);
50 | }
51 |
52 | & ul,
53 | & ol {
54 | list-style-position: inside;
55 |
56 | &>li {
57 | margin-inline-start: var(--space-m);
58 | }
59 |
60 | &>*+li {
61 | margin-block-start: var(--space-2xs);
62 | }
63 | }
64 |
65 | & p>code {
66 | color: var(--clr-foam);
67 | font-style: italic;
68 | }
69 |
70 | & pre {
71 | padding: var(--space-xs);
72 | background: var(--clr-overlay);
73 | font-size: var(--fs-200);
74 | overflow-x: auto;
75 |
76 | &>code {
77 | word-wrap: break-word;
78 | }
79 | }
80 |
81 | & blockquote {
82 | border-inline-start: var(--space-3xs) solid var(--clr-high);
83 | padding-inline-start: var(--space-3xs);
84 | }
85 |
86 | .explore-footer {
87 | width: 100%;
88 | display: flex;
89 | align-items: center;
90 | justify-content: center;
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/public/styles/pages/explore.css:
--------------------------------------------------------------------------------
1 | .blog-cards {
2 | display: flex;
3 | flex-direction: column;
4 | gap: var(--space-m-l);
5 | align-items: center;
6 | margin-inline: max(calc((100% - var(--max-width)) / 2), var(--space-xs-m));
7 | padding-block: var(--space-m-l);
8 | }
9 |
10 | .blog-card {
11 | width: 100%;
12 |
13 | & a {
14 | --_clr: var(--clr-surface);
15 |
16 | background: var(--_clr);
17 | border-radius: var(--space-s);
18 | color: var(--clr-text);
19 | display: flex;
20 | flex-direction: column;
21 | padding: var(--space-m);
22 | text-decoration: none;
23 | transition: transform 0.2s cubic-bezier(0, 0, .2, 1);
24 |
25 | &:hover,
26 | &:focus,
27 | &:focus-visible {
28 | --_clr: var(--clr-muted);
29 | transform: translateY(calc(-1 * var(--space-3xs)));
30 | }
31 |
32 | & p {
33 | margin-block-start: var(--space-xs);
34 | }
35 |
36 | & span {
37 | margin-block-start: var(--space-s);
38 | width: fit-content;
39 | color: var(--clr-iris);
40 | display: block;
41 |
42 | &::after {
43 | content: " →";
44 | white-space: pre;
45 | }
46 |
47 | &:hover,
48 | &:focus,
49 | &:focus-visible {
50 | text-decoration-line: underline;
51 | }
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/public/styles/pages/home.css:
--------------------------------------------------------------------------------
1 | .home {
2 | &>* {
3 | padding-block: var(--space-l-2xl);
4 | padding-inline: var(--padding-inline-m)
5 | }
6 |
7 | & .hero-section {
8 | display: grid;
9 |
10 | gap: var(--space-s-l);
11 |
12 | align-items: center;
13 |
14 | & .text {
15 | display: flex;
16 |
17 | flex-direction: column;
18 | gap: var(--space-s-m);
19 | justify-content: center;
20 |
21 | &:not(:last-child) {
22 | margin-block-start: var(--space-xs-s);
23 | }
24 |
25 | & .subtitle {
26 | text-transform: uppercase;
27 | font-size: var(--fs-100);
28 | color: var(--clr-pine);
29 | font-weight: 700;
30 | }
31 | }
32 | }
33 |
34 | & .blog-section {
35 | padding-inline: var(--padding-inline-l);
36 | padding-block-start: calc(var(--space-l-2xl) / 2);
37 |
38 | background: var(--clr-overlay);
39 |
40 | display: flex;
41 | flex-direction: column;
42 | gap: calc(var(--space-l-2xl) / 2);
43 | align-items: center;
44 |
45 | & header {
46 | width: 100%;
47 | display: flex;
48 | justify-content: space-between;
49 | flex-wrap: wrap;
50 | column-gap: var(--space-3xl);
51 | row-gap: var(--space-m);
52 |
53 | & a {
54 | color: var(--clr-text);
55 | margin-block: auto;
56 | text-decoration: none;
57 | font-size: var(--fs-200);
58 |
59 | &:hover,
60 | &:focus,
61 | &:focus-visible {
62 | color: var(--clr-gold);
63 | }
64 | }
65 | }
66 |
67 | & section {
68 | --grid-gutter: var(--space-s-l, clamp(1.125rem, 0.6467rem + 2.3913vw, 2.5rem));
69 | --content-width: calc(4 * var(--space-xl));
70 |
71 | width: 100%;
72 | display: grid;
73 | grid-template-columns: repeat(var(--grid-columns), 1fr);
74 | grid-gap: var(--grid-gutter);
75 | }
76 | }
77 | }
78 |
79 |
80 | :root {
81 | --grid-columns: 4;
82 | }
83 |
84 | .blog-section .card {
85 | grid-column-end: span 4;
86 | }
87 |
88 | @media (min-width: 700px) {
89 | :root {
90 | --grid-columns: 12;
91 | }
92 |
93 | .blog-section .card {
94 | grid-column-end: span 6;
95 | }
96 |
97 | .blog-section .card:nth-child(3) {
98 | display: none;
99 | }
100 | }
101 |
102 | @media (min-width: 1200px) {
103 | .blog-section .card {
104 | grid-column-end: span 4;
105 | }
106 |
107 | .blog-section .card:nth-child(3) {
108 | display: unset;
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/public/styles/pages/index.css:
--------------------------------------------------------------------------------
1 | @import "blog.css";
2 | @import "explore.css";
3 | @import "home.css";
4 |
--------------------------------------------------------------------------------
/public/svg/logo-github.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/public/svg/logo-twitter.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/public/svg/mail.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/components/blog.re:
--------------------------------------------------------------------------------
1 | open Tyxml;
2 |
3 | let component = (blog: Turso.blog) =>
4 |
5 |
nav ;
21 |
--------------------------------------------------------------------------------
/src/components/hero.re:
--------------------------------------------------------------------------------
1 | open Tyxml;
2 |
3 | let component =
4 |
5 |
6 |
"computer science graduate student"
7 |
"Ethan Thoma"
8 |
9 | "I am a grad student at UBC. I focus in ML, NLP, and deep learning."
10 |
11 |
12 | "This is a collection of my thoughts, research, and code..."
13 |