├── public └── _fs │ ├── girl │ ├── .icons │ ├── favicon.ico │ └── papirus │ │ ├── system-users.svg │ │ └── terminal.svg │ └── pics │ ├── 88x31 │ ├── d14.gif │ ├── d14-barcode.png │ └── d14-dollcode.png │ ├── background.jpg │ └── portfolio │ ├── avatar.webp │ └── banner.webp ├── jsonld ├── 04-dollcode.jsonld ├── 03-resume.jsonld ├── _context.json ├── 01-88x31.jsonld ├── lib │ └── jsonld.ts ├── 02-webring.jsonld └── _render.ts ├── .gitignore ├── TODO ├── backend ├── cmd │ └── backend │ │ └── main.go ├── openapi │ └── _config.yml └── backend.go ├── .github ├── screenshot2.png └── workflows │ ├── deploy.yml.bak │ └── build.yml ├── site ├── components │ ├── Oneko │ │ ├── oneko.gif │ │ ├── sakura.gif │ │ └── oneko.ts │ ├── Papirus │ │ ├── window-minimize.svelte │ │ ├── folder-documents.svelte │ │ ├── window-maximize.svelte │ │ ├── preferences-desktop-accessibility-symbolic.svg │ │ ├── window-restore.svelte │ │ └── window-close.svelte │ ├── MaterialIcons │ │ └── open_in_new.svelte │ ├── Portfolio │ │ ├── sections │ │ │ ├── Webrings.svelte │ │ │ ├── 88x31Badges.svelte │ │ │ ├── Docs.svelte │ │ │ ├── Resume.svelte │ │ │ ├── ResumeWork.svelte │ │ │ ├── ResumeProjects.svelte │ │ │ ├── 88x31Badge.svelte │ │ │ └── About.svelte │ │ └── index.svelte │ ├── Toasts.svelte │ ├── Terminal │ │ ├── color-schemes.json │ │ └── index.svelte │ ├── WindowControl.svelte │ ├── PopoverButton.svelte │ ├── Webring.svelte │ └── Switch.svelte ├── styles │ ├── global.scss │ ├── _root-light.scss │ ├── libadwaita │ │ ├── README │ │ ├── _common.scss │ │ └── _palette.scss │ ├── _root-dark.scss │ ├── _links.scss │ ├── _cards.scss │ ├── _root.scss │ └── _buttons.scss ├── main.ts ├── index.html └── lib │ ├── prefs.ts │ ├── pronouns.ts │ ├── toasts.ts │ ├── jsonld.ts │ ├── views.ts │ └── vm.ts ├── vm ├── programs │ ├── nsfw │ │ ├── prompt.txt │ │ └── nsfw.go │ ├── termio │ │ ├── clear.go │ │ └── termsize.go │ ├── hewwo │ │ └── hewwo.go │ ├── programs.go │ ├── spew │ │ └── spew.go │ ├── coreutils │ │ ├── sleep.go │ │ ├── rm.go │ │ ├── date.go │ │ ├── cat.go │ │ └── ls.go │ ├── vars │ │ └── vars.go │ ├── webring │ │ └── webring.go │ ├── sixel │ │ └── img2sixel.go │ └── resume │ │ └── resume.json ├── fs │ ├── utilfs │ │ ├── utilfs.go │ │ ├── relocatefs_test.go │ │ └── relocatefs.go │ ├── httpfs │ │ ├── http.go │ │ ├── json_test.go │ │ ├── fs_test.go │ │ ├── json.go │ │ ├── fsimpl.go │ │ └── fs.go │ ├── rwfs │ │ ├── rwfs.go │ │ ├── rofs.go │ │ └── fsutil.go │ └── kvfs │ │ ├── memorystorage.go │ │ ├── localstorage_js.go │ │ └── fsimpl.go ├── internal │ ├── liner │ │ ├── output_js.go │ │ ├── output_linux.go │ │ ├── input_js.go │ │ ├── unixmode.go │ │ ├── COPYING │ │ ├── output.go │ │ ├── input_fallback.go │ │ ├── width.go │ │ └── input_linux.go │ ├── vmutil │ │ └── uri.go │ ├── cliprog │ │ └── cliprog.go │ ├── syncg │ │ ├── atomicvalue.go │ │ └── map.go │ ├── nsfw │ │ └── nsfw.go │ ├── ansi │ │ └── ansi.go │ └── vars │ │ └── vars.go ├── ctx.go ├── global │ ├── shellrc │ └── global.go ├── cmd │ └── vm │ │ └── main.go └── terminal.go ├── nix ├── overlays.nix ├── packages │ └── tinygo │ │ ├── 0003-Use-out-path-as-build-id-on-darwin.patch │ │ ├── 0002-Add-clang-header-path.patch │ │ └── 0001-Makefile.patch └── sources.json ├── .editorconfig ├── default.nix ├── tsconfig.json ├── scripts ├── import-mapping ├── xtermjs-colors └── jsonfs ├── go.mod ├── README.md ├── vite.config.ts ├── package.json ├── Makefile ├── gomod2nix.toml └── flake.lock /public/_fs/girl: -------------------------------------------------------------------------------- 1 | nyaa 2 | -------------------------------------------------------------------------------- /jsonld/04-dollcode.jsonld: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | result 2 | /build 3 | /node_modules 4 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | DO ME: GITHUB WORKFLOW TO REGENERATE JSON-LD 2 | -------------------------------------------------------------------------------- /backend/cmd/backend/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func main() { 4 | // TODO 5 | } 6 | -------------------------------------------------------------------------------- /.github/screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diamondburned/libdb/HEAD/.github/screenshot2.png -------------------------------------------------------------------------------- /public/_fs/.icons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diamondburned/libdb/HEAD/public/_fs/.icons/favicon.ico -------------------------------------------------------------------------------- /public/_fs/pics/88x31/d14.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diamondburned/libdb/HEAD/public/_fs/pics/88x31/d14.gif -------------------------------------------------------------------------------- /public/_fs/pics/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diamondburned/libdb/HEAD/public/_fs/pics/background.jpg -------------------------------------------------------------------------------- /site/components/Oneko/oneko.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diamondburned/libdb/HEAD/site/components/Oneko/oneko.gif -------------------------------------------------------------------------------- /site/components/Oneko/sakura.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diamondburned/libdb/HEAD/site/components/Oneko/sakura.gif -------------------------------------------------------------------------------- /public/_fs/pics/88x31/d14-barcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diamondburned/libdb/HEAD/public/_fs/pics/88x31/d14-barcode.png -------------------------------------------------------------------------------- /public/_fs/pics/88x31/d14-dollcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diamondburned/libdb/HEAD/public/_fs/pics/88x31/d14-dollcode.png -------------------------------------------------------------------------------- /public/_fs/pics/portfolio/avatar.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diamondburned/libdb/HEAD/public/_fs/pics/portfolio/avatar.webp -------------------------------------------------------------------------------- /public/_fs/pics/portfolio/banner.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diamondburned/libdb/HEAD/public/_fs/pics/portfolio/banner.webp -------------------------------------------------------------------------------- /site/styles/global.scss: -------------------------------------------------------------------------------- 1 | @use "root"; 2 | @use "buttons"; 3 | @use "links"; 4 | @use "cards"; 5 | 6 | .hidden { 7 | display: none; 8 | } 9 | -------------------------------------------------------------------------------- /site/styles/_root-light.scss: -------------------------------------------------------------------------------- 1 | @use "sass:meta"; 2 | @use "libadwaita/variables" as *; 3 | 4 | :root, 5 | [data-prefers-dark="false"] { 6 | @include theme("light"); 7 | } 8 | -------------------------------------------------------------------------------- /site/main.ts: -------------------------------------------------------------------------------- 1 | import App from "./App.svelte"; 2 | import "./styles/global.scss"; 3 | 4 | const app = new App({ 5 | target: document.body, 6 | }); 7 | 8 | export default app; 9 | -------------------------------------------------------------------------------- /vm/programs/nsfw/prompt.txt: -------------------------------------------------------------------------------- 1 | By enabling `nsfw', you are agreeing to the following: 2 | 3 | - You are at least 18 years of age. 4 | - You are not offended by sexually explicit material. 5 | 6 | -------------------------------------------------------------------------------- /jsonld/03-resume.jsonld: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "@id": "libdb:Resume", 4 | "@type": "rdfs:Class", 5 | "rdfs:comment": "A resume in JSON Resume format.", 6 | "rdfs:label": "Resume" 7 | } 8 | ] 9 | -------------------------------------------------------------------------------- /site/styles/libadwaita/README: -------------------------------------------------------------------------------- 1 | The files here are copy-pasted from GNOME/libadwaita. They may be slightly modified. 2 | They are licensed under the GNU LGPL v2.1. See: 3 | https://github.com/GNOME/libadwaita/blob/main/COPYING 4 | -------------------------------------------------------------------------------- /site/components/Papirus/window-minimize.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /backend/openapi/_config.yml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://raw.githubusercontent.com/deepmap/oapi-codegen/HEAD/configuration-schema.json 2 | package: openapi 3 | output: "" 4 | generate: 5 | models: true 6 | chi-server: true 7 | strict-server: true 8 | -------------------------------------------------------------------------------- /jsonld/_context.json: -------------------------------------------------------------------------------- 1 | { 2 | "@vocab": "https://schema.org/", 3 | "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#", 4 | "rdfs": "http://www.w3.org/2000/01/rdf-schema#", 5 | "zvava": "https://zvava.org/schema#", 6 | "libdb": "https://0xd14.id#" 7 | } 8 | -------------------------------------------------------------------------------- /site/styles/_root-dark.scss: -------------------------------------------------------------------------------- 1 | @use "sass:meta"; 2 | @use "libadwaita/variables" as *; 3 | 4 | @media (prefers-color-scheme: dark) { 5 | :root { 6 | @include theme("dark"); 7 | } 8 | } 9 | 10 | [data-prefers-dark="true"] { 11 | @include theme("dark"); 12 | } 13 | -------------------------------------------------------------------------------- /backend/backend.go: -------------------------------------------------------------------------------- 1 | // Package backend implements the backend for libdb.so. 2 | package backend 3 | 4 | import "net/http" 5 | 6 | // Params defines the parameters for the backend. 7 | type Params struct { 8 | health.HealthService 9 | } 10 | 11 | func New() http.Handler {} 12 | -------------------------------------------------------------------------------- /vm/fs/utilfs/utilfs.go: -------------------------------------------------------------------------------- 1 | package utilfs 2 | 3 | import "io/fs" 4 | 5 | // Apply applies a series of appliers to a filesystem. 6 | func Apply(fs fs.FS, appliers ...func(fs.FS) fs.FS) fs.FS { 7 | for _, apply := range appliers { 8 | fs = apply(fs) 9 | } 10 | return fs 11 | } 12 | -------------------------------------------------------------------------------- /site/styles/_links.scss: -------------------------------------------------------------------------------- 1 | a:not([role="button"]) { 2 | color: var(--adw-link-color); 3 | text-decoration: none; 4 | 5 | &:hover { 6 | text-decoration: underline; 7 | } 8 | 9 | &:not(.no-visited):visited { 10 | color: var(--adw-link-visited-color); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /nix/overlays.nix: -------------------------------------------------------------------------------- 1 | let sources = import ./sources.nix; 2 | in 3 | 4 | [ 5 | (self: super: { 6 | go = super.go_1_21; 7 | buildGoModule = super.buildGo121Module; 8 | }) 9 | (self: super: { 10 | npmlock2nix = super.callPackage sources.npmlock2nix { }; 11 | }) 12 | (import "${sources.gomod2nix}/overlay.nix") 13 | ] 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [{*.ts,*.js}] 2 | indent_style = space 3 | indent_size = 2 4 | 5 | [*.go] 6 | indent_style = tab 7 | indent_size = 4 8 | 9 | [*.nix] 10 | indent_style = tab 11 | indent_size = 2 12 | 13 | [*.json] 14 | indent_style = space 15 | indent_size = 2 16 | 17 | [*.svelte] 18 | indent_style = space 19 | indent_size = 2 20 | -------------------------------------------------------------------------------- /vm/internal/liner/output_js.go: -------------------------------------------------------------------------------- 1 | package liner 2 | 3 | const cursorColumn = false // unsure 4 | 5 | func (s *State) getColumns() bool { 6 | if s.getWinSize == nil { 7 | return false 8 | } 9 | 10 | _, col, ok := s.getWinSize() 11 | if !ok { 12 | return false 13 | } 14 | 15 | s.columns = int(col) 16 | return true 17 | } 18 | -------------------------------------------------------------------------------- /site/components/Papirus/folder-documents.svelte: -------------------------------------------------------------------------------- 1 | 8 | 12 | 13 | -------------------------------------------------------------------------------- /site/components/MaterialIcons/open_in_new.svelte: -------------------------------------------------------------------------------- 1 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | (import 2 | ( 3 | let lock = builtins.fromJSON (builtins.readFile ./flake.lock); in 4 | fetchTarball { 5 | url = lock.nodes.flake-compat.locked.url or "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz"; 6 | sha256 = lock.nodes.flake-compat.locked.narHash; 7 | } 8 | ) 9 | { src = ./.; } 10 | ).defaultNix 11 | -------------------------------------------------------------------------------- /site/styles/_cards.scss: -------------------------------------------------------------------------------- 1 | @mixin card { 2 | background-color: var(--adw-card-bg-color); 3 | color: var(--adw-card-fg-color); 4 | border-radius: var(--adw-card-radius); 5 | box-shadow: 0 0 0 1px rgba(black, 0.03), 0 1px 3px 1px rgba(black, 0.07), 6 | 0 2px 6px 2px rgba(black, 0.03); 7 | box-sizing: border-box; 8 | } 9 | 10 | .card { 11 | @include card; 12 | } 13 | -------------------------------------------------------------------------------- /site/components/Papirus/window-maximize.svelte: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /vm/internal/liner/output_linux.go: -------------------------------------------------------------------------------- 1 | package liner 2 | 3 | import ( 4 | "syscall" 5 | "unsafe" 6 | ) 7 | 8 | func (s *State) getColumns() bool { 9 | var ws winSize 10 | ok, _, _ := syscall.Syscall(syscall.SYS_IOCTL, uintptr(syscall.Stdout), 11 | syscall.TIOCGWINSZ, uintptr(unsafe.Pointer(&ws))) 12 | if int(ok) < 0 { 13 | return false 14 | } 15 | s.columns = int(ws.col) 16 | return true 17 | } 18 | -------------------------------------------------------------------------------- /site/components/Papirus/preferences-desktop-accessibility-symbolic.svg: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | -------------------------------------------------------------------------------- /site/components/Papirus/window-restore.svelte: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "checkJs": true, 5 | "forceConsistentCasingInFileNames": true, 6 | "skipLibCheck": true, 7 | "sourceMap": true, 8 | "strict": true, 9 | "module": "esnext", 10 | "target": "esnext", 11 | "moduleResolution": "node", 12 | "esModuleInterop": true, 13 | "resolveJsonModule": true, 14 | "types": ["vite/client"], 15 | "paths": { 16 | "#/libdb.so/*": ["./*"] 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /vm/programs/termio/clear.go: -------------------------------------------------------------------------------- 1 | package termio 2 | 3 | import ( 4 | "github.com/urfave/cli/v3" 5 | "libdb.so/vm" 6 | "libdb.so/vm/internal/cliprog" 7 | "libdb.so/vm/programs" 8 | ) 9 | 10 | func init() { 11 | programs.Register(cliprog.Wrap(clear)) 12 | } 13 | 14 | var clear = cli.App{ 15 | Name: "clear", 16 | Usage: "clear the terminal screen", 17 | Action: func(c *cli.Context) error { 18 | env := vm.EnvironmentFromContext(c.Context) 19 | env.Print("\033[2J\033[1;1H") 20 | return nil 21 | }, 22 | } 23 | -------------------------------------------------------------------------------- /vm/programs/hewwo/hewwo.go: -------------------------------------------------------------------------------- 1 | package hewwo 2 | 3 | import ( 4 | "context" 5 | 6 | "libdb.so/vm" 7 | "libdb.so/vm/programs" 8 | ) 9 | 10 | func init() { 11 | programs.Register(program{}) 12 | } 13 | 14 | type program struct{} 15 | 16 | func (p program) Name() string { 17 | return "hewwo" 18 | } 19 | 20 | func (p program) Run(ctx context.Context, env vm.Environment, args []string) error { 21 | if len(args) != 1 { 22 | return &vm.UsageError{Usage: "hewwo"} 23 | } 24 | env.Println("hewwo go!") 25 | return nil 26 | } 27 | -------------------------------------------------------------------------------- /scripts/import-mapping: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [[ ! "$1" ]]; then 4 | echo "Usage: $0 " >&2 5 | exit 1 6 | fi 7 | 8 | importMappingArgs=() 9 | importMappingObject="{" 10 | while read -r path; do 11 | name=$(basename "$path" .yml) 12 | importMappingObject+="\"./${name}.yml\": \"libdb.so/backend/openapi/${name}api\"," 13 | done < <(find backend/openapi -name '*.yml' -a -not -name '_*') 14 | importMappingObject+="}" 15 | importMapping=$(jq -n "$importMappingObject") 16 | 17 | yq ".import-mapping = $importMapping" "$1" 18 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml.bak: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | on: 3 | push: 4 | workflow_dispatch: 5 | repository_dispatch: 6 | jobs: 7 | build: 8 | uses: ./.github/workflows/build.yml 9 | 10 | deploy: 11 | runs-on: ubuntu-latest 12 | needs: build 13 | permissions: 14 | pages: write 15 | id-token: write 16 | environment: 17 | name: github-pages 18 | url: ${{ steps.deploy.outputs.page_url }} 19 | steps: 20 | - name: Deploy to GitHub Pages 21 | id: deploy 22 | uses: actions/deploy-pages@v1 23 | -------------------------------------------------------------------------------- /vm/programs/termio/termsize.go: -------------------------------------------------------------------------------- 1 | package termio 2 | 3 | import ( 4 | "github.com/urfave/cli/v3" 5 | "libdb.so/vm" 6 | "libdb.so/vm/internal/cliprog" 7 | "libdb.so/vm/programs" 8 | ) 9 | 10 | func init() { 11 | programs.Register(cliprog.Wrap(termsize)) 12 | } 13 | 14 | var termsize = cli.App{ 15 | Name: "termsize", 16 | Usage: "print the terminal size", 17 | Action: func(c *cli.Context) error { 18 | env := vm.EnvironmentFromContext(c.Context) 19 | q := env.Terminal.Query() 20 | env.Printf("%d %d", q.Width, q.Height) 21 | return nil 22 | }, 23 | } 24 | -------------------------------------------------------------------------------- /vm/programs/programs.go: -------------------------------------------------------------------------------- 1 | package programs 2 | 3 | import "libdb.so/vm" 4 | 5 | var programs = map[string]vm.Program{} 6 | 7 | // Register registers the given program to the global program registry. 8 | func Register(program vm.Program) { 9 | if program == nil { 10 | return 11 | } 12 | 13 | if _, ok := programs[program.Name()]; ok { 14 | panic("program already registered: " + program.Name()) 15 | } 16 | 17 | programs[program.Name()] = program 18 | } 19 | 20 | // All returns all registered programs. 21 | func All() map[string]vm.Program { 22 | return programs 23 | } 24 | -------------------------------------------------------------------------------- /public/_fs/.icons/papirus/system-users.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /public/_fs/.icons/papirus/terminal.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /vm/ctx.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "context" 5 | "log" 6 | ) 7 | 8 | type ctxKey uint 9 | 10 | const ( 11 | environmentKey ctxKey = iota 12 | loggerKey ctxKey = iota 13 | ) 14 | 15 | // EnvironmentFromContext returns the console environment from the context. 16 | func EnvironmentFromContext(ctx context.Context) Environment { 17 | return *ctx.Value(environmentKey).(*Environment) 18 | } 19 | 20 | // LoggerFromContext returns the logger from the context. 21 | func LoggerFromContext(ctx context.Context) *log.Logger { 22 | return ctx.Value(loggerKey).(*log.Logger) 23 | } 24 | -------------------------------------------------------------------------------- /vm/internal/vmutil/uri.go: -------------------------------------------------------------------------------- 1 | package vmutil 2 | 3 | import "net/url" 4 | 5 | // MakeURI formats a URI from the given scheme, opaque, and values. 6 | func MakeURI(scheme, opaque string, values url.Values) string { 7 | u := url.URL{ 8 | Scheme: scheme, 9 | Opaque: opaque, 10 | RawQuery: values.Encode(), 11 | } 12 | return u.String() 13 | } 14 | 15 | // MakeTerminalWriteURI formats a URI that, when clicked in a terminal, will 16 | // paste the given data. 17 | func MakeTerminalWriteURI(data string) string { 18 | return MakeURI("terminal", "write", url.Values{"data": {data}}) 19 | } 20 | -------------------------------------------------------------------------------- /site/styles/_root.scss: -------------------------------------------------------------------------------- 1 | @use "sass:meta"; 2 | @use "root-light"; 3 | @use "root-dark"; 4 | @use "libadwaita/common" as common; 5 | 6 | :root { 7 | @each $key, $value in meta.module-variables("common") { 8 | --adw-#{$key}: #{$value}; 9 | } 10 | 11 | --blue: rgba(85, 205, 252, 1); 12 | --pink: rgba(247, 168, 184, 1); 13 | 14 | --blue-rgb: 85, 205, 252; 15 | --pink-rgb: 247, 168, 184; 16 | 17 | --glow-radius: 0.35em; 18 | --glow-alpha: 0.65; 19 | --pink-glow: 0 0 var(--glow-radius) rgba(var(--pink-rgb), var(--glow-alpha)); 20 | --blue-glow: 0 0 var(--glow-radius) rgba(var(--blue-rgb), var(--glow-alpha)); 21 | } 22 | -------------------------------------------------------------------------------- /site/components/Portfolio/sections/Webrings.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 |
10 |

Webrings

11 |
12 | {#each doc.self["libdb:webring"] as webring} 13 | {#await usingContext(doc, webring, "https://0xd14.id#Webring/") then webring} 14 | 15 | {/await} 16 | {/each} 17 |
18 |
19 | -------------------------------------------------------------------------------- /site/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | diamondburned 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /vm/internal/liner/input_js.go: -------------------------------------------------------------------------------- 1 | package liner 2 | 3 | type termios = noopMode 4 | 5 | func TerminalSupported() bool { 6 | return true // no stdin on js anyway 7 | } 8 | 9 | type noopMode struct{} 10 | 11 | func (n noopMode) ApplyMode() error { 12 | return nil 13 | } 14 | 15 | // TerminalMode returns a noop InputModeSetter on this platform. 16 | func TerminalMode() (ModeApplier, error) { 17 | return noopMode{}, nil 18 | } 19 | 20 | func initLinerTerminal(s *State) { 21 | s.inputRedirected = false 22 | s.outputRedirected = false 23 | } 24 | 25 | func (s *State) supportedStartPrompt() {} 26 | 27 | func (s *State) supportedStopPrompt() {} 28 | 29 | func (s *State) close() {} 30 | -------------------------------------------------------------------------------- /site/components/Papirus/window-close.svelte: -------------------------------------------------------------------------------- 1 | 8 | 12 | 16 | 17 | -------------------------------------------------------------------------------- /scripts/xtermjs-colors: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | scheme=( 3 | "#272224" "#FF473D" "#3DCCB2" "#FF9600" 4 | "#3B7ECB" "#F74C6D" "#00B5FC" "#3E3E3E" 5 | "#52494C" "#FF6961" "#85E6D4" "#FFB347" 6 | "#779ECB" "#F7A8B8" "#55CDFC" "#EEEEEC" 7 | "#1D1D1D" 8 | "#FFFFFF" 9 | ) 10 | 11 | mapping=( 12 | black red green yellow 13 | blue magenta cyan white 14 | brightBlack brightRed brightGreen brightYellow 15 | brightBlue brightMagenta brightCyan brightWhite 16 | background 17 | foreground 18 | ) 19 | 20 | jqArgs=() 21 | jqKeys=() 22 | 23 | for ((i = 0; i < ${#scheme[@]}; i++)); do 24 | k=${mapping[$i]} 25 | v=${scheme[$i]} 26 | jqArgs+=( --arg "$k" "$v" ) 27 | jqKeys+=( "${k}" ) 28 | done 29 | 30 | jq --null-input "${jqArgs[@]}" "{ $(printf "$%s," "${jqKeys[@]}") }" 31 | -------------------------------------------------------------------------------- /site/styles/libadwaita/_common.scss: -------------------------------------------------------------------------------- 1 | $ease-out-quad: cubic-bezier(0.25, 0.46, 0.45, 0.94); 2 | $backdrop-transition: 200ms ease-out; 3 | $focus-transition: outline-color 200ms $ease-out-quad, 4 | outline-width 200ms $ease-out-quad, outline-offset 200ms $ease-out-quad; 5 | $button-transition: background 200ms $ease-out-quad, 6 | box-shadow 200ms $ease-out-quad; 7 | $button-radius: 6px; 8 | $card-radius: $button-radius + 6; 9 | $menu-radius: 6px; 10 | $menu-margin: 6px; //margin around menuitems & sidebar items 11 | $menu-padding: 12px; //inner menuitem padding 12 | $popover-radius: $button-radius + 6; 13 | $dialog-radius: $button-radius + 6; 14 | $window-radius: $button-radius + 6; 15 | $normal-icon-size: 16px; 16 | $large-icon-size: 32px; 17 | $card-padding: 8px 12px; 18 | -------------------------------------------------------------------------------- /vm/fs/httpfs/http.go: -------------------------------------------------------------------------------- 1 | package httpfs 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "net/http" 7 | "net/url" 8 | "path" 9 | "time" 10 | ) 11 | 12 | type httpClient struct { 13 | client *http.Client 14 | url *url.URL 15 | } 16 | 17 | // Timeout is the default timeout for http requests. 18 | const Timeout = 8 * time.Second 19 | 20 | func (c *httpClient) get(filepath string, _ FileInfo) (io.ReadCloser, error) { 21 | u := *c.url 22 | u.Path = path.Join(u.Path, filepath) 23 | 24 | ctx, cancel := context.WithTimeout(context.Background(), Timeout) 25 | defer cancel() 26 | 27 | req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil) 28 | if err != nil { 29 | return nil, err 30 | } 31 | 32 | resp, err := c.client.Do(req) 33 | if err != nil { 34 | return nil, err 35 | } 36 | 37 | return resp.Body, nil 38 | } 39 | -------------------------------------------------------------------------------- /vm/global/shellrc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | bold() { printf '\e[1m%s\e[0m' "$1"; } 3 | dark() { printf '\e[3%dm%s\e[0m' "$1" "$2"; } 4 | light() { printf '\e[9%dm%s\e[0m' "$1" "$2"; } 5 | faint() { printf '\e[2m%s\e[0m' "$1"; } 6 | hidden() { printf '\e[8m%s\e[0m' "$1"; } 7 | 8 | clear 9 | neofetch 10 | echo 11 | echo -e "Welcome to $(light 5 "$(bold "libdb.so")") $(light 5 "<3")" 12 | echo -e "Type $(bold "help") for a list of commands, also try $(bold "cd"), $(bold "ls"), $(bold "cat"), $(bold "resume")" 13 | 14 | sizes=( $(termsize) ) 15 | width=${sizes[0]} 16 | if (( width > 80 )); then 17 | width=80 18 | fi 19 | 20 | if [ $(nsfw get) == true ]; then 21 | echo 22 | echo -e "hey!! just a heads up, you have \e[1mnsfw\e[0m mode enabled!" 23 | fi 24 | 25 | echo 26 | echo "The following variables are available to you:" 27 | echo 28 | vars 29 | echo 30 | -------------------------------------------------------------------------------- /vm/programs/spew/spew.go: -------------------------------------------------------------------------------- 1 | package spew 2 | 3 | import ( 4 | "io/fs" 5 | "path" 6 | 7 | "github.com/davecgh/go-spew/spew" 8 | "github.com/urfave/cli/v3" 9 | "libdb.so/vm" 10 | "libdb.so/vm/internal/cliprog" 11 | "libdb.so/vm/programs" 12 | ) 13 | 14 | func init() { 15 | programs.Register(cliprog.Wrap(app)) 16 | } 17 | 18 | var app = cli.App{ 19 | Name: "spew", 20 | Usage: "spew file(s)", 21 | Action: func(c *cli.Context) error { 22 | env := vm.EnvironmentFromContext(c.Context) 23 | log := vm.LoggerFromContext(c.Context) 24 | 25 | for _, arg := range c.Args().Slice() { 26 | path := path.Join(env.Cwd, arg) 27 | 28 | f, err := fs.ReadFile(env.Filesystem, path) 29 | if err != nil { 30 | log.Println("cat:", err) 31 | continue 32 | } 33 | 34 | spew.Fdump(env.Terminal.Stdout, f) 35 | } 36 | 37 | return nil 38 | }, 39 | } 40 | -------------------------------------------------------------------------------- /vm/cmd/vm/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "os" 7 | 8 | "github.com/fatih/color" 9 | "libdb.so/vm" 10 | "libdb.so/vm/programs" 11 | 12 | _ "libdb.so/vm/programs/hewwo" 13 | ) 14 | 15 | var colors = []color.Attribute{ 16 | color.FgRed, 17 | color.FgGreen, 18 | color.FgYellow, 19 | color.FgBlue, 20 | color.FgMagenta, 21 | color.FgCyan, 22 | color.FgWhite, 23 | } 24 | 25 | func main() { 26 | io := vm.IO{ 27 | Stdin: os.Stdin, 28 | Stdout: os.Stdout, 29 | Stderr: os.Stderr, 30 | } 31 | 32 | inst, err := vm.NewInterpreter(io, vm.InterpreterOpts{ 33 | MakeRaw: false, // maybe? 34 | Programs: programs.All(), 35 | }) 36 | if err != nil { 37 | log.Fatalln("failed to create console instance:", err) 38 | } 39 | 40 | if err := inst.Run(context.Background()); err != nil { 41 | log.Fatalln("failed to run console instance:", err) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /vm/internal/cliprog/cliprog.go: -------------------------------------------------------------------------------- 1 | package cliprog 2 | 3 | import ( 4 | "context" 5 | "log" 6 | 7 | "github.com/urfave/cli/v3" 8 | "libdb.so/vm" 9 | ) 10 | 11 | // Wrap wraps a cli.App into a vm.Program. 12 | func Wrap(app cli.App) vm.Program { 13 | app.UseShortOptionHandling = true 14 | app.Setup() 15 | if app.Name == "" { 16 | log.Println("cli: not registering app with empty name") 17 | return nil 18 | } 19 | return program{app} 20 | } 21 | 22 | type program struct { 23 | cli.App 24 | } 25 | 26 | func (p program) Name() string { 27 | return p.App.Name 28 | } 29 | 30 | func (p program) Usage() string { 31 | return p.App.Usage 32 | } 33 | 34 | func (p program) Run(ctx context.Context, env vm.Environment, args []string) error { 35 | p.Reader = env.Terminal.Stdin 36 | p.Writer = env.Terminal.Stdout 37 | p.ErrWriter = env.Terminal.Stderr 38 | p.ExitErrHandler = func(*cli.Context, error) {} 39 | return p.App.RunContext(ctx, args) 40 | } 41 | -------------------------------------------------------------------------------- /vm/internal/liner/unixmode.go: -------------------------------------------------------------------------------- 1 | //go:build linux 2 | // +build linux 3 | 4 | package liner 5 | 6 | import ( 7 | "syscall" 8 | "unsafe" 9 | ) 10 | 11 | func (mode *termios) ApplyMode() error { 12 | _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(syscall.Stdin), setTermios, uintptr(unsafe.Pointer(mode))) 13 | 14 | if errno != 0 { 15 | return errno 16 | } 17 | return nil 18 | } 19 | 20 | // TerminalMode returns the current terminal input mode as an InputModeSetter. 21 | // 22 | // This function is provided for convenience, and should 23 | // not be necessary for most users of liner. 24 | func TerminalMode() (ModeApplier, error) { 25 | return getMode(syscall.Stdin) 26 | } 27 | 28 | func getMode(handle int) (*termios, error) { 29 | var mode termios 30 | var err error 31 | _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(handle), getTermios, uintptr(unsafe.Pointer(&mode))) 32 | if errno != 0 { 33 | err = errno 34 | } 35 | 36 | return &mode, err 37 | } 38 | -------------------------------------------------------------------------------- /vm/internal/syncg/atomicvalue.go: -------------------------------------------------------------------------------- 1 | package syncg 2 | 3 | import "sync/atomic" 4 | 5 | // AtomicValue is a generic wrapper around atomic.Value. 6 | type AtomicValue[T any] atomic.Value 7 | 8 | // Load returns the value stored in the AtomicValue. 9 | func (v *AtomicValue[T]) Load() (T, bool) { 10 | val, ok := (*atomic.Value)(v).Load().(T) 11 | if ok { 12 | return val, true 13 | } 14 | var z T 15 | return z, false 16 | } 17 | 18 | // Store stores the given value in the AtomicValue. 19 | func (v *AtomicValue[T]) Store(val T) { 20 | (*atomic.Value)(v).Store(val) 21 | } 22 | 23 | // Swap swaps the value stored in the AtomicValue with the given value and 24 | // returns the old value. 25 | func (v *AtomicValue[T]) Swap(new T) T { 26 | return (*atomic.Value)(v).Swap(new).(T) 27 | } 28 | 29 | // CompareAndSwap compares the value stored in the AtomicValue with the given 30 | // value and, if they are equal, stores the new value and returns true. If they 31 | // are not equal, it returns false. 32 | func (v *AtomicValue[T]) CompareAndSwap(old, new T) bool { 33 | return (*atomic.Value)(v).CompareAndSwap(old, new) 34 | } 35 | -------------------------------------------------------------------------------- /site/lib/prefs.ts: -------------------------------------------------------------------------------- 1 | import { persisted } from "svelte-persisted-store"; 2 | import { derived, get, writable } from "svelte/store"; 3 | 4 | const isReducedMotion = !!window.matchMedia(`(prefers-reduced-motion: reduce)`)?.matches; 5 | 6 | export const onekoCursor = persisted("oneko-cursor", !isReducedMotion); 7 | export const dragWindows = persisted("drag-windows", true); 8 | export const nsfw = persisted("nsfw-v1", false); 9 | export const theme = persisted("theme", "Adwaita"); 10 | export const prefersDark = persisted("prefers-dark", null); 11 | 12 | for (const store of [onekoCursor, dragWindows]) { 13 | store.set(get(store)); 14 | } 15 | 16 | const systemIsDark = writable(false); 17 | if (window.matchMedia) { 18 | const prefersDark = window.matchMedia("(prefers-color-scheme: dark)"); 19 | prefersDark.addEventListener("change", (event) => systemIsDark.set(event.matches)); 20 | systemIsDark.set(prefersDark.matches); 21 | } 22 | 23 | export const isDark = derived([prefersDark, systemIsDark], ([prefersDark, systemIsDark]) => { 24 | return prefersDark !== null ? prefersDark : systemIsDark; 25 | }); 26 | -------------------------------------------------------------------------------- /vm/programs/coreutils/sleep.go: -------------------------------------------------------------------------------- 1 | package coreutils 2 | 3 | import ( 4 | "math" 5 | "strconv" 6 | "time" 7 | 8 | "github.com/pkg/errors" 9 | "github.com/urfave/cli/v3" 10 | "libdb.so/vm" 11 | "libdb.so/vm/internal/cliprog" 12 | "libdb.so/vm/programs" 13 | ) 14 | 15 | func init() { 16 | programs.Register(cliprog.Wrap(sleep)) 17 | } 18 | 19 | var sleep = cli.App{ 20 | Name: "sleep", 21 | Usage: "sleep for NUMBER seconds or a given duration", 22 | UsageText: `sleep NUMBER[SUFFIX]`, 23 | Action: func(c *cli.Context) error { 24 | if c.NArg() != 1 { 25 | return &vm.UsageError{Usage: "sleep NUMBER[SUFFIX]"} 26 | } 27 | 28 | d, err := time.ParseDuration(c.Args().First()) 29 | if err != nil { 30 | i, ierr := strconv.ParseFloat(c.Args().First(), 64) 31 | if ierr != nil { 32 | return errors.Wrap(err, "invalid duration or number") 33 | } 34 | d = time.Duration(math.Ceil(i * float64(time.Second))) 35 | } 36 | 37 | timer := time.NewTimer(d) 38 | defer timer.Stop() 39 | 40 | select { 41 | case <-timer.C: 42 | return nil 43 | case <-c.Context.Done(): 44 | return nil 45 | } 46 | }, 47 | } 48 | -------------------------------------------------------------------------------- /vm/internal/liner/COPYING: -------------------------------------------------------------------------------- 1 | Copyright © 2012 Peter Harris 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation 6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the 8 | Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice (including the next 11 | paragraph) shall be included in all copies or substantial portions of the 12 | Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 17 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | DEALINGS IN THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- /vm/fs/rwfs/rwfs.go: -------------------------------------------------------------------------------- 1 | package rwfs 2 | 3 | import ( 4 | "io" 5 | "io/fs" 6 | ) 7 | 8 | // FS implements a read-writable filesystem. 9 | type FS interface { 10 | fs.FS 11 | // OpenFile opens the named file with specified flag. The returned file 12 | // may be read or written depending on the flag. 13 | OpenFile(name string, flag int, perm fs.FileMode) (File, error) 14 | // Remove removes the named file or (empty) directory. 15 | Remove(name string) error 16 | // Mkdir creates a new directory with the specified name and permission 17 | // bits. 18 | Mkdir(name string, perm fs.FileMode) error 19 | // MkdirAll creates a directory named path, along with any necessary 20 | // parents, and returns nil, or else returns an error. 21 | MkdirAll(name string, perm fs.FileMode) error 22 | // RemoveAll removes path and any children it contains. It removes 23 | // everything it can and returns errors using errors.Join. 24 | RemoveAll(name string) error 25 | } 26 | 27 | // File is a read-writable file. 28 | type File interface { 29 | fs.File 30 | io.ReadWriteCloser 31 | } 32 | 33 | // Type aliases. 34 | type ( 35 | FileInfo = fs.FileInfo 36 | DirEntry = fs.DirEntry 37 | FileMode = fs.FileMode 38 | ) 39 | -------------------------------------------------------------------------------- /vm/internal/nsfw/nsfw.go: -------------------------------------------------------------------------------- 1 | //go:build js 2 | 3 | package nsfw 4 | 5 | import ( 6 | "fmt" 7 | "io/fs" 8 | 9 | "libdb.so/vm/internal/vars" 10 | "libdb.so/vm/fs/rwfs" 11 | ) 12 | 13 | var Variable = vars. 14 | New[bool]("nsfw-v1"). 15 | WithDefault(false). 16 | WithHidden(true) 17 | 18 | func IsEnabled() bool { 19 | return Variable.Getz() 20 | } 21 | 22 | func Enable() { 23 | Variable.Set(true) 24 | } 25 | 26 | func Disable() { 27 | Variable.Set(false) 28 | } 29 | 30 | func WrapFS(rofs fs.FS) fs.FS { 31 | return nsfwFS{ro: rofs} 32 | } 33 | 34 | type nsfwFS struct { 35 | ro fs.FS 36 | } 37 | 38 | var errDenied = fmt.Errorf("%w (see `help')", fs.ErrPermission) 39 | 40 | func (f nsfwFS) Open(path string) (fs.File, error) { 41 | if pathHasNSFW(path) && !IsEnabled() { 42 | return nil, errDenied 43 | } 44 | return f.ro.Open(path) 45 | } 46 | 47 | func (f nsfwFS) Stat(path string) (fs.FileInfo, error) { 48 | if pathHasNSFW(path) && !IsEnabled() { 49 | return nil, errDenied 50 | } 51 | return fs.Stat(f.ro, path) 52 | } 53 | 54 | func pathHasNSFW(path string) bool { 55 | for _, part := range rwfs.Split(path) { 56 | if part == ".nsfw" { 57 | return true 58 | } 59 | } 60 | 61 | return false 62 | } 63 | -------------------------------------------------------------------------------- /site/components/Toasts.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | {#each $toasts as toast} 14 |
18 | {toast.text} 19 | toasts.remove(toast)}> 20 | 21 | 22 |
23 | {/each} 24 | 25 | 46 | -------------------------------------------------------------------------------- /site/components/Portfolio/sections/88x31Badges.svelte: -------------------------------------------------------------------------------- 1 | 18 | 19 | {#await combined88x31s(doc) then badges} 20 |
21 | {#each badges as badge} 22 | 23 | {/each} 24 |
25 | {/await} 26 | 27 | 41 | -------------------------------------------------------------------------------- /site/components/Terminal/color-schemes.json: -------------------------------------------------------------------------------- 1 | { 2 | "light": { 3 | "black": "#3E3E3E", 4 | "red": "#FF473D", 5 | "green": "#3DCCB2", 6 | "yellow": "#FF9600", 7 | "blue": "#3B7ECB", 8 | "magenta": "#F74C6D", 9 | "cyan": "#00B5FC", 10 | "white": "#141414", 11 | "brightBlack": "#EEEEEC", 12 | "brightRed": "#FF6961", 13 | "brightGreen": "#85E6D4", 14 | "brightYellow": "#FFB347", 15 | "brightBlue": "#779ECB", 16 | "brightMagenta": "#F7A8B8", 17 | "brightCyan": "#55CDFC", 18 | "brightWhite": "#52494C", 19 | "background": "#fcfcfc", 20 | "foreground": "#1D1D1D", 21 | "cursor": "#0f0f0f" 22 | }, 23 | "dark": { 24 | "black": "#141414", 25 | "red": "#FF473D", 26 | "green": "#3DCCB2", 27 | "yellow": "#FF9600", 28 | "blue": "#3B7ECB", 29 | "magenta": "#F74C6D", 30 | "cyan": "#00B5FC", 31 | "white": "#3E3E3E", 32 | "brightBlack": "#52494C", 33 | "brightRed": "#FF6961", 34 | "brightGreen": "#85E6D4", 35 | "brightYellow": "#FFB347", 36 | "brightBlue": "#779ECB", 37 | "brightMagenta": "#F7A8B8", 38 | "brightCyan": "#55CDFC", 39 | "brightWhite": "#EEEEEC", 40 | "background": "#1D1D1D", 41 | "foreground": "#fcfcfc", 42 | "cursor": "#fcfcfc" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /site/components/WindowControl.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 | 13 | 14 | 56 | -------------------------------------------------------------------------------- /site/lib/pronouns.ts: -------------------------------------------------------------------------------- 1 | import { nsfw } from "#/libdb.so/site/lib/prefs.js"; 2 | import { derived } from "svelte/store"; 3 | 4 | export const pronouns = derived(nsfw, (nsfw) => (!nsfw ? "she/her/hers" : "it/its/its")); 5 | 6 | export const selectPronouns = (content: string, option: string) => { 7 | const pronoun = content.toLowerCase(); 8 | const replace = { 9 | "it/its/its": { 10 | it2: "it", 11 | its3: "its", 12 | she: "it", 13 | her: "its", 14 | hers: "its", 15 | herself: "itself", 16 | "she's": "it's", 17 | "she/her": "it/its", 18 | }[pronoun], 19 | "she/her/hers": { 20 | it: "she", 21 | it2: "her", 22 | its: "her", 23 | its3: "hers", 24 | itself: "herself", 25 | "it's": "she's", 26 | "it/its": "she/her", 27 | }[pronoun], 28 | }[option]; 29 | return replace ? preserveCase(content, replace) : content; 30 | }; 31 | 32 | export function preserveCase(original: string, replace: string): string { 33 | // First-letter capitalization. 34 | if (original[0].toUpperCase() == original[0]) { 35 | return replace[0].toUpperCase() + replace.slice(1); 36 | } 37 | // All caps. 38 | if (original == original.toUpperCase()) { 39 | return replace.toUpperCase(); 40 | } 41 | // Lowercase. 42 | return replace; 43 | } 44 | -------------------------------------------------------------------------------- /site/components/PopoverButton.svelte: -------------------------------------------------------------------------------- 1 | 17 | 18 |
19 | 20 | 31 | 32 | 33 | 34 | 35 | 36 |
37 | 38 | 54 | -------------------------------------------------------------------------------- /vm/programs/coreutils/rm.go: -------------------------------------------------------------------------------- 1 | package coreutils 2 | 3 | import ( 4 | "errors" 5 | "path" 6 | 7 | "github.com/urfave/cli/v3" 8 | "libdb.so/vm" 9 | "libdb.so/vm/internal/cliprog" 10 | "libdb.so/vm/programs" 11 | ) 12 | 13 | func init() { 14 | programs.Register(cliprog.Wrap(rm)) 15 | } 16 | 17 | var rm = cli.App{ 18 | Name: "rm", 19 | Usage: "remove files or directories", 20 | UsageText: `rm [OPTION]... FILE...`, 21 | Flags: []cli.Flag{ 22 | &cli.BoolFlag{ 23 | Name: "recursive", 24 | Aliases: []string{"r"}, 25 | Usage: "remove directories and their contents recursively", 26 | }, 27 | &cli.BoolFlag{ 28 | Name: "force", 29 | Aliases: []string{"f"}, 30 | Usage: "no-op, for compatibility only", 31 | }, 32 | }, 33 | Action: func(c *cli.Context) error { 34 | env := vm.EnvironmentFromContext(c.Context) 35 | log := vm.LoggerFromContext(c.Context) 36 | 37 | rm := env.Filesystem.Remove 38 | if c.Bool("recursive") { 39 | rm = env.Filesystem.RemoveAll 40 | } 41 | 42 | var failed bool 43 | for _, arg := range c.Args().Slice() { 44 | path := path.Join(env.Cwd, arg) 45 | 46 | if err := rm(path); err != nil { 47 | log.Println("rm:", err) 48 | failed = true 49 | } 50 | } 51 | 52 | if failed { 53 | return errors.New("failed to remove one or more files") 54 | } 55 | 56 | return nil 57 | }, 58 | } 59 | -------------------------------------------------------------------------------- /site/lib/toasts.ts: -------------------------------------------------------------------------------- 1 | import * as store from "svelte/store"; 2 | 3 | export type Toast = { 4 | text: string; 5 | class?: string; 6 | dismissed?: () => void; 7 | 8 | _timeout?: number; 9 | }; 10 | 11 | export class ToastStore { 12 | readonly store = store.writable([]); 13 | 14 | constructor() { 15 | console.log(this); 16 | } 17 | 18 | add(toast: Toast, timeout = 0) { 19 | this.store.update((toasts) => { 20 | toasts.push(toast); 21 | if (timeout > 0) { 22 | toast._timeout = window.setTimeout(() => this.remove(toast), timeout); 23 | } 24 | return toasts; 25 | }); 26 | } 27 | 28 | remove(toast: Toast) { 29 | let dismissed = false; 30 | 31 | this.store.update((toasts) => { 32 | const toastIx = toasts.indexOf(toast); 33 | if (toastIx == -1) { 34 | return toasts; 35 | } 36 | 37 | toasts.splice(toastIx, 1); 38 | dismissed = true; 39 | return toasts; 40 | }); 41 | 42 | if (dismissed) { 43 | if (toast._timeout) { 44 | window.clearTimeout(toast._timeout); 45 | } 46 | if (toast.dismissed) { 47 | toast.dismissed(); 48 | } 49 | } 50 | } 51 | 52 | subscribe( 53 | run: store.Subscriber, 54 | invalidate?: (value?: Toast[]) => void 55 | ): store.Unsubscriber { 56 | return this.store.subscribe(run, invalidate); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /scripts/jsonfs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | scanDir() { 5 | declare -A objs 6 | for f in "$1"/*; do 7 | n=$(basename "$f") 8 | if [[ -d "$f" ]]; then 9 | obj=$(scanDir "$f") 10 | objs["${n}/"]="$obj" 11 | elif [[ -f "$f" ]]; then 12 | obj=$(jq --null-input \ 13 | --argjson size "$(stat -c%s "$f")" \ 14 | '{ $size }') 15 | objs["$n"]="$obj" 16 | else 17 | echo "Skipping $f since it's neither a file nor a directory" >&2 18 | fi 19 | done 20 | jsonobj objs 21 | } 22 | 23 | basename() { 24 | echo "${1##*/}" 25 | } 26 | 27 | # jsonobj varname 28 | jsonobj() { 29 | declare -n obj=$1 30 | if [[ "${!obj[@]}" == "" ]]; then 31 | echo "{}" 32 | return 33 | fi 34 | 35 | args=() 36 | keys=() 37 | 38 | i=0 39 | for k in "${!obj[@]}"; do 40 | v="${obj[$k]}" 41 | args+=(--argjson "v$i" "$v") 42 | keys+=("\"$k\": \$v$i") 43 | i=$((i + 1)) 44 | done 45 | 46 | jq --null-input "${args[@]}" "{ $(printf "%s, " "${keys[@]}") }" 47 | } 48 | 49 | main() { 50 | if [[ $# -ne 1 ]]; then 51 | echo "Usage: jsonfs " 52 | exit 1 53 | fi 54 | 55 | dir="$1" 56 | if [[ "$dir" == "" ]]; then 57 | dir="." 58 | fi 59 | 60 | shopt -s dotglob 61 | tree=$(scanDir "$dir") 62 | baseURL="${BASE_URL:-}${dir}" 63 | 64 | jq --null-input \ 65 | --arg base_url "$baseURL" \ 66 | --argjson tree "$tree" \ 67 | '{ "$version": 2, $base_url, $tree }' 68 | } 69 | 70 | main "$@" 71 | -------------------------------------------------------------------------------- /vm/internal/liner/output.go: -------------------------------------------------------------------------------- 1 | //go:build linux || js 2 | // +build linux js 3 | 4 | package liner 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | "strings" 10 | ) 11 | 12 | func (s *State) cursorPos(x int) { 13 | if s.useCHA { 14 | // 'G' is "Cursor Character Absolute (CHA)" 15 | fmt.Printf("\x1b[%dG", x+1) 16 | } else { 17 | // 'C' is "Cursor Forward (CUF)" 18 | fmt.Print("\r") 19 | if x > 0 { 20 | fmt.Printf("\x1b[%dC", x) 21 | } 22 | } 23 | } 24 | 25 | func (s *State) eraseLine() { 26 | fmt.Print("\x1b[0K") 27 | } 28 | 29 | func (s *State) eraseScreen() { 30 | fmt.Print("\x1b[H\x1b[2J") 31 | } 32 | 33 | func (s *State) moveUp(lines int) { 34 | fmt.Printf("\x1b[%dA", lines) 35 | } 36 | 37 | func (s *State) moveDown(lines int) { 38 | fmt.Printf("\x1b[%dB", lines) 39 | } 40 | 41 | func (s *State) emitNewLine() { 42 | fmt.Print("\n") 43 | } 44 | 45 | type winSize struct { 46 | row, col uint16 47 | xpixel, ypixel uint16 48 | } 49 | 50 | func (s *State) checkOutput() { 51 | // xterm is known to support CHA 52 | if strings.Contains(strings.ToLower(os.Getenv("TERM")), "xterm") { 53 | s.useCHA = true 54 | return 55 | } 56 | 57 | // The test for functional ANSI CHA is unreliable (eg the Windows 58 | // telnet command does not support reading the cursor position with 59 | // an ANSI DSR request, despite setting TERM=ansi) 60 | 61 | // Assume CHA isn't supported (which should be safe, although it 62 | // does result in occasional visible cursor jitter) 63 | s.useCHA = false 64 | } 65 | -------------------------------------------------------------------------------- /vm/internal/liner/input_fallback.go: -------------------------------------------------------------------------------- 1 | //go:build !linux && !js 2 | // +build !linux,!js 3 | 4 | package liner 5 | 6 | import ( 7 | "bufio" 8 | "errors" 9 | "os" 10 | ) 11 | 12 | // State represents an open terminal 13 | type State struct { 14 | commonState 15 | } 16 | 17 | // Prompt displays p, and then waits for user input. Prompt does not support 18 | // line editing on this operating system. 19 | func (s *State) Prompt(p string) (string, error) { 20 | return s.promptUnsupported(p) 21 | } 22 | 23 | // PasswordPrompt is not supported in this OS. 24 | func (s *State) PasswordPrompt(p string) (string, error) { 25 | return "", errors.New("liner: function not supported in this terminal") 26 | } 27 | 28 | // NewLiner initializes a new *State 29 | // 30 | // Note that this operating system uses a fallback mode without line 31 | // editing. Patches welcome. 32 | func NewLiner() *State { 33 | var s State 34 | s.r = bufio.NewReader(os.Stdin) 35 | return &s 36 | } 37 | 38 | // Close returns the terminal to its previous mode 39 | func (s *State) Close() error { 40 | return nil 41 | } 42 | 43 | // TerminalSupported returns false because line editing is not 44 | // supported on this platform. 45 | func TerminalSupported() bool { 46 | return false 47 | } 48 | 49 | type noopMode struct{} 50 | 51 | func (n noopMode) ApplyMode() error { 52 | return nil 53 | } 54 | 55 | // TerminalMode returns a noop InputModeSetter on this platform. 56 | func TerminalMode() (ModeApplier, error) { 57 | return noopMode{}, nil 58 | } 59 | 60 | const cursorColumn = true 61 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module libdb.so 2 | 3 | go 1.22.0 4 | 5 | require ( 6 | github.com/alecthomas/assert/v2 v2.4.0 7 | github.com/davecgh/go-spew v1.1.1 8 | github.com/fatih/color v1.15.0 9 | github.com/go-chi/chi/v5 v5.1.0 10 | github.com/leaanthony/go-ansi-parser v1.6.0 11 | github.com/lucasb-eyer/go-colorful v1.0.2 12 | github.com/mattn/go-runewidth v0.0.3 13 | github.com/mattn/go-sixel v0.0.5 14 | github.com/oapi-codegen/runtime v1.1.1 15 | github.com/pkg/errors v0.9.1 16 | github.com/urfave/cli/v3 v3.0.0-alpha2 17 | gitlab.com/diamondburned/dotfiles/Scripts/lineprompt v0.0.0-20230407082541-a6924ecdc0d4 18 | golang.org/x/crypto v0.17.0 19 | golang.org/x/image v0.6.0 20 | libdb.so/go-mommy v0.1.1 21 | libdb.so/libwebring-go v0.0.0-20230521133149-d80b3d3c5163 22 | mvdan.cc/sh/v3 v3.8.0 23 | ) 24 | 25 | require ( 26 | github.com/alecthomas/repr v0.3.0 // indirect 27 | github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect 28 | github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect 29 | github.com/google/uuid v1.5.0 // indirect 30 | github.com/hexops/gotextdiff v1.0.3 // indirect 31 | github.com/mattn/go-colorable v0.1.13 // indirect 32 | github.com/mattn/go-isatty v0.0.20 // indirect 33 | github.com/rivo/uniseg v0.2.0 // indirect 34 | github.com/russross/blackfriday/v2 v2.1.0 // indirect 35 | github.com/soniakeys/quant v1.0.0 // indirect 36 | github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect 37 | golang.org/x/sync v0.6.0 // indirect 38 | golang.org/x/sys v0.17.0 // indirect 39 | golang.org/x/term v0.17.0 // indirect 40 | ) 41 | -------------------------------------------------------------------------------- /site/components/Portfolio/sections/Docs.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 |
9 |

Documentation

10 |
11 |
12 |

13 | A user manual exists to help you understand the basics of operating, interacting with, 14 | and using Diamond. 15 |

16 |

17 | The guide is maintained by Diamond 18 | {pronoun("herself")}. You may access it via the link below: 19 |

20 |
21 | 22 | docs.0xd14.id 23 | 24 |
25 |
26 | 27 | 58 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | push: 4 | pull_request: 5 | workflow_call: 6 | workflow_dispatch: 7 | jobs: 8 | gomod2nix: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | with: 13 | submodules: true 14 | 15 | - uses: DeterminateSystems/nix-installer-action@main 16 | 17 | - name: Tidy Go modules 18 | run: |- 19 | nix develop -c go mod tidy 20 | 21 | - name: Update gomod2nix 22 | run: |- 23 | nix develop -c gomod2nix 24 | 25 | if [[ $(git status --porcelain) ]]; then 26 | echo "updated=1" >> $GITHUB_OUTPUT 27 | fi 28 | 29 | - name: Update jsonld 30 | run: |- 31 | nix develop -c make jsonld 32 | 33 | - name: Commit changes (if any) 34 | id: commit 35 | uses: stefanzweifel/git-auto-commit-action@v5 36 | with: 37 | commit_message: |- 38 | Update gomod2nix.toml 39 | 40 | Update based on revision ${{ github.sha }}. 41 | branch: ${{ github.ref_name }} 42 | 43 | build: 44 | runs-on: ubuntu-latest 45 | needs: gomod2nix 46 | steps: 47 | - uses: actions/checkout@v4 48 | with: 49 | submodules: true 50 | 51 | - uses: DeterminateSystems/nix-installer-action@main 52 | - uses: DeterminateSystems/magic-nix-cache-action@main 53 | 54 | - name: Build 55 | run: nix build .# 56 | 57 | - name: Upload artifact(s) 58 | uses: actions/upload-pages-artifact@v3 59 | with: 60 | path: result 61 | -------------------------------------------------------------------------------- /vm/programs/nsfw/nsfw.go: -------------------------------------------------------------------------------- 1 | package nsfw 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "strconv" 8 | 9 | _ "embed" 10 | 11 | "libdb.so/vm" 12 | "libdb.so/vm/internal/nsfw" 13 | "libdb.so/vm/internal/vars" 14 | "libdb.so/vm/programs" 15 | ) 16 | 17 | //go:embed prompt.txt 18 | var prompt string 19 | 20 | func init() { 21 | programs.Register(prog{}) 22 | } 23 | 24 | type prog struct{} 25 | 26 | func (prog) Name() string { return "nsfw" } 27 | 28 | func (prog) Usage() string { 29 | return "opt in to potentially nsfw content" 30 | } 31 | 32 | var errAlreadyEnabled = errors.New("nsfw is already enabled") 33 | 34 | func (prog) Run(ctx context.Context, env vm.Environment, args []string) error { 35 | if len(args) > 1 { 36 | switch args[1] { 37 | case "enable": 38 | break 39 | case "disable": 40 | nsfw.Disable() 41 | vars.Get("shell-mommy").Set(false) 42 | return nil 43 | case "get": 44 | env.Println(strconv.FormatBool(nsfw.IsEnabled())) 45 | return nil 46 | case "help", "-h", "--help": 47 | return &vm.UsageError{Usage: "nsfw [enable|get|help]"} 48 | default: 49 | return fmt.Errorf("unknown subcommand %q", args[1]) 50 | } 51 | } 52 | 53 | if nsfw.IsEnabled() { 54 | return vm.WrapError(1, errAlreadyEnabled) 55 | } 56 | 57 | fmt.Fprint(env.Terminal.Stdout, prompt) 58 | 59 | answer, err := env.PromptLine("To agree, type `yes' or `y': ") 60 | if err != nil { 61 | return err 62 | } 63 | 64 | switch answer { 65 | case "yes", "y": 66 | nsfw.Enable() 67 | vars.Get("shell-mommy").Set(true) 68 | return nil 69 | default: 70 | return errors.New("user declined") 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [libdb.so](https://libdb.so) 2 | 3 |
4 | 5 | Homepage for [libdb.so](https://libdb.so). 6 | 7 | ![screenshot](.github/screenshot2.png) 8 | 9 |
10 | 11 | [libdb.so](https://libdb.so) is my personal homepage. It is a portfolio-ish 12 | site, but I want it to be a place that's really cool to poke around in. 13 | 14 | This project contains the source code for [libdb.so](https://libdb.so). The 15 | project consists of two components: 16 | 17 | - Frontend, which is an xterm.js terminal emulator that runs in the browser. 18 | - Backend, which is a Go WebAssembly machine that also runs in the browser. 19 | 20 | These two components communicate with each other using WebAssembly calls. They 21 | act almost like traditional computers did with actual physical terminals over a 22 | serial port. 23 | 24 | The terminal is fully capable of SIXEL, so images can be printed using either 25 | `cat` or `img2sixel`. 26 | 27 | The backend is implemented using custom user-space libraries, such as 28 | [mvdan/sh](https://github.com/mvdan/sh) for a Shell, 29 | [peterh/liner](https://github.com/peterh/liner) for a Readline-like editor, and 30 | `libdb.so/vm` for the VM-like components. Some of these libraries are forked to 31 | add support for WebAssembly and xterm.js. 32 | 33 | The goal of this project is partly to show off my programming language stack. It 34 | uses the trinity of Go, TypeScript and Nix. Of which: 35 | 36 | - Go is used for the backend, which is the WebAssembly VM. 37 | - TypeScript (with Svelte) is used for the frontend, which runs the xterm.js 38 | terminal emulator and the Go VM. 39 | - Nix is used for the development and build environments. 40 | -------------------------------------------------------------------------------- /vm/internal/syncg/map.go: -------------------------------------------------------------------------------- 1 | package syncg 2 | 3 | import "sync" 4 | 5 | // Map is a generic wrapper around sync.Map. 6 | type Map[K comparable, V any] sync.Map 7 | 8 | // Load returns the value stored in the Map for the given key. 9 | func (m *Map[K, V]) Load(key K) (V, bool) { 10 | v, ok := (*sync.Map)(m).Load(key) 11 | if ok { 12 | return v.(V), true 13 | } 14 | var z V 15 | return z, false 16 | } 17 | 18 | // Store stores the given value in the Map for the given key. 19 | func (m *Map[K, V]) Store(key K, val V) { 20 | (*sync.Map)(m).Store(key, val) 21 | } 22 | 23 | // Delete deletes the value stored in the Map for the given key. 24 | func (m *Map[K, V]) Delete(key K) { 25 | (*sync.Map)(m).Delete(key) 26 | } 27 | 28 | // Range calls f sequentially for each key and value present in the Map. If f 29 | // returns false, range stops the iteration. 30 | func (m *Map[K, V]) Range(f func(key K, value V) bool) { 31 | (*sync.Map)(m).Range(func(key, value interface{}) bool { 32 | return f(key.(K), value.(V)) 33 | }) 34 | } 35 | 36 | // LoadOrStore returns the existing value for the key if present. Otherwise, it 37 | // stores and returns the given value. The loaded result is true if the value was 38 | // loaded, false if stored. 39 | func (m *Map[K, V]) LoadOrStore(key K, val V) (actual V, loaded bool) { 40 | v, loaded := (*sync.Map)(m).LoadOrStore(key, val) 41 | return v.(V), loaded 42 | } 43 | 44 | // LoadAndDelete deletes the value for the key if present. The loaded result is 45 | // true if the value was loaded, false if the Map contains no value for the key. 46 | func (m *Map[K, V]) LoadAndDelete(key K) (value V, loaded bool) { 47 | v, loaded := (*sync.Map)(m).LoadAndDelete(key) 48 | if loaded { 49 | return v.(V), true 50 | } 51 | var z V 52 | return z, false 53 | } 54 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import { svelte } from "@sveltejs/vite-plugin-svelte"; 3 | import * as path from "path"; 4 | import viteCompression from "vite-plugin-compression"; 5 | import sveltePreprocess from "svelte-preprocess"; 6 | 7 | const root = path.resolve(__dirname); 8 | 9 | export default defineConfig({ 10 | // Why the FUCK is clearScreen true by default? That is fucking stupid. 11 | clearScreen: false, 12 | plugins: [ 13 | viteCompression({ 14 | filter: /\.(js|mjs|json|css|html|wasm)$/i, 15 | }), 16 | svelte({ 17 | preprocess: sveltePreprocess(), 18 | onwarn: (warning, handler) => { 19 | const ignoredCodes = ["css-unused-selector", "a11y/no-noninteractive-element-interactions"]; 20 | if (ignoredCodes.find((code) => code == warning.code)) return; 21 | if (handler) handler(warning); 22 | }, 23 | }), 24 | ], 25 | root: path.join(root, "site"), 26 | publicDir: path.join(root, "build", "public"), 27 | server: { 28 | port: 5001, 29 | }, 30 | build: { 31 | outDir: path.join(root, "build", "dist"), 32 | assetsDir: "_assets", 33 | emptyOutDir: true, 34 | rollupOptions: { 35 | output: { 36 | format: "esm", 37 | }, 38 | }, 39 | target: "esnext", 40 | sourcemap: true, 41 | reportCompressedSize: false, // viteCompression does this for us 42 | }, 43 | esbuild: { 44 | sourcemap: true, 45 | }, 46 | // https://github.com/vitejs/vite/issues/7385#issuecomment-1286606298 47 | resolve: { 48 | alias: { 49 | "#/libdb.so": root, 50 | }, 51 | }, 52 | }); 53 | 54 | if (import.meta.hot) { 55 | // always reload the page on change because v86 is fragile 56 | import.meta.hot.accept(() => import.meta.hot!.invalidate()); 57 | } 58 | -------------------------------------------------------------------------------- /vm/programs/coreutils/date.go: -------------------------------------------------------------------------------- 1 | package coreutils 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/urfave/cli/v3" 9 | "libdb.so/vm" 10 | "libdb.so/vm/internal/cliprog" 11 | "libdb.so/vm/programs" 12 | ) 13 | 14 | func init() { 15 | programs.Register(cliprog.Wrap(date)) 16 | } 17 | 18 | var date = cli.App{ 19 | Name: "date", 20 | Usage: "print the local date and time", 21 | Flags: []cli.Flag{ 22 | &cli.BoolFlag{ 23 | Name: "iso-8601", 24 | Aliases: []string{"I", "rfc-3339"}, 25 | }, 26 | &cli.StringFlag{ 27 | Name: "date", 28 | Aliases: []string{"d"}, 29 | Usage: "display time described by STRING, not 'now'; requires -f", 30 | }, 31 | &cli.StringFlag{ 32 | Name: "format", 33 | Aliases: []string{"f"}, 34 | Usage: "use FORMAT for date parsing; see godate -h", 35 | }, 36 | }, 37 | Action: func(c *cli.Context) error { 38 | switch c.Args().First() { 39 | case "diamond", "me": 40 | fmt.Println("I love you <3") 41 | return nil 42 | } 43 | 44 | env := vm.EnvironmentFromContext(c.Context) 45 | 46 | now := time.Now() 47 | if c.Bool("date") { 48 | if !c.Bool("format") { 49 | return errors.New("date: -d requires -f") 50 | } 51 | 52 | t, err := time.Parse(c.String("format"), c.String("date")) 53 | if err != nil { 54 | return fmt.Errorf("date: invalid time %q: %w", c.String("date"), err) 55 | } 56 | 57 | now = t 58 | } 59 | 60 | if c.Bool("iso-8601") { 61 | env.Println(now.Format(time.RFC3339)) 62 | } else { 63 | env.Println(now.Format(time.UnixDate)) 64 | } 65 | 66 | return nil 67 | }, 68 | } 69 | 70 | var godate = cli.App{ 71 | Name: "godate", 72 | Usage: "convert strftime to Go time format", 73 | UsageText: `godate `, 74 | // TODO 75 | } 76 | -------------------------------------------------------------------------------- /site/lib/jsonld.ts: -------------------------------------------------------------------------------- 1 | /* 2 | export const context = { 3 | "@vocab": "https://schema.org/", 4 | rdf: "http://www.w3.org/1999/02/22-rdf-syntax-ns#", 5 | rdfs: "http://www.w3.org/2000/01/rdf-schema#", 6 | zvava: "https://zvava.org/schema#", 7 | libdb: "https://0xd14.id#", 8 | }; 9 | 10 | export function expand(key: `${keyof typeof context}:${string}`) { 11 | const [prefix, value] = key.split(":"); 12 | return context[prefix as keyof typeof context] + value; 13 | } 14 | 15 | expand("libdb:toy"); // type checks 16 | */ 17 | 18 | import jsonld from "jsonld"; 19 | 20 | export type JSONLDDocument = { 21 | "@context": Record; 22 | "@graph": jsonld.NodeObject[]; 23 | }; 24 | 25 | export type FetchedJSONLD = { 26 | root: JSONLDDocument; 27 | self: jsonld.NodeObject & { 28 | // TypeScript. Shut up. 29 | [key: string]: any; 30 | }; 31 | }; 32 | 33 | export const jsonldURL = "/_fs/0xd14.jsonld"; 34 | 35 | export const jsonldFetch = () => 36 | fetch(jsonldURL) 37 | .then((r) => r.json() as Promise) 38 | .then( 39 | (r) => 40 | ({ 41 | root: r, 42 | self: r["@graph"][0] as jsonld.NodeObject & { 43 | [key: string]: any; 44 | }, 45 | }) as FetchedJSONLD, 46 | ) 47 | .catch((err) => { 48 | console.error("Failed to fetch self JSON-LD:", err); 49 | throw err; 50 | }); 51 | 52 | export const jsonldDoc = jsonldFetch(); 53 | 54 | export async function usingContext( 55 | doc: Awaited, 56 | object: any, 57 | vocab: string, 58 | ): Promise { 59 | return (await jsonld.compact( 60 | { 61 | "@context": doc.root["@context"], 62 | ...object, 63 | }, 64 | { 65 | "@vocab": vocab, 66 | }, 67 | )) as T; 68 | } 69 | -------------------------------------------------------------------------------- /jsonld/01-88x31.jsonld: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "@id": "libdb:88x31", 4 | "@type": "rdf:Property", 5 | "rdfs:comment": "The 88x31 pixel logo(s) of the Thing.", 6 | "rdfs:label": "88x31", 7 | "rangeIncludes": [ 8 | { 9 | "@id": "schema:ImageObject" 10 | }, 11 | { 12 | "@id": "schema:URL" 13 | }, 14 | { 15 | "@id": "libdb:88x31Badge" 16 | } 17 | ] 18 | }, 19 | { 20 | "@id": "libdb:other88x31", 21 | "@type": "rdf:Property", 22 | "rdfs:comment": "Other 88x31 pixel logo(s) not belonging to the Thing.", 23 | "rdfs:label": "other88x31", 24 | "rangeIncludes": [ 25 | { 26 | "@id": "schema:ImageObject" 27 | }, 28 | { 29 | "@id": "schema:URL" 30 | }, 31 | { 32 | "@id": "libdb:88x31Badge" 33 | } 34 | ] 35 | }, 36 | { 37 | "@id": "libdb:88x31Badge", 38 | "@type": "rdfs:Class", 39 | "rdfs:comment": "A 88x31 pixels badge.", 40 | "rdfs:label": "88x31Badge" 41 | }, 42 | { 43 | "@id": "libdb:88x31Badge/image", 44 | "@type": "rdf:Property", 45 | "rdfs:comment": "The URL of the 88x31 pixel image/logo.", 46 | "rdfs:label": "image", 47 | "rangeIncludes": [ 48 | { 49 | "@id": "schema:ImageObject" 50 | }, 51 | { 52 | "@id": "schema:URL" 53 | } 54 | ] 55 | }, 56 | { 57 | "@id": "libdb:88x31Badge/link", 58 | "@type": "rdf:Property", 59 | "rdfs:comment": "The URL the 88x31 pixel image/logo links to.", 60 | "rdfs:label": "link", 61 | "rangeIncludes": "schema:URL" 62 | }, 63 | { 64 | "@id": "libdb:88x31Badge/alt", 65 | "@type": "rdf:Property", 66 | "rdfs:comment": "The alt text of the 88x31 pixel image/logo.", 67 | "rdfs:label": "alt", 68 | "rangeIncludes": "schema:Text" 69 | } 70 | ] 71 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "libdb.so", 3 | "type": "module", 4 | "license": "GPL-3.0-or-later", 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "make" 8 | }, 9 | "prettier": { 10 | "plugins": [ 11 | "prettier-plugin-svelte" 12 | ], 13 | "printWidth": 100, 14 | "overrides": [ 15 | { 16 | "files": "*.jsonld", 17 | "options": { 18 | "parser": "json" 19 | } 20 | }, 21 | { 22 | "files": "*.svelte", 23 | "options": { 24 | "printWidth": 100 25 | } 26 | } 27 | ] 28 | }, 29 | "devDependencies": { 30 | "@material-design-icons/svg": "^0.14.13", 31 | "@sveltejs/vite-plugin-svelte": "^2.4.6", 32 | "@tsconfig/svelte": "^5.0.2", 33 | "@types/fontfaceobserver": "^2.1.3", 34 | "@types/jsonld": "^1.5.14", 35 | "@xterm/addon-canvas": "^0.7.0", 36 | "@xterm/addon-fit": "^0.10.0", 37 | "@xterm/addon-image": "^0.8.0", 38 | "@xterm/addon-webgl": "^0.18.0", 39 | "@xterm/xterm": "^5.5.0", 40 | "env": "^0.0.2", 41 | "fontfaceobserver": "^2.3.0", 42 | "libwebring": "github:diamondburned/libwebring#nightly", 43 | "normalize.css": "^8.0.1", 44 | "prettier": "^3.1.1", 45 | "prettier-plugin-svelte": " ^3.1.2", 46 | "sass": "^1.69.5", 47 | "super-tiny-icons": "^0.5.0", 48 | "svelte": " ^4.2.8", 49 | "svelte-check": " ^3.6.2", 50 | "svelte-language-server": "^0.15.22", 51 | "svelte-persisted-store": "^0.7.0", 52 | "svelte-preprocess": "^5.0.1", 53 | "typescript": "^5.3.3", 54 | "typescript-language-server": "^3.3.2", 55 | "vite": "^4.1.1", 56 | "vite-plugin-compression": "^0.5.1" 57 | }, 58 | "imports": { 59 | "#/libdb.so/*": "./*" 60 | }, 61 | "dependencies": { 62 | "jsonld": "^8.3.2", 63 | "tsx": "^4.11.2" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /vm/fs/kvfs/memorystorage.go: -------------------------------------------------------------------------------- 1 | package kvfs 2 | 3 | import ( 4 | "io/fs" 5 | "strings" 6 | "sync" 7 | ) 8 | 9 | // MemoryStorage is a simple non-persistent key-value store. 10 | func MemoryStorage() Store { 11 | return &memoryStorage{ 12 | m: make(map[string]StoredValue), 13 | } 14 | } 15 | 16 | // MemoryStorageFromExisting creates a new MemoryStorage from an existing map. 17 | func MemoryStorageFromExisting(m map[string]StoredValue) Store { 18 | return &memoryStorage{ 19 | m: m, 20 | } 21 | } 22 | 23 | type memoryStorage struct { 24 | mu sync.RWMutex 25 | m map[string]StoredValue 26 | } 27 | 28 | var _ Store = (*memoryStorage)(nil) 29 | 30 | func (s *memoryStorage) Get(fullpath string) (StoredValue, error) { 31 | s.mu.RLock() 32 | defer s.mu.RUnlock() 33 | 34 | v, ok := s.m[fullpath] 35 | if !ok { 36 | return nil, fs.ErrNotExist 37 | } 38 | 39 | return v, nil 40 | } 41 | 42 | func (s *memoryStorage) Set(fullpath string, v StoredValue) error { 43 | s.mu.Lock() 44 | defer s.mu.Unlock() 45 | 46 | s.m[fullpath] = v 47 | 48 | return nil 49 | } 50 | 51 | func (s *memoryStorage) Delete(fullpath string) error { 52 | s.mu.Lock() 53 | defer s.mu.Unlock() 54 | 55 | delete(s.m, fullpath) 56 | 57 | return nil 58 | } 59 | 60 | func (s *memoryStorage) List(fullpath string, recursive bool) ([]PathedStoreValue, error) { 61 | s.mu.RLock() 62 | defer s.mu.RUnlock() 63 | 64 | var values []PathedStoreValue 65 | for filepath, value := range s.m { 66 | if !strings.HasPrefix(filepath, fullpath) { 67 | continue 68 | } 69 | 70 | filename := strings.TrimPrefix(filepath, fullpath) 71 | if !recursive && strings.Contains(filename, "/") { 72 | continue 73 | } 74 | 75 | values = append(values, PathedStoreValue{ 76 | Path: filepath, 77 | StoredValue: value, 78 | }) 79 | } 80 | 81 | return values, nil 82 | } 83 | -------------------------------------------------------------------------------- /site/components/Portfolio/sections/Resume.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 |
11 |

Resume

12 | 33 |
34 | 35 | 36 | 37 | 38 | 75 | -------------------------------------------------------------------------------- /site/components/Webring.svelte: -------------------------------------------------------------------------------- 1 | 28 | 29 | 30 | ... 31 | 36 | 37 | 38 | 83 | -------------------------------------------------------------------------------- /vm/fs/utilfs/relocatefs_test.go: -------------------------------------------------------------------------------- 1 | package utilfs 2 | 3 | import ( 4 | "io" 5 | "io/fs" 6 | "testing" 7 | 8 | "github.com/alecthomas/assert/v2" 9 | "libdb.so/vm/fs/kvfs" 10 | ) 11 | 12 | func TestRelocateFS(t *testing.T) { 13 | store := kvfs.MemoryStorageFromExisting(map[string]kvfs.StoredValue{ 14 | "/d": kvfs.StoredDirectory{IsDir: true}, 15 | "/d/e": kvfs.StoredFile{Data: []byte("hello")}, 16 | }) 17 | testFS := kvfs.New(store) 18 | 19 | relocatedFS := Apply(testFS, RelocateFS("a/b/c")) 20 | 21 | t.Run("a/b/c/d/e", func(t *testing.T) { 22 | f, err := relocatedFS.Open("a/b/c/d/e") 23 | assert.NoError(t, err) 24 | 25 | b, err := io.ReadAll(f) 26 | f.Close() 27 | assert.NoError(t, err) 28 | assert.Equal(t, "hello", string(b), "file contents") 29 | }) 30 | 31 | t.Run("a/b/c/d", func(t *testing.T) { 32 | files, err := fs.ReadDir(relocatedFS, "a/b/c/d") 33 | assert.NoError(t, err) 34 | assert.Equal(t, 1, len(files), "number of files") 35 | assert.Equal(t, "e", files[0].Name(), "name") 36 | }) 37 | 38 | t.Run("a/b/c", func(t *testing.T) { 39 | files, err := fs.ReadDir(relocatedFS, "a/b/c") 40 | assert.NoError(t, err) 41 | assert.Equal(t, 1, len(files), "number of files") 42 | assert.Equal(t, "d", files[0].Name(), "name") 43 | }) 44 | 45 | t.Run("a/b", func(t *testing.T) { 46 | files, err := fs.ReadDir(relocatedFS, "a/b") 47 | assert.NoError(t, err) 48 | assert.Equal(t, 1, len(files), "number of files") 49 | assert.Equal(t, "c", files[0].Name(), "name") 50 | }) 51 | 52 | t.Run("a", func(t *testing.T) { 53 | files, err := fs.ReadDir(relocatedFS, "a") 54 | assert.NoError(t, err) 55 | assert.Equal(t, 1, len(files), "number of files") 56 | assert.Equal(t, "b", files[0].Name(), "name") 57 | }) 58 | 59 | t.Run("-", func(t *testing.T) { 60 | files, err := fs.ReadDir(relocatedFS, "") 61 | assert.NoError(t, err) 62 | assert.Equal(t, 1, len(files), "number of files") 63 | assert.Equal(t, "a", files[0].Name(), "name") 64 | }) 65 | } 66 | -------------------------------------------------------------------------------- /nix/packages/tinygo/0003-Use-out-path-as-build-id-on-darwin.patch: -------------------------------------------------------------------------------- 1 | From e7357c383188dd735592bd9f2202d2afcfffa39d Mon Sep 17 00:00:00 2001 2 | From: =?UTF-8?q?Mustafa=20=C3=87al=C4=B1=C5=9Fkan?= 3 | Date: Sun, 11 Sep 2022 17:08:33 +0300 4 | Subject: [PATCH 3/3] Use out path as build id on darwin 5 | 6 | 7 | diff --git a/builder/buildid.go b/builder/buildid.go 8 | index e6527700..65cb08e8 100644 9 | --- a/builder/buildid.go 10 | +++ b/builder/buildid.go 11 | @@ -3,8 +3,6 @@ package builder 12 | import ( 13 | "bytes" 14 | "debug/elf" 15 | - "debug/macho" 16 | - "encoding/binary" 17 | "fmt" 18 | "io" 19 | "os" 20 | @@ -53,30 +51,9 @@ func ReadBuildID() ([]byte, error) { 21 | return goID, nil 22 | } 23 | case "darwin": 24 | - // Read the LC_UUID load command, which contains the equivalent of a 25 | - // build ID. 26 | - file, err := macho.NewFile(f) 27 | - if err != nil { 28 | - return nil, err 29 | - } 30 | - for _, load := range file.Loads { 31 | - // Unfortunately, the debug/macho package doesn't support the 32 | - // LC_UUID command directly. So we have to read it from 33 | - // macho.LoadBytes. 34 | - load, ok := load.(macho.LoadBytes) 35 | - if !ok { 36 | - continue 37 | - } 38 | - raw := load.Raw() 39 | - command := binary.LittleEndian.Uint32(raw) 40 | - if command != 0x1b { 41 | - // Looking for the LC_UUID load command. 42 | - // LC_UUID is defined here as 0x1b: 43 | - // https://opensource.apple.com/source/xnu/xnu-4570.71.2/EXTERNAL_HEADERS/mach-o/loader.h.auto.html 44 | - continue 45 | - } 46 | - return raw[4:], nil 47 | - } 48 | + // On darwin, os.Executable() returns broken path in nix build environment 49 | + // So we are using $out path as build id since its also unique 50 | + return []byte("OUT_PATH"), nil 51 | default: 52 | // On other platforms (such as Windows) there isn't such a convenient 53 | // build ID. Luckily, Go does have an equivalent of the build ID, which 54 | -- 55 | 2.37.2 56 | 57 | -------------------------------------------------------------------------------- /site/components/Portfolio/sections/ResumeWork.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 31 | 32 | 80 | -------------------------------------------------------------------------------- /vm/fs/httpfs/json_test.go: -------------------------------------------------------------------------------- 1 | package httpfs 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | type testResult[T any] struct { 10 | value T 11 | error error 12 | } 13 | 14 | func okResult[T any](value T) testResult[T] { 15 | return testResult[T]{value: value} 16 | } 17 | 18 | func TestFileTree_UnmarshalJSON(t *testing.T) { 19 | tests := []struct { 20 | name string 21 | in string 22 | out testResult[FileTreeRoot] 23 | }{ 24 | { 25 | name: "v0-short", 26 | in: `{"a": {"size": 1}}`, 27 | out: okResult(FileTreeRoot{ 28 | BaseURL: "", 29 | Tree: map[string]FileTreeValue{ 30 | "a": FileInfo{Size: 1}, 31 | }, 32 | }), 33 | }, 34 | { 35 | name: "v0-full", 36 | in: `{"$version": 0, "tree": {"a": {"size": 1}}}`, 37 | out: testResult[FileTreeRoot]{ 38 | error: fmt.Errorf("json: cannot unmarshal number into Go value of type httpfs.FileInfo"), 39 | }, 40 | }, 41 | { 42 | name: "v1", 43 | in: `{"$version": 1}`, 44 | out: testResult[FileTreeRoot]{ 45 | error: fmt.Errorf("file tree version 1 is not used"), 46 | }, 47 | }, 48 | { 49 | name: "v2", 50 | in: `{"$version": 2, "base_url": "http://example.com", "tree": {"a": {"size": 1}}}`, 51 | out: okResult(FileTreeRoot{ 52 | BaseURL: "http://example.com", 53 | Tree: map[string]FileTreeValue{ 54 | "a": FileInfo{Size: 1}, 55 | }, 56 | }), 57 | }, 58 | } 59 | 60 | for _, test := range tests { 61 | t.Run(test.name, func(t *testing.T) { 62 | var tree FileTreeRoot 63 | if err := tree.UnmarshalJSON([]byte(test.in)); err != nil { 64 | if test.out.error == nil { 65 | t.Fatalf("unexpected error: %v", err) 66 | } 67 | if err.Error() != test.out.error.Error() { 68 | t.Fatalf("unexpected error: %v", err) 69 | } 70 | return 71 | } 72 | 73 | if test.out.error != nil { 74 | t.Fatalf("expected error: %v", test.out.error) 75 | } 76 | 77 | if !reflect.DeepEqual(tree, test.out.value) { 78 | t.Fatalf("did not get expected value:\n"+ 79 | "expect: %+v\n"+ 80 | "got: %+v", test.out.value, tree) 81 | } 82 | }) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /vm/fs/httpfs/fs_test.go: -------------------------------------------------------------------------------- 1 | package httpfs 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | "path" 7 | "testing" 8 | 9 | "github.com/alecthomas/assert/v2" 10 | ) 11 | 12 | func TestReconcileURL(t *testing.T) { 13 | const appendPath = "/a" 14 | 15 | tests := []struct { 16 | baseURL string 17 | fsURL string 18 | expected string 19 | appended string 20 | }{ 21 | { 22 | baseURL: ".", 23 | fsURL: "https://libdb.so/docs/_docsfs.json", 24 | expected: "https://libdb.so/docs", 25 | appended: "https://libdb.so/docs/a", 26 | }, 27 | { 28 | baseURL: "https://docs.libdb.so/", 29 | fsURL: "https://docs.libdb.so/_docsfs.json", 30 | expected: "https://docs.libdb.so/", 31 | appended: "https://docs.libdb.so/a", 32 | }, 33 | { 34 | baseURL: "https://docs.libdb.so/.", 35 | fsURL: "https://docs.libdb.so/_docsfs.json", 36 | expected: "https://docs.libdb.so/.", 37 | appended: "https://docs.libdb.so/a", 38 | }, 39 | { 40 | baseURL: "https://docs.libdb.so", 41 | fsURL: "https://docs.libdb.so/_docsfs.json", 42 | expected: "https://docs.libdb.so/", 43 | appended: "https://docs.libdb.so/a", 44 | }, 45 | { 46 | baseURL: "https://docs.libdb.so", 47 | fsURL: "https://libdb.so/docs/_docsfs.json", 48 | expected: "https://docs.libdb.so/docs", 49 | appended: "https://docs.libdb.so/docs/a", 50 | }, 51 | { 52 | baseURL: "https://docs.libdb.so/", 53 | fsURL: "https://libdb.so/docs/_docsfs.json", 54 | expected: "https://docs.libdb.so/", 55 | appended: "https://docs.libdb.so/a", 56 | }, 57 | } 58 | 59 | for i, test := range tests { 60 | t.Run(fmt.Sprint("case-", i+1), func(t *testing.T) { 61 | baseURL := mustURL(test.baseURL) 62 | fsURL := mustURL(test.fsURL) 63 | 64 | got := reconcileURL(*baseURL, *fsURL) 65 | assert.Equal(t, test.expected, got.String(), "expected") 66 | 67 | got.Path = path.Join(got.Path, appendPath) 68 | assert.Equal(t, test.appended, got.String(), "appended") 69 | }) 70 | } 71 | } 72 | 73 | func mustURL(s string) *url.URL { 74 | u, err := url.Parse(s) 75 | if err != nil { 76 | panic(err) 77 | } 78 | return u 79 | } 80 | -------------------------------------------------------------------------------- /nix/packages/tinygo/0002-Add-clang-header-path.patch: -------------------------------------------------------------------------------- 1 | diff --git a/builder/builtins.go b/builder/builtins.go 2 | index a1066b67..f4f8ca79 100644 3 | --- a/builder/builtins.go 4 | +++ b/builder/builtins.go 5 | @@ -179,7 +179,7 @@ var avrBuiltins = []string{ 6 | var CompilerRT = Library{ 7 | name: "compiler-rt", 8 | cflags: func(target, headerPath string) []string { 9 | - return []string{"-Werror", "-Wall", "-std=c11", "-nostdlibinc"} 10 | + return []string{"-Werror", "-Wall", "-std=c11", "-isystem", "@clang_include@"} 11 | }, 12 | sourceDir: func() string { 13 | llvmDir := filepath.Join(goenv.Get("TINYGOROOT"), "llvm-project/compiler-rt/lib/builtins") 14 | diff --git a/builder/picolibc.go b/builder/picolibc.go 15 | index 1b7c748b..8a6b9ddd 100644 16 | --- a/builder/picolibc.go 17 | +++ b/builder/picolibc.go 18 | @@ -32,7 +32,7 @@ var Picolibc = Library{ 19 | "-D__OBSOLETE_MATH_FLOAT=1", // use old math code that doesn't expect a FPU 20 | "-D__OBSOLETE_MATH_DOUBLE=0", 21 | "-D_WANT_IO_C99_FORMATS", 22 | - "-nostdlibinc", 23 | + "-isystem", "@clang_include@", 24 | "-isystem", newlibDir + "/libc/include", 25 | "-I" + newlibDir + "/libc/tinystdio", 26 | "-I" + newlibDir + "/libm/common", 27 | diff --git a/compileopts/config.go b/compileopts/config.go 28 | index 62c9fa64..9e89cff3 100644 29 | --- a/compileopts/config.go 30 | +++ b/compileopts/config.go 31 | @@ -284,6 +284,7 @@ func (c *Config) CFlags() []string { 32 | path, _ := c.LibcPath("picolibc") 33 | cflags = append(cflags, 34 | "--sysroot="+path, 35 | + "-isystem", "@clang_include@", 36 | "-isystem", filepath.Join(path, "include"), // necessary for Xtensa 37 | "-isystem", filepath.Join(picolibcDir, "include"), 38 | "-isystem", filepath.Join(picolibcDir, "tinystdio"), 39 | @@ -293,7 +294,6 @@ func (c *Config) CFlags() []string { 40 | path, _ := c.LibcPath("musl") 41 | arch := MuslArchitecture(c.Triple()) 42 | cflags = append(cflags, 43 | - "-nostdlibinc", 44 | "-isystem", filepath.Join(path, "include"), 45 | "-isystem", filepath.Join(root, "lib", "musl", "arch", arch), 46 | "-isystem", filepath.Join(root, "lib", "musl", "include"), 47 | -------------------------------------------------------------------------------- /vm/internal/liner/width.go: -------------------------------------------------------------------------------- 1 | package liner 2 | 3 | import ( 4 | "strings" 5 | "unicode" 6 | 7 | "github.com/leaanthony/go-ansi-parser" 8 | "github.com/mattn/go-runewidth" 9 | ) 10 | 11 | // These character classes are mostly zero width (when combined). 12 | // A few might not be, depending on the user's font. Fixing this 13 | // is non-trivial, given that some terminals don't support 14 | // ANSI DSR/CPR 15 | var zeroWidth = []*unicode.RangeTable{ 16 | unicode.Mn, 17 | unicode.Me, 18 | unicode.Cc, 19 | unicode.Cf, 20 | } 21 | 22 | // countGlyphs considers zero-width characters to be zero glyphs wide, 23 | // and members of Chinese, Japanese, and Korean scripts to be 2 glyphs wide. 24 | func countGlyphs(runes []rune) int { 25 | s := string(runes) 26 | 27 | newline := strings.LastIndexByte(s, '\n') 28 | if newline == -1 { 29 | newline = 0 30 | } 31 | 32 | n, err := ansi.Length(s[newline:]) 33 | if err != nil { 34 | panic(err) 35 | } 36 | 37 | return n 38 | } 39 | 40 | func countMultiLineGlyphs(s []rune, columns int, start int) int { 41 | n := start 42 | for _, r := range s { 43 | if r < 127 { 44 | n++ 45 | continue 46 | } 47 | switch runewidth.RuneWidth(r) { 48 | case 0: 49 | case 1: 50 | n++ 51 | case 2: 52 | n += 2 53 | // no room for a 2-glyphs-wide char in the ending 54 | // so skip a column and display it at the beginning 55 | if n%columns == 1 { 56 | n++ 57 | } 58 | } 59 | } 60 | return n 61 | } 62 | 63 | func getPrefixGlyphs(s []rune, num int) []rune { 64 | p := 0 65 | for n := 0; n < num && p < len(s); p++ { 66 | // speed up the common case 67 | if s[p] < 127 { 68 | n++ 69 | continue 70 | } 71 | if !unicode.IsOneOf(zeroWidth, s[p]) { 72 | n++ 73 | } 74 | } 75 | for p < len(s) && unicode.IsOneOf(zeroWidth, s[p]) { 76 | p++ 77 | } 78 | return s[:p] 79 | } 80 | 81 | func getSuffixGlyphs(s []rune, num int) []rune { 82 | p := len(s) 83 | for n := 0; n < num && p > 0; p-- { 84 | // speed up the common case 85 | if s[p-1] < 127 { 86 | n++ 87 | continue 88 | } 89 | if !unicode.IsOneOf(zeroWidth, s[p-1]) { 90 | n++ 91 | } 92 | } 93 | return s[p:] 94 | } 95 | -------------------------------------------------------------------------------- /jsonld/lib/jsonld.ts: -------------------------------------------------------------------------------- 1 | import type { JsonLdDocument } from "jsonld"; 2 | 3 | export async function fetchDocument< 4 | T extends object = Record, 5 | ExtrasT extends object = JsonLdDocument 6 | >(url: string, extras?: ExtrasT): Promise { 7 | let doc = await fetchJSON(url); 8 | if (typeof doc !== "object") { 9 | throw new Error(`Expected document to be an object, got ${typeof doc}`); 10 | } 11 | if (extras) { 12 | doc = { ...doc, ...extras }; 13 | } 14 | 15 | let entries = Object.entries(doc as Record); 16 | entries = [ 17 | ...entries.filter(([key]) => key.startsWith("@")), 18 | ...entries.filter(([key]) => !key.startsWith("@")), 19 | ]; 20 | return Object.fromEntries(entries) as T & ExtrasT; 21 | } 22 | 23 | export async function fetchJSON(url: string): Promise { 24 | const resp = await fetch(url); 25 | if (!resp.ok) { 26 | throw new Error(`Failed to fetch document: ${resp.statusText}`); 27 | } 28 | 29 | let doc = (await resp.json()) as T; 30 | return doc; 31 | } 32 | 33 | export function dedent(str: TemplateStringsArray): string { 34 | const lines = str.join("").split("\n"); 35 | return lines 36 | .map((line) => line.trim()) 37 | .join(" ") 38 | .trim(); 39 | // const minIndent = Math.min( 40 | // ...lines.map((line) => (line.match(/^ */) ?? [""])[0].length) 41 | // ); 42 | // return lines 43 | // .map((line) => line.slice(minIndent)) 44 | // .join(" ") 45 | // .trim(); 46 | } 47 | 48 | export function gitFile(uri: `${string}:${string}`): string { 49 | const url = new URL(uri); 50 | switch (url.protocol) { 51 | case "github:": { 52 | const [user, repo, ...parts] = url.pathname.split("/"); 53 | const path = parts.join("/"); 54 | const ref = url.searchParams.get("ref") ?? "main"; 55 | return `https://raw.githubusercontent.com/${user}/${repo}/${ref}/${path}`; 56 | } 57 | default: { 58 | throw new Error(`Unsupported protocol: ${url.protocol}`); 59 | } 60 | } 61 | } 62 | 63 | export function assert(condition: boolean, message?: string): asserts condition { 64 | if (!condition) { 65 | throw new Error(message); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /vm/fs/httpfs/json.go: -------------------------------------------------------------------------------- 1 | package httpfs 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "strings" 7 | ) 8 | 9 | // FileTreeValue is either a FileTree or a FileSize. 10 | type FileTreeValue interface { 11 | fileTreeValue() 12 | } 13 | 14 | func (FileInfo) fileTreeValue() {} 15 | func (FileTree) fileTreeValue() {} 16 | 17 | // FileInfo is the information of a file. 18 | type FileInfo struct { 19 | Size int64 `json:"size"` 20 | } 21 | 22 | // FileTree is a tree of files and directories. Directories will have its keys 23 | // end with a slash, while files will not. 24 | type FileTree map[string]FileTreeValue 25 | 26 | func (t *FileTree) UnmarshalJSON(b []byte) error { 27 | m := make(map[string]json.RawMessage) 28 | if err := json.Unmarshal(b, &m); err != nil { 29 | return err 30 | } 31 | 32 | *t = make(map[string]FileTreeValue, len(m)) 33 | for k, b := range m { 34 | if strings.HasSuffix(k, "/") { 35 | var v FileTree 36 | if err := json.Unmarshal(b, &v); err != nil { 37 | return err 38 | } 39 | k = strings.TrimSuffix(k, "/") 40 | (*t)[k] = v 41 | } else { 42 | var v FileInfo 43 | if err := json.Unmarshal(b, &v); err != nil { 44 | return err 45 | } 46 | (*t)[k] = v 47 | } 48 | } 49 | 50 | return nil 51 | } 52 | 53 | // FileTreeRoot is a root file tree. 54 | type FileTreeRoot struct { 55 | // BaseURL is the base URL of the file tree. Iti is prepended to the file 56 | // paths when fetching files. 57 | BaseURL string `json:"base_url"` 58 | // Tree is the file tree. 59 | Tree FileTree `json:"tree"` 60 | } 61 | 62 | func (t *FileTreeRoot) UnmarshalJSON(b []byte) error { 63 | var versionedTree struct { 64 | Version int `json:"$version"` 65 | } 66 | 67 | if err := json.Unmarshal(b, &versionedTree); err != nil { 68 | return fmt.Errorf("unmarshal file tree version: %w", err) 69 | } 70 | 71 | switch versionedTree.Version { 72 | case 0: 73 | return json.Unmarshal(b, &t.Tree) 74 | case 1: 75 | return fmt.Errorf("file tree version 1 is not used") 76 | case 2: 77 | type RawFileTreeRoot FileTreeRoot 78 | return json.Unmarshal(b, (*RawFileTreeRoot)(t)) 79 | default: 80 | return fmt.Errorf("unknown file tree version: %d", versionedTree.Version) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /site/components/Switch.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 | 16 | 17 | 102 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ROOT = $(shell pwd) 2 | SITE = $(shell find site -type f) node_modules vite.config.ts package*.json 3 | PUBLIC = $(shell find public/_fs -type f) 4 | GOFILES = $(shell find vm -type f) go.mod go.sum 5 | GOMODULE = $(shell go list -m) 6 | 7 | # phony 8 | 9 | .PHONY: all 10 | all: build/dist 11 | 12 | .PHONY: dev 13 | dev: dist-deps 14 | vite dev 15 | 16 | .PHONY: dist-deps 17 | dist-deps: \ 18 | $(SITE) \ 19 | site/components/Terminal/color-schemes.json \ 20 | build/public \ 21 | build/vm.wasm 22 | 23 | .PHONY: clean 24 | clean: 25 | rm -r build 26 | 27 | .PHONY: jsonld 28 | jsonld: node_modules $(shell find jsonld -type f) 29 | tsx jsonld/_render.ts > public/_fs/0xd14.jsonld 30 | 31 | .PHONY: openapi 32 | openapi: backend/openapi 33 | 34 | # real paths 35 | 36 | OPENAPI_SCHEMAS = $(shell find backend/openapi -name '*.yml' -a -not -name '_*') 37 | OPENAPI_CODE = $(patsubst backend/openapi/%.yml,backend/openapi/%api,$(OPENAPI_SCHEMAS)) 38 | 39 | backend/openapi/%api: backend/openapi/%.yml build/openapi.config.yml 40 | mkdir -p "$@" 41 | cd backend/openapi && oapi-codegen \ 42 | --config $(ROOT)/build/openapi.config.yml \ 43 | --package "$(shell basename $@)" \ 44 | -o "$(shell basename $@)/openapi.gen.go" "$(shell basename $<)" 45 | 46 | backend/openapi: $(OPENAPI_CODE) 47 | 48 | site/components/Terminal/color-schemes.json: ./scripts/xtermjs-colors 49 | ./scripts/xtermjs-colors > $@ 50 | 51 | node_modules: package-lock.json package.json 52 | npm install 53 | 54 | build/public/_fs: $(PUBLIC) 55 | rm -rf $@ && mkdir -p $@ 56 | cp -r public/_fs $(dir $@) 57 | 58 | build/openapi.config.yml: backend/openapi/_config.yml $(OPENAPI_SCHEMAS) 59 | ./scripts/import-mapping "$<" > "$@" 60 | 61 | build/public/_fs.json: $(PUBLIC) build/public/_fs scripts/jsonfs 62 | cd $(dir $@) && bash $(ROOT)/scripts/jsonfs _fs > _fs.json 63 | 64 | build/public: build/public/_fs build/public/_fs.json 65 | 66 | build/dist: dist-deps 67 | vite build 68 | 69 | build/vm.wasm: $(GOFILES) 70 | mkdir -p build 71 | GOOS=js GOARCH=wasm go build -o build/vm.wasm ./vm/cmd/vm-wasm 72 | 73 | build/backend: $(GOFILES) backend/openapi 74 | mkdir -p build 75 | go build -o build/backend ./backend/cmd/backend 76 | 77 | #build/console.wasm: build/wasm_exec.js $(GOFILES) 78 | # mkdir -p build 79 | # tinygo build -o build/console.wasm -opt 2 -scheduler asyncify -target wasm ./cmd/console-wasm/ 80 | 81 | build/wasm_exec.js: 82 | cp $$(go env GOROOT)/misc/wasm/wasm_exec.js $@ 83 | -------------------------------------------------------------------------------- /jsonld/02-webring.jsonld: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "@id": "libdb:webring", 4 | "@type": "rdf:Property", 5 | "rdfs:comment": "The webring(s) that the thing is part of.", 6 | "rdfs:label": "webring", 7 | "rangeIncludes": "libdb:Webring" 8 | }, 9 | { 10 | "@id": "libdb:Webring", 11 | "@type": "rdfs:Class", 12 | "rdfs:comment": "A webring of websites, follows libdb.so/libwebring.", 13 | "rdfs:label": "Webring" 14 | }, 15 | { 16 | "@id": "libdb:Webring/name", 17 | "@type": "rdf:Property", 18 | "rdfs:comment": "The name of the webring.", 19 | "rdfs:label": "name", 20 | "rangeIncludes": "Text" 21 | }, 22 | { 23 | "@id": "libdb:Webring/root", 24 | "@type": "rdf:Property", 25 | "rdfs:comment": "The URL link of the webring, if any.", 26 | "rdfs:label": "root", 27 | "rangeIncludes": "URL" 28 | }, 29 | { 30 | "@id": "libdb:Webring/self", 31 | "@type": "rdf:Property", 32 | "rdfs:comment": "The name of the current Thing that matches the name of a link", 33 | "rdfs:label": "self", 34 | "rangeIncludes": "Text" 35 | }, 36 | { 37 | "@id": "libdb:Webring/version", 38 | "@type": "rdf:Property", 39 | "rdfs:comment": "The version of the webring.", 40 | "rdfs:label": "version", 41 | "rangeIncludes": "Number" 42 | }, 43 | { 44 | "@id": "libdb:Webring/ring", 45 | "@type": "rdf:Property", 46 | "rdfs:comment": "The ring of the webring.", 47 | "rdfs:label": "ring", 48 | "rangeIncludes": "libdb:Webring/Ring" 49 | }, 50 | { 51 | "@id": "libdb:Webring/Ring", 52 | "@type": "rdfs:Class", 53 | "rdfs:comment": "A link in a webring.", 54 | "rdfs:label": "Ring" 55 | }, 56 | { 57 | "@id": "libdb:Webring/Ring/link", 58 | "@type": "rdf:Property", 59 | "rdfs:comment": "The URL link of the webring link.", 60 | "rdfs:label": "link", 61 | "rangeIncludes": "URL" 62 | }, 63 | { 64 | "@id": "libdb:Webring/Ring/name", 65 | "@type": "rdf:Property", 66 | "rdfs:comment": "The name of the webring link.", 67 | "rdfs:label": "name", 68 | "rangeIncludes": "Text" 69 | }, 70 | { 71 | "@id": "libdb:Webring/Ring/88x31", 72 | "@type": "rdf:Property", 73 | "rdfs:comment": "The 88x31 pixel logo(s) of the webring link.", 74 | "rdfs:label": "88x31", 75 | "rangeIncludes": [ 76 | { 77 | "@id": "schema:ImageObject" 78 | }, 79 | { 80 | "@id": "schema:URL" 81 | } 82 | ] 83 | } 84 | ] 85 | -------------------------------------------------------------------------------- /site/components/Portfolio/sections/ResumeProjects.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 |

Projects

7 |
    8 | {#each resume.projects as project} 9 |
  • 10 |
    11 | {project.name ?? ""} 12 | 13 | {(project.keywords ?? []).join(", ")} 14 | 15 | {#if project.url} 16 | 21 | {project.url ?? ""} 22 | 23 | {/if} 24 |
    25 |

    {project.description ?? ""}

    26 |
  • 27 | {/each} 28 |
29 |
30 | 31 | 98 | -------------------------------------------------------------------------------- /vm/internal/liner/input_linux.go: -------------------------------------------------------------------------------- 1 | package liner 2 | 3 | import ( 4 | "os" 5 | "os/signal" 6 | "strings" 7 | "syscall" 8 | ) 9 | 10 | const ( 11 | getTermios = syscall.TCGETS 12 | setTermios = syscall.TCSETS 13 | ) 14 | 15 | const ( 16 | icrnl = syscall.ICRNL 17 | inpck = syscall.INPCK 18 | istrip = syscall.ISTRIP 19 | ixon = syscall.IXON 20 | opost = syscall.OPOST 21 | cs8 = syscall.CS8 22 | isig = syscall.ISIG 23 | icanon = syscall.ICANON 24 | iexten = syscall.IEXTEN 25 | ) 26 | 27 | type termios struct { 28 | syscall.Termios 29 | } 30 | 31 | const cursorColumn = false 32 | 33 | // TerminalSupported returns true if the current terminal supports 34 | // line editing features, and false if liner will use the 'dumb' 35 | // fallback for input. 36 | // Note that TerminalSupported does not check all factors that may 37 | // cause liner to not fully support the terminal (such as stdin redirection) 38 | func TerminalSupported() bool { 39 | bad := map[string]bool{"": true, "dumb": true, "cons25": true} 40 | return !bad[strings.ToLower(os.Getenv("TERM"))] 41 | } 42 | 43 | func initLinerTerminal(s *State) { 44 | if m, err := TerminalMode(); err == nil { 45 | s.origMode = *m.(*termios) 46 | } else { 47 | s.inputRedirected = true 48 | } 49 | if _, err := getMode(syscall.Stdout); err != nil { 50 | s.outputRedirected = true 51 | } 52 | if s.inputRedirected && s.outputRedirected { 53 | s.terminalSupported = false 54 | } 55 | if s.terminalSupported && !s.inputRedirected && !s.outputRedirected { 56 | mode := s.origMode 57 | mode.Iflag &^= icrnl | inpck | istrip | ixon 58 | mode.Cflag |= cs8 59 | mode.Lflag &^= syscall.ECHO | icanon | iexten 60 | mode.Cc[syscall.VMIN] = 1 61 | mode.Cc[syscall.VTIME] = 0 62 | mode.ApplyMode() 63 | 64 | winch := make(chan os.Signal, 1) 65 | signal.Notify(winch, syscall.SIGWINCH) 66 | s.winch = winch 67 | 68 | s.checkOutput() 69 | } 70 | 71 | if !s.outputRedirected { 72 | s.outputRedirected = !s.getColumns() 73 | } 74 | } 75 | 76 | func (s *State) supportedStartPrompt() { 77 | if s.terminalSupported { 78 | if m, err := TerminalMode(); err == nil { 79 | s.defaultMode = *m.(*termios) 80 | mode := s.defaultMode 81 | mode.Lflag &^= isig 82 | mode.ApplyMode() 83 | } 84 | } 85 | } 86 | 87 | func (s *State) supportedStopPrompt() { 88 | if s.terminalSupported { 89 | s.defaultMode.ApplyMode() 90 | } 91 | } 92 | 93 | func (s *State) close() { 94 | if !s.inputRedirected { 95 | s.origMode.ApplyMode() 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /nix/sources.json: -------------------------------------------------------------------------------- 1 | { 2 | "gomod2nix": { 3 | "branch": "master", 4 | "description": "Convert applications using Go modules to Nix expressions [maintainer=@adisbladis]", 5 | "homepage": "", 6 | "owner": "nix-community", 7 | "repo": "gomod2nix", 8 | "rev": "05c993c9a5bd55a629cd45ed49951557b7e9c61a", 9 | "sha256": "0qs1l7sqzgpm0fz45dzw5mcvjhdc0ps5lgvhk0ibjvw7v9bg5py4", 10 | "type": "tarball", 11 | "url": "https://github.com/nix-community/gomod2nix/archive/05c993c9a5bd55a629cd45ed49951557b7e9c61a.tar.gz", 12 | "url_template": "https://github.com///archive/.tar.gz" 13 | }, 14 | "nixpkgs": { 15 | "branch": "nixos-unstable", 16 | "description": "Nix Packages collection", 17 | "homepage": "", 18 | "owner": "NixOS", 19 | "repo": "nixpkgs", 20 | "rev": "85f1ba3e51676fa8cc604a3d863d729026a6b8eb", 21 | "sha256": "0992mbqdvvkxy1gz8bzmqdx3kz5and17xik6d836p65vkll64ksz", 22 | "type": "tarball", 23 | "url": "https://github.com/NixOS/nixpkgs/archive/85f1ba3e51676fa8cc604a3d863d729026a6b8eb.tar.gz", 24 | "url_template": "https://github.com///archive/.tar.gz" 25 | }, 26 | "not-os": { 27 | "branch": "compat-fix", 28 | "description": "An operating system generator, based on NixOS, that, given a config, outputs a small (47 MB), read-only squashfs for a runit-based operating system, with support for iPXE and signed boot.", 29 | "homepage": "", 30 | "owner": "Mic92", 31 | "repo": "not-os", 32 | "rev": "9dbd509335bc8f5bdc4bd4cc31925b9152a2680a", 33 | "sha256": "1ajayf23mxjdxyc3irbv75jvjzclrjpq51y1ki5hi6hwzhis9y35", 34 | "type": "tarball", 35 | "url": "https://github.com/Mic92/not-os/archive/9dbd509335bc8f5bdc4bd4cc31925b9152a2680a.tar.gz", 36 | "url_template": "https://github.com///archive/.tar.gz" 37 | }, 38 | "npmlock2nix": { 39 | "branch": "master", 40 | "description": "nixify npm based packages [maintainer=@andir] ", 41 | "homepage": "", 42 | "owner": "nix-community", 43 | "repo": "npmlock2nix", 44 | "rev": "9197bbf397d76059a76310523d45df10d2e4ca81", 45 | "sha256": "1ddfby72fsnpn4yw43g4zxvg6jzlk5hi5cfnnccg9jgw53ckr4xh", 46 | "type": "tarball", 47 | "url": "https://github.com/nix-community/npmlock2nix/archive/9197bbf397d76059a76310523d45df10d2e4ca81.tar.gz", 48 | "url_template": "https://github.com///archive/.tar.gz" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /vm/programs/coreutils/cat.go: -------------------------------------------------------------------------------- 1 | package coreutils 2 | 3 | import ( 4 | "bufio" 5 | "io" 6 | "net/http" 7 | 8 | "github.com/pkg/errors" 9 | "github.com/urfave/cli/v3" 10 | "libdb.so/vm" 11 | "libdb.so/vm/internal/cliprog" 12 | "libdb.so/vm/internal/nsfw" 13 | "libdb.so/vm/programs" 14 | ) 15 | 16 | func init() { 17 | programs.Register(cliprog.Wrap(cat)) 18 | } 19 | 20 | var cat = cli.App{ 21 | Name: "cat", 22 | Usage: "concatenate files and print on the standard output", 23 | UsageText: `cat [FILE]...`, 24 | Flags: []cli.Flag{ 25 | &cli.BoolFlag{ 26 | Name: "no-sixel", 27 | Usage: "print sixel graphics as raw bytes (always true if no img2sixel)", 28 | }, 29 | }, 30 | Action: func(c *cli.Context) error { 31 | if c.NArg() == 0 { 32 | if nsfw.IsEnabled() { 33 | env := vm.EnvironmentFromContext(c.Context) 34 | env.Println("nyaa~") 35 | return nil 36 | } 37 | return errors.New("no files given") 38 | } 39 | 40 | var failed bool 41 | for _, arg := range c.Args().Slice() { 42 | if !printFile(c, arg) { 43 | failed = true 44 | } 45 | } 46 | 47 | if failed { 48 | return errors.New("failed to print one or more files") 49 | } 50 | 51 | return nil 52 | }, 53 | } 54 | 55 | func printFile(c *cli.Context, path string) bool { 56 | env := vm.EnvironmentFromContext(c.Context) 57 | log := vm.LoggerFromContext(c.Context) 58 | 59 | f, err := env.Open(path) 60 | if err != nil { 61 | log.Println("open:", err) 62 | return false 63 | } 64 | defer f.Close() 65 | 66 | mime, r, err := readMIME(f) 67 | if err != nil { 68 | log.Println("readMIME:", err) 69 | return false 70 | } 71 | 72 | switch mime { 73 | case "image/jpeg", "image/png": 74 | if c.Bool("no-sixel") { 75 | break 76 | } 77 | 78 | err := env.Execute(c.Context, env, "img2sixel", path) 79 | if vm.ErrorIsUnknownProgram(err) { 80 | // Just print as raw if img2sixel is not available. 81 | break 82 | } 83 | 84 | if err != nil { 85 | log.Println(err) 86 | return false 87 | } 88 | 89 | return true 90 | } 91 | 92 | if _, err = io.Copy(env.Terminal.Stdout, r); err != nil { 93 | log.Println("io.Copy:", err) 94 | } 95 | 96 | return true 97 | } 98 | 99 | func readMIME(r io.Reader) (string, io.Reader, error) { 100 | buf := bufio.NewReaderSize(r, 512) 101 | 102 | head, err := buf.Peek(512) 103 | if err != nil && err != io.EOF { 104 | return "", nil, errors.Wrap(err, "peek") 105 | } 106 | 107 | return http.DetectContentType(head), buf, nil 108 | } 109 | -------------------------------------------------------------------------------- /site/styles/libadwaita/_palette.scss: -------------------------------------------------------------------------------- 1 | $blue-1: #99c1f1; 2 | $blue-2: #62a0ea; 3 | $blue-3: #3584e4; 4 | $blue-4: #1c71d8; 5 | $blue-5: #1a5fb4; 6 | $green-1: #8ff0a4; 7 | $green-2: #57e389; 8 | $green-3: #33d17a; 9 | $green-4: #2ec27e; 10 | $green-5: #26a269; 11 | $yellow-1: #f9f06b; 12 | $yellow-2: #f8e45c; 13 | $yellow-3: #f6d32d; 14 | $yellow-4: #f5c211; 15 | $yellow-5: #e5a50a; 16 | $orange-1: #ffbe6f; 17 | $orange-2: #ffa348; 18 | $orange-3: #ff7800; 19 | $orange-4: #e66100; 20 | $orange-5: #c64600; 21 | $red-1: #f66151; 22 | $red-2: #ed333b; 23 | $red-3: #e01b24; 24 | $red-4: #c01c28; 25 | $red-5: #a51d2d; 26 | $purple-1: #dc8add; 27 | $purple-2: #c061cb; 28 | $purple-3: #9141ac; 29 | $purple-4: #813d9c; 30 | $purple-5: #613583; 31 | $brown-1: #cdab8f; 32 | $brown-2: #b5835a; 33 | $brown-3: #986a44; 34 | $brown-4: #865e3c; 35 | $brown-5: #63452c; 36 | $light-1: #ffffff; 37 | $light-2: #f6f5f4; 38 | $light-3: #deddda; 39 | $light-4: #c0bfbc; 40 | $light-5: #9a9996; 41 | $dark-1: #77767b; 42 | $dark-2: #5e5c64; 43 | $dark-3: #3d3846; 44 | $dark-4: #241f31; 45 | $dark-5: #000000; 46 | 47 | :root { 48 | --adw-blue-1: #{$blue-1}; 49 | --adw-blue-2: #{$blue-2}; 50 | --adw-blue-3: #{$blue-3}; 51 | --adw-blue-4: #{$blue-4}; 52 | --adw-blue-5: #{$blue-5}; 53 | --adw-green-1: #{$green-1}; 54 | --adw-green-2: #{$green-2}; 55 | --adw-green-3: #{$green-3}; 56 | --adw-green-4: #{$green-4}; 57 | --adw-green-5: #{$green-5}; 58 | --adw-yellow-1: #{$yellow-1}; 59 | --adw-yellow-2: #{$yellow-2}; 60 | --adw-yellow-3: #{$yellow-3}; 61 | --adw-yellow-4: #{$yellow-4}; 62 | --adw-yellow-5: #{$yellow-5}; 63 | --adw-orange-1: #{$orange-1}; 64 | --adw-orange-2: #{$orange-2}; 65 | --adw-orange-3: #{$orange-3}; 66 | --adw-orange-4: #{$orange-4}; 67 | --adw-orange-5: #{$orange-5}; 68 | --adw-red-1: #{$red-1}; 69 | --adw-red-2: #{$red-2}; 70 | --adw-red-3: #{$red-3}; 71 | --adw-red-4: #{$red-4}; 72 | --adw-red-5: #{$red-5}; 73 | --adw-purple-1: #{$purple-1}; 74 | --adw-purple-2: #{$purple-2}; 75 | --adw-purple-3: #{$purple-3}; 76 | --adw-purple-4: #{$purple-4}; 77 | --adw-purple-5: #{$purple-5}; 78 | --adw-brown-1: #{$brown-1}; 79 | --adw-brown-2: #{$brown-2}; 80 | --adw-brown-3: #{$brown-3}; 81 | --adw-brown-4: #{$brown-4}; 82 | --adw-brown-5: #{$brown-5}; 83 | --adw-light-1: #{$light-1}; 84 | --adw-light-2: #{$light-2}; 85 | --adw-light-3: #{$light-3}; 86 | --adw-light-4: #{$light-4}; 87 | --adw-light-5: #{$light-5}; 88 | --adw-dark-1: #{$dark-1}; 89 | --adw-dark-2: #{$dark-2}; 90 | --adw-dark-3: #{$dark-3}; 91 | --adw-dark-4: #{$dark-4}; 92 | --adw-dark-5: #{$dark-5}; 93 | } 94 | -------------------------------------------------------------------------------- /site/components/Portfolio/sections/88x31Badge.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 | 26 | 27 | 51 | 52 | 101 | -------------------------------------------------------------------------------- /vm/fs/httpfs/fsimpl.go: -------------------------------------------------------------------------------- 1 | package httpfs 2 | 3 | import ( 4 | "io" 5 | "io/fs" 6 | "sort" 7 | "time" 8 | ) 9 | 10 | type fsFileInfo struct { 11 | name string 12 | size int64 13 | dir bool 14 | } 15 | 16 | var _ fs.FileInfo = fsFileInfo{} 17 | 18 | func dirInfo(name string) fsFileInfo { 19 | return fsFileInfo{ 20 | name: name, 21 | size: 0, 22 | dir: true, 23 | } 24 | } 25 | 26 | func fileInfo(name string, info FileInfo) fsFileInfo { 27 | return fsFileInfo{ 28 | name: name, 29 | size: int64(info.Size), 30 | } 31 | } 32 | 33 | func (stat fsFileInfo) Name() string { return stat.name } 34 | func (stat fsFileInfo) Size() int64 { return stat.size } 35 | func (stat fsFileInfo) ModTime() time.Time { return time.Time{} } 36 | func (stat fsFileInfo) IsDir() bool { return stat.dir } 37 | func (stat fsFileInfo) Sys() any { return stat } 38 | func (stat fsFileInfo) Mode() fs.FileMode { 39 | mode := fs.FileMode(0444) 40 | if stat.IsDir() { 41 | mode |= 0111 42 | mode |= fs.ModeDir 43 | } 44 | return mode 45 | } 46 | 47 | func (stat fsFileInfo) Type() fs.FileMode { return stat.Mode().Type() } 48 | func (stat fsFileInfo) Info() (fs.FileInfo, error) { return stat, nil } 49 | 50 | type fsFile struct { 51 | i fsFileInfo 52 | r io.ReadCloser 53 | } 54 | 55 | var _ fs.File = fsFile{} 56 | 57 | func (f fsFile) Stat() (fs.FileInfo, error) { 58 | return f.i, nil 59 | } 60 | 61 | func (f fsFile) Read(b []byte) (int, error) { 62 | return f.r.Read(b) 63 | } 64 | 65 | func (f fsFile) Close() error { 66 | return f.r.Close() 67 | } 68 | 69 | type fsDir struct { 70 | i fsFileInfo 71 | d FileTree 72 | } 73 | 74 | var _ fs.ReadDirFile = fsDir{} 75 | 76 | func (f fsDir) Stat() (fs.FileInfo, error) { 77 | return f.i, nil 78 | } 79 | 80 | func (f fsDir) Read(b []byte) (int, error) { 81 | return 0, fs.ErrInvalid 82 | } 83 | 84 | func (f fsDir) Close() error { return nil } 85 | 86 | func (f fsDir) ReadDir(n int) ([]fs.DirEntry, error) { 87 | i := 0 88 | ents := make([]fs.DirEntry, 0, len(f.d)) 89 | for name, df := range f.d { 90 | var size int64 91 | var isDir bool 92 | 93 | switch df := df.(type) { 94 | case FileInfo: 95 | size = int64(df.Size) 96 | case FileTree: 97 | isDir = true 98 | } 99 | 100 | ents = append(ents, fsFileInfo{ 101 | name: name, 102 | size: size, 103 | dir: isDir, 104 | }) 105 | 106 | i++ 107 | if n > 0 && i >= n { 108 | break 109 | } 110 | } 111 | sort.Slice(ents, func(i, j int) bool { 112 | return ents[i].Name() < ents[j].Name() 113 | }) 114 | return ents, nil 115 | } 116 | -------------------------------------------------------------------------------- /vm/fs/kvfs/localstorage_js.go: -------------------------------------------------------------------------------- 1 | package kvfs 2 | 3 | import ( 4 | "encoding/json" 5 | "io/fs" 6 | "path" 7 | "strings" 8 | "syscall/js" 9 | 10 | "github.com/pkg/errors" 11 | ) 12 | 13 | const localStoragePrefix = "__kvfs" 14 | 15 | // LocalStorage is a simple key-value store that is persisted to the 16 | // browser's local storage. 17 | func LocalStorage() Store { 18 | return localStorage{ 19 | js.Global().Get("localStorage"), 20 | js.Global().Get("Object"), 21 | } 22 | } 23 | 24 | var _ Store = localStorage{} 25 | 26 | type localStorage struct { 27 | js js.Value 28 | object js.Value 29 | } 30 | 31 | func (s localStorage) Get(fullpath string) (StoredValue, error) { 32 | fullpath = path.Clean("/" + fullpath) 33 | key := localStoragePrefix + fullpath 34 | 35 | jsv := s.js.Call("getItem", key) 36 | if jsv.IsNull() { 37 | return nil, fs.ErrNotExist 38 | } 39 | 40 | v, err := UnmarshalStoredValue([]byte(jsv.String())) 41 | if err != nil { 42 | return nil, errors.Wrap(err, "file contains invalid json") 43 | } 44 | 45 | return v, nil 46 | } 47 | 48 | func (s localStorage) Set(fullpath string, v StoredValue) error { 49 | fullpath = path.Clean("/" + fullpath) 50 | key := localStoragePrefix + fullpath 51 | 52 | b, err := json.Marshal(v) 53 | if err != nil { 54 | return errors.Wrap(err, "failed to marshal value") 55 | } 56 | 57 | s.js.Call("setItem", key, string(b)) 58 | 59 | return nil 60 | } 61 | 62 | func (s localStorage) Delete(fullpath string) error { 63 | fullpath = path.Clean("/" + fullpath) 64 | key := localStoragePrefix + fullpath 65 | 66 | s.js.Call("removeItem", key) 67 | return nil 68 | } 69 | 70 | func (s localStorage) List(prefix string, recursive bool) ([]PathedStoreValue, error) { 71 | localPrefix := localStoragePrefix + prefix 72 | 73 | var values []PathedStoreValue 74 | for i := 0; i < s.js.Get("length").Int(); i++ { 75 | k := s.js.Call("key", i).String() 76 | if !strings.HasPrefix(k, localPrefix) { 77 | continue 78 | } 79 | 80 | filepath := strings.TrimPrefix(k, localStoragePrefix) 81 | filename := strings.TrimPrefix(filepath, prefix) 82 | if !recursive && strings.Contains(filename, "/") { 83 | // Not a direct child. Skip. 84 | continue 85 | } 86 | 87 | jsv := s.js.Call("getItem", k) 88 | if jsv.IsNull() { 89 | // Shouldn't happen, but whatever. 90 | continue 91 | } 92 | 93 | v, err := UnmarshalStoredValue([]byte(jsv.String())) 94 | if err != nil { 95 | return nil, errors.Wrap(err, "file contains invalid json") 96 | } 97 | 98 | values = append(values, PathedStoreValue{ 99 | StoredValue: v, 100 | Path: filepath, 101 | }) 102 | } 103 | 104 | return values, nil 105 | } 106 | -------------------------------------------------------------------------------- /site/styles/_buttons.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | --adw-button-color: color-mix(in srgb, currentColor 10%, transparent); 3 | --adw-button-hover-color: color-mix(in srgb, currentColor 15%, transparent); 4 | --adw-button-active-color: color-mix(in srgb, currentColor 30%, transparent); 5 | --adw-button-checked-color: color-mix(in srgb, currentColor 30%, transparent); 6 | --adw-button-checked-hover-color: color-mix(in srgb, currentColor 35%, transparent); 7 | --adw-button-checked-active-color: color-mix(in srgb, currentColor 40%, transparent); 8 | 9 | --adw-opaque-button-default-bg: color-mix( 10 | in srgb, 11 | var(--window-bg-color) 85%, 12 | var(--window-fg-color) 13 | ); 14 | 15 | --adw-destructive-button-color: color-mix(in srgb, currentColor 15%, transparent); 16 | --adw-destructive-button-hover-color: color-mix(in srgb, currentColor 20%, transparent); 17 | --adw-destructive-button-active-color: color-mix(in srgb, currentColor 35%, transparent); 18 | --adw-destructive-button-checked-color: color-mix(in srgb, currentColor 35%, transparent); 19 | --adw-destructive-button-checked-hover-color: color-mix(in srgb, currentColor 40%, transparent); 20 | --adw-destructive-button-checked-active-color: color-mix(in srgb, currentColor 45%, transparent); 21 | } 22 | 23 | button, 24 | a[role="button"] { 25 | color: var(--adw-fg-color); 26 | border: none; 27 | cursor: pointer; 28 | box-sizing: border-box; 29 | background-color: var(--adw-button-color); 30 | 31 | text-decoration: none; 32 | transition: all 0.15s ease-in-out; 33 | 34 | &:hover { 35 | background-color: var(--adw-button-hover-color); 36 | text-decoration: none; 37 | } 38 | 39 | &:active { 40 | background-color: var(--adw-button-active-color); 41 | } 42 | 43 | &:checked { 44 | background-color: var(--adw-button-checked-color); 45 | &:hover { 46 | background-color: var(--adw-button-checked-hover-color); 47 | } 48 | &:active { 49 | background-color: var(--adw-button-checked-active-color); 50 | } 51 | } 52 | 53 | &.destructive { 54 | background-color: var(--adw-destructive-button-color); 55 | 56 | &:hover { 57 | background-color: var(--adw-destructive-button-hover-color); 58 | } 59 | 60 | &:active { 61 | background-color: var(--adw-destructive-button-active-color); 62 | } 63 | 64 | &:checked { 65 | background-color: var(--adw-destructive-button-checked-color); 66 | &:hover { 67 | background-color: var(--adw-destructive-button-checked-hover-color); 68 | } 69 | &:active { 70 | background-color: var(--adw-destructive-button-checked-active-color); 71 | } 72 | } 73 | } 74 | } 75 | 76 | a[role="button"] { 77 | &, 78 | &:hover { 79 | text-decoration: none; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /site/components/Oneko/oneko.ts: -------------------------------------------------------------------------------- 1 | import onekoSprite from "./oneko.gif"; 2 | import sakuraSprite from "./sakura.gif"; 3 | 4 | export type SpriteSets = Record; 5 | 6 | export type Character = { 7 | url: string; 8 | spriteSets: SpriteSets; 9 | }; 10 | 11 | export const speed = 10; 12 | 13 | export const oneko: Character = { 14 | url: onekoSprite, 15 | spriteSets: { 16 | idle: [[-3, -3]], 17 | alert: [[-7, -3]], 18 | scratchSelf: [ 19 | [-5, 0], 20 | [-6, 0], 21 | [-7, 0], 22 | ], 23 | scratchWallN: [ 24 | [0, 0], 25 | [0, -1], 26 | ], 27 | scratchWallS: [ 28 | [-7, -1], 29 | [-6, -2], 30 | ], 31 | scratchWallE: [ 32 | [-2, -2], 33 | [-2, -3], 34 | ], 35 | scratchWallW: [ 36 | [-4, 0], 37 | [-4, -1], 38 | ], 39 | tired: [[-3, -2]], 40 | sleeping: [ 41 | [-2, 0], 42 | [-2, -1], 43 | ], 44 | N: [ 45 | [-1, -2], 46 | [-1, -3], 47 | ], 48 | NE: [ 49 | [0, -2], 50 | [0, -3], 51 | ], 52 | E: [ 53 | [-3, 0], 54 | [-3, -1], 55 | ], 56 | SE: [ 57 | [-5, -1], 58 | [-5, -2], 59 | ], 60 | S: [ 61 | [-6, -3], 62 | [-7, -2], 63 | ], 64 | SW: [ 65 | [-5, -3], 66 | [-6, -1], 67 | ], 68 | W: [ 69 | [-4, -2], 70 | [-4, -3], 71 | ], 72 | NW: [ 73 | [-1, 0], 74 | [-1, -1], 75 | ], 76 | }, 77 | }; 78 | 79 | export const sakura: Character = { 80 | url: sakuraSprite, 81 | spriteSets: { 82 | idle: [[-3, -3]], 83 | alert: [[-7, -3]], 84 | scratchSelf: [ 85 | [-5, 0], 86 | [-6, 0], 87 | [-7, 0], 88 | ], 89 | scratchWallN: [ 90 | [0, 0], 91 | [0, -1], 92 | ], 93 | scratchWallS: [ 94 | [-7, -1], 95 | [-6, -2], 96 | ], 97 | scratchWallE: [ 98 | [-2, -2], 99 | [-2, -3], 100 | ], 101 | scratchWallW: [ 102 | [-4, 0], 103 | [-4, -1], 104 | ], 105 | tired: [[-3, -2]], 106 | sleeping: [ 107 | [-2, 0], 108 | [-2, -1], 109 | ], 110 | N: [ 111 | [-1, -2], 112 | [-1, -3], 113 | ], 114 | NE: [ 115 | [0, -2], 116 | [0, -3], 117 | ], 118 | E: [ 119 | [-3, 0], 120 | [-3, -1], 121 | ], 122 | SE: [ 123 | [-5, -3], 124 | [-6, -0], 125 | ], 126 | S: [ 127 | [-6, -3], 128 | [-7, -2], 129 | ], 130 | SW: [ 131 | [-5, -3], 132 | [-6, -1], 133 | ], 134 | W: [ 135 | [-4, -2], 136 | [-4, -3], 137 | ], 138 | NW: [ 139 | [-1, 0], 140 | [-1, -1], 141 | ], 142 | }, 143 | }; 144 | -------------------------------------------------------------------------------- /vm/fs/rwfs/rofs.go: -------------------------------------------------------------------------------- 1 | package rwfs 2 | 3 | import ( 4 | "io/fs" 5 | "os" 6 | ) 7 | 8 | // ReadOnlyFS wraps a read-only filesystem into a read-writable filesystem. Any 9 | // functions that write to the filesystem will return an error. 10 | func ReadOnlyFS(fs fs.FS) FS { 11 | return rofs{fs} 12 | } 13 | 14 | type rofs struct{ fs fs.FS } 15 | 16 | var _ FS = rofs{} 17 | 18 | func (ro rofs) Open(name string) (fs.File, error) { 19 | f, err := ro.fs.Open(ConvertAbs(name)) 20 | if err != nil { 21 | return nil, err 22 | } 23 | 24 | return roFile{f}, nil 25 | } 26 | 27 | func (ro rofs) OpenFile(name string, flag int, perm fs.FileMode) (File, error) { 28 | if flag != os.O_RDONLY { 29 | return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrPermission} 30 | } 31 | 32 | f, err := ro.Open(ConvertAbs(name)) 33 | if err != nil { 34 | return nil, err 35 | } 36 | 37 | return roFile{f}, nil 38 | } 39 | 40 | func (ro rofs) Remove(name string) error { 41 | return &fs.PathError{Op: "remove", Path: name, Err: fs.ErrPermission} 42 | } 43 | 44 | func (ro rofs) ReadDir(name string) ([]fs.DirEntry, error) { 45 | entries, err := fs.ReadDir(ro.fs, ConvertAbs(name)) 46 | if err != nil { 47 | return nil, err 48 | } 49 | 50 | for i := range entries { 51 | entries[i] = roDirEntry{entries[i]} 52 | } 53 | 54 | return entries, nil 55 | } 56 | 57 | func (ro rofs) Mkdir(name string, perm fs.FileMode) error { 58 | return &fs.PathError{Op: "mkdir", Path: name, Err: fs.ErrPermission} 59 | } 60 | 61 | func (ro rofs) MkdirAll(name string, perm fs.FileMode) error { 62 | return &fs.PathError{Op: "mkdir", Path: name, Err: fs.ErrPermission} 63 | } 64 | 65 | func (ro rofs) RemoveAll(name string) error { 66 | return &fs.PathError{Op: "remove", Path: name, Err: fs.ErrPermission} 67 | } 68 | 69 | type roFile struct{ fs.File } 70 | 71 | var _ File = roFile{} 72 | 73 | // WrapFile wraps a read-only file into a read-writable file. Any functions that 74 | // write to the file will return an error. 75 | func WrapFile(f fs.File) File { 76 | return roFile{f} 77 | } 78 | 79 | func (f roFile) Write([]byte) (int, error) { 80 | return 0, fs.ErrPermission 81 | } 82 | 83 | func (f roFile) Stat() (fs.FileInfo, error) { 84 | s, err := f.File.Stat() 85 | if err != nil { 86 | return nil, err 87 | } 88 | return roStat{s}, nil 89 | } 90 | 91 | type roStat struct{ fs.FileInfo } 92 | 93 | var _ fs.FileInfo = roStat{} 94 | 95 | func (s roStat) Mode() fs.FileMode { 96 | return s.FileInfo.Mode() &^ 0222 97 | } 98 | 99 | type roDirEntry struct{ fs.DirEntry } 100 | 101 | var _ fs.DirEntry = roDirEntry{} 102 | 103 | func (e roDirEntry) Type() fs.FileMode { 104 | return e.DirEntry.Type() &^ 0222 105 | } 106 | 107 | func (e roDirEntry) Info() (fs.FileInfo, error) { 108 | s, err := e.DirEntry.Info() 109 | if err != nil { 110 | return nil, err 111 | } 112 | return roStat{s}, nil 113 | } 114 | -------------------------------------------------------------------------------- /vm/fs/utilfs/relocatefs.go: -------------------------------------------------------------------------------- 1 | package utilfs 2 | 3 | import ( 4 | "io/fs" 5 | "path" 6 | "strings" 7 | "time" 8 | ) 9 | 10 | type relocatedFS struct { 11 | fs fs.FS 12 | newPath string 13 | } 14 | 15 | var _ fs.FS = relocatedFS{} 16 | 17 | // RelocateFS returns a filesystem that relocates all paths in fs from oldPrefix 18 | // to newPrefix. 19 | func RelocateFS(newPath string) func(fs.FS) fs.FS { 20 | return func(fs fs.FS) fs.FS { 21 | return relocatedFS{fs, path.Clean(newPath)} 22 | } 23 | } 24 | 25 | func (r relocatedFS) Open(name string) (fs.File, error) { 26 | name = path.Clean(name) 27 | 28 | // Cases to be handled: 29 | // 30 | // - If newPath is "docs/files", and ReadDir is called with 31 | // "docs", we have to generate a single DirEntry pointing to "files". 32 | // - If newPath is "docs/files", and ReadDir is called with "docs/files", 33 | // then we pass through the entire listing. 34 | 35 | if strings.HasPrefix(name, r.newPath) { 36 | name = strings.TrimPrefix(name, r.newPath) 37 | name = strings.TrimPrefix(name, "/") 38 | name = path.Clean(name) 39 | 40 | return r.fs.Open(name) 41 | } 42 | 43 | if strings.HasPrefix(r.newPath, name) || name == "." { 44 | next := name 45 | name = path.Base(name) 46 | 47 | next = strings.TrimPrefix(r.newPath, next) 48 | next = strings.TrimPrefix(next, "/") 49 | next = path.Clean(next) 50 | next = popFirstPart(next) 51 | 52 | return relocatingDir{r: &r, name: name, next: next}, nil 53 | } 54 | 55 | return nil, fs.ErrNotExist 56 | } 57 | 58 | type relocatingDir struct { 59 | r *relocatedFS 60 | name string 61 | next string 62 | } 63 | 64 | var ( 65 | _ fs.File = (*relocatingDir)(nil) 66 | _ fs.FileInfo = (*relocatingDir)(nil) 67 | _ fs.DirEntry = (*relocatingDir)(nil) 68 | _ fs.ReadDirFile = (*relocatingDir)(nil) 69 | ) 70 | 71 | func (r relocatingDir) Name() string { return r.name } 72 | func (r relocatingDir) Size() int64 { return 0 } 73 | func (r relocatingDir) Mode() fs.FileMode { return fs.ModeDir | fs.ModePerm } 74 | func (r relocatingDir) Type() fs.FileMode { return fs.ModeDir } 75 | func (r relocatingDir) IsDir() bool { return true } 76 | func (r relocatingDir) ModTime() time.Time { return time.Time{} } 77 | func (r relocatingDir) Sys() interface{} { return nil } 78 | func (r relocatingDir) Info() (fs.FileInfo, error) { return r, nil } 79 | func (r relocatingDir) Stat() (fs.FileInfo, error) { return r, nil } 80 | func (r relocatingDir) Read([]byte) (int, error) { return 0, fs.ErrInvalid } 81 | func (r relocatingDir) Close() error { return nil } 82 | 83 | func (r relocatingDir) ReadDir(n int) ([]fs.DirEntry, error) { 84 | return []fs.DirEntry{ 85 | relocatingDir{ 86 | r: r.r, 87 | name: r.next, 88 | }, 89 | }, nil 90 | } 91 | 92 | func popFirstPart(name string) string { 93 | first, _, _ := strings.Cut(name, "/") 94 | return first 95 | } 96 | -------------------------------------------------------------------------------- /site/components/Portfolio/sections/About.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 14 | 15 |
16 |
17 | Diamond 18 |
19 | Hi, this is 20 |

21 | Diamond{#if $nsfw} 22 | :3{:else}!{/if} 23 |

24 |
25 |
26 |

27 | {pronoun("She")} is a software engineer at Google with over 28 | 5 years of experience. 29 |

30 |

31 | {pronoun("She")} considers {pronoun("herself")} the world's biggest "open source 32 | {#if $nsfw}slut{:else}cheerleader{/if}"! 33 | {pronoun("She's")} very passionate about making the world a better place through technology and open 34 | source! 35 |

36 |
37 | 38 | 102 | -------------------------------------------------------------------------------- /vm/fs/rwfs/fsutil.go: -------------------------------------------------------------------------------- 1 | package rwfs 2 | 3 | import ( 4 | "io" 5 | "io/fs" 6 | "os" 7 | "path" 8 | "strings" 9 | 10 | "github.com/pkg/errors" 11 | ) 12 | 13 | // Split splits an absolute path into its components. The first element is 14 | // always the directory within root, meaning it is not empty. If absPath is "/", 15 | // then Split returns []string{}. 16 | func Split(absPath string) []string { 17 | absPath = strings.TrimPrefix(path.Clean("/"+absPath), "/") 18 | if absPath == "" { 19 | return []string{} 20 | } 21 | 22 | return strings.Split(absPath, "/") 23 | } 24 | 25 | // JoinAbs joins a path to an absolute path. 26 | func JoinAbs(parts []string) string { 27 | return "/" + path.Join(parts...) 28 | } 29 | 30 | // ConvertAbs converts an absolute path to an io/fs-compatible path. The 31 | // returned path will still be absolute, but it will not start with a 32 | // leading slash. If the path is the root, "." is returned. 33 | func ConvertAbs(abs string) string { 34 | cleaned := path.Clean(abs) 35 | if !strings.HasPrefix(cleaned, "/") { 36 | return cleaned 37 | } 38 | 39 | cleaned = strings.TrimPrefix(cleaned, "/") 40 | if cleaned == "" { 41 | cleaned = "." 42 | } 43 | 44 | return cleaned 45 | } 46 | 47 | // Copy copies a file at srcPath to dstPath. dstPath and srcPath can be in 48 | // different filesystems. 49 | func Copy(dstFS FS, dstPath string, srcFS fs.FS, srcPath string) error { 50 | srcFile, err := srcFS.Open(srcPath) 51 | if err != nil { 52 | return &fs.PathError{ 53 | Op: "open", 54 | Path: srcPath, 55 | Err: errors.Wrap(err, "failed to open source file"), 56 | } 57 | } 58 | defer srcFile.Close() 59 | 60 | srcStat, err := srcFile.Stat() 61 | if err != nil { 62 | return &fs.PathError{ 63 | Op: "stat", 64 | Path: srcPath, 65 | Err: errors.Wrap(err, "failed to stat source file"), 66 | } 67 | } 68 | 69 | if srcStat.Size() == 0 { 70 | // There's nothing to copy. We're done. 71 | return nil 72 | } 73 | 74 | dstFile, err := dstFS.OpenFile(dstPath, os.O_WRONLY|os.O_CREATE, srcStat.Mode()) 75 | if err != nil { 76 | return &fs.PathError{ 77 | Op: "open", 78 | Path: dstPath, 79 | Err: errors.Wrap(err, "failed to create destination file"), 80 | } 81 | } 82 | defer dstFile.Close() 83 | 84 | if _, err := io.Copy(dstFile, srcFile); err != nil { 85 | return &fs.PathError{ 86 | Op: "copy", 87 | Path: dstPath, 88 | Err: errors.Wrap(err, "failed to copy file"), 89 | } 90 | } 91 | 92 | return nil 93 | } 94 | 95 | // DeduplicateDirEntries removes duplicate directory entries in-place. The first 96 | // entry encountered is kept, and all subsequent entries are removed. 97 | func DeduplicateDirEntries(dirEntries []fs.DirEntry) []fs.DirEntry { 98 | set := make(map[string]struct{}, len(dirEntries)) 99 | deduped := dirEntries[:0] 100 | for _, dirEntry := range dirEntries { 101 | if _, ok := set[dirEntry.Name()]; ok { 102 | continue 103 | } 104 | set[dirEntry.Name()] = struct{}{} 105 | deduped = append(deduped, dirEntry) 106 | } 107 | return deduped 108 | } 109 | -------------------------------------------------------------------------------- /vm/programs/vars/vars.go: -------------------------------------------------------------------------------- 1 | package vars 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "text/tabwriter" 7 | 8 | "github.com/urfave/cli/v3" 9 | "libdb.so/vm" 10 | "libdb.so/vm/internal/cliprog" 11 | "libdb.so/vm/internal/vars" 12 | "libdb.so/vm/programs" 13 | ) 14 | 15 | func init() { 16 | programs.Register(cliprog.Wrap(app)) 17 | } 18 | 19 | var app = cli.App{ 20 | Name: "vars", 21 | Usage: "list, get, and set flag variables", 22 | UsageText: "vars [options] [command]", 23 | Flags: []cli.Flag{ 24 | &cli.BoolFlag{ 25 | Name: "json", 26 | Aliases: []string{"j"}, 27 | Usage: "always print in JSON format", 28 | }, 29 | }, 30 | Commands: []*cli.Command{ 31 | { 32 | Name: "get", 33 | Usage: "get the value of a variable", 34 | UsageText: "vars get [options] ", 35 | Action: get, 36 | }, 37 | { 38 | Name: "set", 39 | Usage: "set the value of a variable", 40 | UsageText: "vars set [options] ", 41 | Action: set, 42 | }, 43 | }, 44 | Action: list, 45 | } 46 | 47 | func list(c *cli.Context) error { 48 | env := vm.EnvironmentFromContext(c.Context) 49 | 50 | variables := vars.Variables() 51 | if c.Bool("json") { 52 | b, _ := json.MarshalIndent(variables, "", " ") 53 | env.Println(string(b)) 54 | return nil 55 | } 56 | 57 | w := tabwriter.NewWriter(env.Terminal.Stdout, 0, 0, 2, ' ', 0) 58 | fmt.Fprintf(w, "%s\t%s\t%s\n", "[key]", "[value]", "[description]") 59 | for _, variable := range variables { 60 | var value json.RawMessage 61 | variable.Get(&value) 62 | fmt.Fprintf(w, "%s\t%s\t%s\n", variable.Key, value, variable.Description) 63 | } 64 | w.Flush() 65 | return nil 66 | } 67 | 68 | func get(c *cli.Context) error { 69 | env := vm.EnvironmentFromContext(c.Context) 70 | log := vm.LoggerFromContext(c.Context) 71 | 72 | name := c.Args().First() 73 | if name == "" { 74 | return cli.Exit("missing variable name", 1) 75 | } 76 | 77 | v := vars.Get(name) 78 | if v == nil { 79 | return cli.Exit(fmt.Sprintf("unknown variable %s", name), 1) 80 | } 81 | 82 | var value json.RawMessage 83 | ok, err := v.Get(&value) 84 | if err != nil { 85 | return cli.Exit(err.Error(), 1) 86 | } 87 | if !ok { 88 | log.Printf("variable %s is not set", name) 89 | return nil 90 | } 91 | 92 | fmt.Fprintln(env.Terminal.Stdout, string(value)) 93 | return nil 94 | } 95 | 96 | func set(c *cli.Context) error { 97 | name := c.Args().Get(0) 98 | if name == "" { 99 | return cli.Exit("missing variable name", 1) 100 | } 101 | 102 | v := vars.Get(name) 103 | if v == nil { 104 | return cli.Exit(fmt.Sprintf("unknown variable %s", name), 1) 105 | } 106 | 107 | value := c.Args().Get(1) 108 | if value == "" { 109 | return cli.Exit("missing variable value", 1) 110 | } 111 | 112 | var jsonValue any 113 | if err := json.Unmarshal([]byte(value), &jsonValue); err != nil { 114 | return cli.Exit(err.Error(), 1) 115 | } 116 | 117 | if err := v.Set(jsonValue); err != nil { 118 | return cli.Exit(err.Error(), 1) 119 | } 120 | 121 | return nil 122 | } 123 | -------------------------------------------------------------------------------- /vm/fs/kvfs/fsimpl.go: -------------------------------------------------------------------------------- 1 | package kvfs 2 | 3 | import ( 4 | "bytes" 5 | "io/fs" 6 | "os" 7 | "path" 8 | "sync/atomic" 9 | "time" 10 | ) 11 | 12 | // dmask is the default mask. 13 | const dmask = 0777 14 | 15 | type fsFileInfo struct { 16 | path string 17 | size int64 18 | time time.Time 19 | mode fs.FileMode 20 | } 21 | 22 | var _ fs.FileInfo = fsFileInfo{} 23 | 24 | func dirInfo(store Store, path string, d StoredDirectory) fsFileInfo { 25 | return fsFileInfo{ 26 | path: path, 27 | time: time.Unix(dirMtime(store, path, d.CreateTime), 0), 28 | mode: dmask | os.ModeDir, 29 | } 30 | } 31 | 32 | func fileInfo(path string, f StoredFile) fsFileInfo { 33 | return fsFileInfo{ 34 | path: path, 35 | size: int64(len(f.Data)), 36 | time: time.Unix(f.ModTime, 0), 37 | mode: dmask, 38 | } 39 | } 40 | 41 | func (stat fsFileInfo) Name() string { return path.Base(stat.path) } 42 | func (stat fsFileInfo) Size() int64 { return stat.size } 43 | func (stat fsFileInfo) ModTime() time.Time { return time.Time{} } 44 | func (stat fsFileInfo) IsDir() bool { return stat.mode&os.ModeDir != 0 } 45 | func (stat fsFileInfo) Sys() any { return stat } 46 | func (stat fsFileInfo) Mode() fs.FileMode { return stat.mode } 47 | 48 | func (stat fsFileInfo) Type() fs.FileMode { return stat.mode.Type() } 49 | func (stat fsFileInfo) Info() (fs.FileInfo, error) { return stat, nil } 50 | 51 | type fsFile struct { 52 | parent *FS 53 | info fsFileInfo 54 | buf bytes.Buffer 55 | flag int // open mode 56 | closed int32 57 | } 58 | 59 | var _ fs.File = (*fsFile)(nil) 60 | 61 | func (f *fsFile) Stat() (fs.FileInfo, error) { 62 | return f.info, nil 63 | } 64 | 65 | func (f *fsFile) Read(b []byte) (int, error) { 66 | if !flagRead(f.flag) { 67 | return 0, fs.ErrPermission 68 | } 69 | return f.buf.Read(b) 70 | } 71 | 72 | func (f *fsFile) Write(b []byte) (int, error) { 73 | if !flagWrite(f.flag) { 74 | return 0, fs.ErrPermission 75 | } 76 | return f.buf.Write(b) 77 | } 78 | 79 | func (f *fsFile) Close() error { 80 | if !atomic.CompareAndSwapInt32(&f.closed, 0, 1) { 81 | return fs.ErrClosed 82 | } 83 | if flagWrite(f.flag) { 84 | return f.parent.write(f) 85 | } 86 | return nil 87 | } 88 | 89 | type fsDir struct { 90 | parent *FS 91 | info fsFileInfo 92 | } 93 | 94 | var _ fs.ReadDirFile = (*fsDir)(nil) 95 | 96 | func (d *fsDir) Stat() (fs.FileInfo, error) { return d.info, nil } 97 | 98 | func (d *fsDir) Read(b []byte) (int, error) { return 0, fs.ErrInvalid } 99 | 100 | func (d *fsDir) Close() error { return nil } 101 | 102 | func (d *fsDir) ReadDir(n int) ([]fs.DirEntry, error) { 103 | return d.parent.readDir(d.info.path, n) 104 | } 105 | 106 | func flagRead(flag int) bool { 107 | return flagHas(flag, os.O_RDONLY, os.O_RDWR) 108 | } 109 | 110 | func flagWrite(flag int) bool { 111 | return flagHas(flag, os.O_WRONLY, os.O_RDWR) 112 | } 113 | 114 | func flagHas(flag int, anyOf ...int) bool { 115 | for _, arg := range anyOf { 116 | if arg < 0b11 { 117 | // Since O_RDONLY starts at 0 (0b00) and O_WRONLY is 1 (0b01), we 118 | // have to AND the flag and compare equality. 119 | if flag&0b11 == arg { 120 | return true 121 | } 122 | continue 123 | } 124 | if flag&arg != 0 { 125 | return true 126 | } 127 | } 128 | return false 129 | } 130 | -------------------------------------------------------------------------------- /vm/global/global.go: -------------------------------------------------------------------------------- 1 | package global 2 | 3 | import ( 4 | "fmt" 5 | "io/fs" 6 | "net/http" 7 | "strings" 8 | 9 | _ "embed" 10 | 11 | "github.com/lucasb-eyer/go-colorful" 12 | "gitlab.com/diamondburned/dotfiles/Scripts/lineprompt/lineprompt" 13 | "libdb.so/vm" 14 | "libdb.so/vm/fs/httpfs" 15 | "libdb.so/vm/fs/kvfs" 16 | "libdb.so/vm/fs/utilfs" 17 | 18 | _ "libdb.so/vm/programs/coreutils" 19 | _ "libdb.so/vm/programs/hewwo" 20 | _ "libdb.so/vm/programs/neofetch" 21 | _ "libdb.so/vm/programs/nsfw" 22 | _ "libdb.so/vm/programs/resume" 23 | _ "libdb.so/vm/programs/sixel" 24 | _ "libdb.so/vm/programs/spew" 25 | _ "libdb.so/vm/programs/termio" 26 | _ "libdb.so/vm/programs/vars" 27 | _ "libdb.so/vm/programs/webring" 28 | ) 29 | 30 | const InitialCwd = "/" 31 | 32 | //go:embed shellrc 33 | var shellrc []byte 34 | 35 | // RootFS is the filesystem that contains default read-only files, such as the 36 | // shellrc file. 37 | func RootFS(extraFSes ...fs.FS) []fs.FS { 38 | fses := make([]fs.FS, 0, 3+len(extraFSes)) 39 | fses = append(fses, coreFS) 40 | fses = append(fses, extraFSes...) 41 | fses = append(fses, onlineFSes...) 42 | return fses 43 | } 44 | 45 | var coreFS = kvfs.New(kvfs.MemoryStorageFromExisting( 46 | map[string]kvfs.StoredValue{ 47 | "/.shellrc": kvfs.StoredFile{Data: shellrc}, 48 | }, 49 | )) 50 | 51 | var httpClient = http.DefaultClient 52 | 53 | var onlineFSes = []fs.FS{ 54 | httpfs.NewFromURL(httpClient, "https://libdb.so/_fs.json"), 55 | utilfs.Apply( 56 | httpfs.NewFromURL(httpClient, "https://docs.0xd14.id/_docsfs.json"), 57 | utilfs.RelocateFS("docs"), 58 | ), 59 | } 60 | 61 | var InitialEnv = vm.EnvironFromMap(map[string]string{ 62 | "TERM": "xterm-256color", 63 | "HOME": "/", 64 | "SITE": "libdb.so", 65 | "SHELL": "github.com/mvdan/sh/v3", 66 | "SHLVL": "1", 67 | }) 68 | 69 | // PromptMonochrome is a monochromic prompter. 70 | func PromptMonochrome(env vm.Environment) string { 71 | return fmt.Sprintf("\n$ libdb.so @ %s\n―❤―▶ ", env.Cwd) 72 | } 73 | 74 | var transBlend = []colorful.Color{ 75 | rgb(85, 205, 252), 76 | rgb(147, 194, 255), 77 | rgb(200, 181, 245), 78 | rgb(234, 171, 217), 79 | rgb(247, 148, 168), 80 | } 81 | 82 | // PromptColored is a colorful prompter. 83 | func PromptColored() vm.PromptFunc { 84 | var s strings.Builder 85 | s.Grow(1024) 86 | 87 | var state struct { 88 | Column int 89 | Dir string 90 | } 91 | 92 | return func(env vm.Environment) string { 93 | q := env.Terminal.Query() 94 | if q.Width == state.Column && state.Dir == env.Cwd { 95 | return s.String() 96 | } 97 | 98 | s.Reset() 99 | 100 | lineprompt.Blend(&s, "", q.Width, transBlend, lineprompt.Opts{ 101 | LOD: 15, 102 | Underline: true, 103 | }) 104 | s.WriteByte('\n') 105 | 106 | line1 := fmt.Sprintf("$ libdb.so @ %s", env.Cwd) 107 | if len(line1) > q.Width { 108 | line1 = line1[:q.Width] 109 | } 110 | 111 | lineprompt.Blend(&s, line1, q.Width, transBlend, lineprompt.Opts{ 112 | LOD: 15, 113 | Underline: false, 114 | }) 115 | s.WriteByte('\n') 116 | 117 | s.WriteString("\033[38;2;85;205;252m―❤―\033[0m\033[38;2;247;157;208m▶\033[m ") 118 | 119 | state.Column = q.Width 120 | state.Dir = env.Cwd 121 | return s.String() 122 | } 123 | } 124 | 125 | func rgb(r, g, b uint8) colorful.Color { 126 | return colorful.Color{ 127 | R: float64(r) / 255, 128 | G: float64(g) / 255, 129 | B: float64(b) / 255, 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /vm/internal/ansi/ansi.go: -------------------------------------------------------------------------------- 1 | package ansi 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | "strings" 7 | 8 | "github.com/leaanthony/go-ansi-parser" 9 | "github.com/mattn/go-runewidth" 10 | "libdb.so/vm" 11 | ) 12 | 13 | const ( 14 | EchoOff = "\x1b[?25l" 15 | EchoOn = "\x1b[?25h" 16 | 17 | ClearLine = "\x1b[2K" 18 | ClearToStart = "\x1b[1K" 19 | ClearToEnd = "\x1b[K" 20 | 21 | MoveCursorToStart = "\x1b[1G" 22 | MoveCursorToEnd = "\x1b[G" 23 | ) 24 | 25 | func MoveCursorUp(n int) string { 26 | return fmt.Sprintf("\x1b[%dA", n) 27 | } 28 | 29 | func MoveCursorDown(n int) string { 30 | return fmt.Sprintf("\x1b[%dB", n) 31 | } 32 | 33 | func MoveCursorRight(n int) string { 34 | return fmt.Sprintf("\x1b[%dC", n) 35 | } 36 | 37 | func MoveCursorLeft(n int) string { 38 | return fmt.Sprintf("\x1b[%dD", n) 39 | } 40 | 41 | var ansiLinkRe = regexp.MustCompile(`(?m)\x1b]8;;([^\x1b]*)\x1b\\([^\x1b]*)\x1b]8;;\x1b\\`) 42 | 43 | const ansiLinkf = "\x1b]8;;%s\x1b\\%s\x1b]8;;\x1b\\" 44 | 45 | func init() { 46 | // keep in sync with the regex 47 | if !ansiLinkRe.MatchString(ansiLinkf) { 48 | panic("ansiLinkf is not in sync with ansiLinkRe") 49 | } 50 | } 51 | 52 | // Link formats a link with the given text and url. 53 | func Link(text, url string) string { 54 | return fmt.Sprintf(ansiLinkf, url, text) 55 | } 56 | 57 | // StringWidth returns the width of the given string, ignoring ANSI escape 58 | // sequences. 59 | func StringWidth(str string) int { 60 | // Undo all the ANSI links. 61 | str = ansiLinkRe.ReplaceAllString(str, "$2") 62 | return ansiLength(str) 63 | } 64 | 65 | func ansiLength(str string) int { 66 | if str == "" { 67 | return 0 68 | } 69 | 70 | parsed, err := ansi.Parse(str) 71 | if err != nil { 72 | return 0 73 | } 74 | 75 | var result int 76 | for _, element := range parsed { 77 | result += runewidth.StringWidth(element.Label) 78 | } 79 | 80 | return result 81 | } 82 | 83 | // PrintAligned prints three strings, left-aligned, center-aligned, and 84 | // right-aligned, with the given width. If width is 0, the terminal width is 85 | // used. 86 | func PrintAligned(env vm.Environment, width int, left, center, right string) { 87 | w := env.Terminal.Stdout 88 | if width == 0 { 89 | width = env.Terminal.Query().Width 90 | } 91 | 92 | leftWidth := StringWidth(left) 93 | rightWidth := StringWidth(right) 94 | centerWidth := StringWidth(center) 95 | halfCenterWidth := centerWidth / 2 96 | 97 | var totalWidth int 98 | if centerWidth > 0 { 99 | totalWidth = max( 100 | 2*(leftWidth+halfCenterWidth+1), 101 | 2*(halfCenterWidth+rightWidth+1), 102 | ) 103 | } else { 104 | totalWidth = leftWidth + rightWidth 105 | } 106 | if totalWidth > width { 107 | centerPad := max((width-centerWidth)/2, 0) 108 | rightPad := max(width-rightWidth, 0) 109 | 110 | if left != "" { 111 | fmt.Fprintln(w, left) 112 | } 113 | 114 | if center != "" { 115 | fmt.Fprint(w, strings.Repeat(" ", centerPad)) 116 | fmt.Fprintln(w, center) 117 | } 118 | 119 | if right != "" { 120 | fmt.Fprint(w, strings.Repeat(" ", rightPad)) 121 | fmt.Fprintln(w, right) 122 | } 123 | 124 | return 125 | } 126 | 127 | pivot := width / 2 128 | leftPad := max(pivot-halfCenterWidth-leftWidth, 1) 129 | rightPad := max(width-leftWidth-leftPad-centerWidth-rightWidth, 1) 130 | 131 | fmt.Fprint(w, left) 132 | fmt.Fprint(w, strings.Repeat(" ", leftPad)) 133 | fmt.Fprint(w, center) 134 | fmt.Fprint(w, strings.Repeat(" ", rightPad)) 135 | fmt.Fprint(w, right) 136 | 137 | fmt.Fprintln(w) 138 | } 139 | 140 | func min(a, b int) int { 141 | if a < b { 142 | return a 143 | } 144 | return b 145 | } 146 | 147 | func max(a, b int) int { 148 | if a > b { 149 | return a 150 | } 151 | return b 152 | } 153 | -------------------------------------------------------------------------------- /jsonld/_render.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs/promises"; 2 | import * as path from "path"; 3 | import { assert } from "./lib/jsonld.js"; 4 | import context from "./_context.json"; 5 | import jsonld, { ContextDefinition } from "jsonld"; 6 | 7 | export const sourceFile = new URL(import.meta.url).pathname; 8 | export const baseDir = path.dirname(sourceFile); 9 | 10 | async function main() { 11 | const files = await fs.readdir(baseDir); 12 | const allNodes: jsonld.NodeObject[] = []; 13 | 14 | for (const file of files) { 15 | const filePath = path.join(baseDir, file); 16 | try { 17 | if (file.startsWith("_")) { 18 | continue; 19 | } 20 | 21 | if (file.endsWith(".jsonld.ts")) { 22 | const nodes = (await import(filePath)).default; 23 | assertNodeObjects(nodes); 24 | allNodes.push(...nodes); 25 | continue; 26 | } 27 | 28 | if (file.endsWith(".jsonld")) { 29 | const content = await fs.readFile(filePath, "utf-8"); 30 | const graph = JSON.parse(content); 31 | assertNodeObjects(graph); 32 | allNodes.push(...graph); 33 | continue; 34 | } 35 | } catch (err) { 36 | throw new Error(`Failed to add TS graph ${file}`, { cause: err }); 37 | } 38 | } 39 | 40 | let document: jsonld.JsonLdDocument = { 41 | "@context": context, 42 | "@graph": expandIDsRecursively(context, allNodes), 43 | }; 44 | 45 | document = await jsonld.compact(document, context, { 46 | compactArrays: true, 47 | compactToRelative: true, 48 | }); 49 | 50 | // jsonld.compact messes up the key order of objects, so we need to reorder them. 51 | document["@graph"] = (document["@graph"] as jsonld.NodeObject[]).map((node, i) => 52 | reorderSameObject(node, allNodes[i]) 53 | ); 54 | 55 | const rendered = JSON.stringify(document, null, 2); 56 | console.log(rendered); 57 | } 58 | 59 | function expandIDsRecursively( 60 | context: ContextDefinition, 61 | obj: T 62 | ): T { 63 | if (Array.isArray(obj)) { 64 | return obj.map((item) => 65 | typeof item === "object" // 66 | ? expandIDsRecursively(context, item as jsonld.NodeObject) 67 | : item 68 | ) as T; 69 | } 70 | 71 | let id = obj["@id"]; 72 | if (!id) { 73 | return obj; 74 | } 75 | 76 | if (typeof id !== "string") { 77 | throw new Error(`Expected @id to be a string, got ${typeof id}`); 78 | } 79 | 80 | if (id.includes(":") && !id.includes("://")) { 81 | const [prefix, suffix] = id.split(":"); 82 | const expanded = context[prefix]; 83 | if (expanded) { 84 | id = expanded + suffix; 85 | } 86 | } 87 | 88 | try { 89 | const url = new URL(id); 90 | id = url.href; 91 | } catch (err) { 92 | throw new Error(`Failed to parse ID as URL "${id}" in object ${JSON.stringify(obj)}`, { 93 | cause: err, 94 | }); 95 | } 96 | 97 | obj["@id"] = id; 98 | 99 | for (const [key, value] of Object.entries(obj)) { 100 | if (typeof value == "object") { 101 | obj[key] = expandIDsRecursively(context, value as jsonld.NodeObject | jsonld.NodeObject[]); 102 | } 103 | } 104 | 105 | return obj; 106 | } 107 | 108 | function reorderSameObject>(unordered: T, ordered: T): T { 109 | const result: T = {} as T; 110 | for (const key of Object.keys(ordered)) { 111 | result[key as keyof T] = unordered[key] as T[keyof T]; 112 | } 113 | return result; 114 | } 115 | 116 | function assertNodeObjects(doc: unknown): asserts doc is jsonld.NodeObject[] { 117 | assert(Array.isArray(doc), "Expected document to be an array"); 118 | assert( 119 | doc.every((item) => typeof item === "object"), 120 | "Expected document to be an array of objects" 121 | ); 122 | } 123 | 124 | await main(); 125 | -------------------------------------------------------------------------------- /vm/programs/webring/webring.go: -------------------------------------------------------------------------------- 1 | package webring 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | "strings" 9 | "time" 10 | 11 | "github.com/urfave/cli/v3" 12 | "libdb.so/libwebring-go" 13 | "libdb.so/vm" 14 | "libdb.so/vm/internal/ansi" 15 | "libdb.so/vm/internal/cliprog" 16 | "libdb.so/vm/programs" 17 | ) 18 | 19 | func init() { 20 | programs.Register(cliprog.Wrap(app)) 21 | } 22 | 23 | var webringURLs = []string{ 24 | "https://raw.githubusercontent.com/diamondburned/acmfriends-webring/%3C3-spring-2023/webring.json", 25 | } 26 | 27 | var app = cli.App{ 28 | Name: "webring", 29 | Usage: "print the webrings of this site", 30 | UsageText: "webring [name]", 31 | Flags: []cli.Flag{ 32 | &cli.BoolFlag{ 33 | Name: "json", 34 | Aliases: []string{"j"}, 35 | Usage: "print as JSON", 36 | }, 37 | &cli.IntFlag{ 38 | Name: "width", 39 | Aliases: []string{"w"}, 40 | Usage: "width of the output", 41 | }, 42 | }, 43 | Action: func(c *cli.Context) error { 44 | return Print(c.Context, Opts{ 45 | JSON: c.Bool("json"), 46 | Width: c.Int("width"), 47 | FilterName: c.Args().First(), 48 | }) 49 | }, 50 | } 51 | 52 | type Opts struct { 53 | JSON bool 54 | Width int 55 | FilterName string 56 | } 57 | 58 | // Print prints the webring. 59 | func Print(ctx context.Context, opts Opts) error { 60 | ctx, cancel := context.WithTimeout(ctx, 3*time.Second) 61 | defer cancel() 62 | 63 | env := vm.EnvironmentFromContext(ctx) 64 | log := vm.LoggerFromContext(ctx) 65 | 66 | webrings := make([]*libwebring.Data, 0, len(webringURLs)) 67 | for _, url := range webringURLs { 68 | w, err := libwebring.FetchData(ctx, url) 69 | if err != nil { 70 | log.Println("cannot fetch webring:", err) 71 | continue 72 | } 73 | 74 | s, err := libwebring.FetchStatusForWebring(ctx, url) 75 | if err == nil { 76 | w.Ring = w.Ring.ExcludeAnomalies(s.Anomalies) 77 | } 78 | 79 | webrings = append(webrings, w) 80 | } 81 | 82 | if len(webrings) == 0 && len(webringURLs) > 0 { 83 | return errors.New("no webrings found") 84 | } 85 | 86 | if opts.FilterName != "" { 87 | webrings = []*libwebring.Data{findWebring(webrings, opts.FilterName)} 88 | if webrings[0] == nil { 89 | return fmt.Errorf("webring %q not found", opts.FilterName) 90 | } 91 | } 92 | 93 | if opts.JSON { 94 | e := json.NewEncoder(env.Terminal.Stdout) 95 | e.SetIndent("", " ") 96 | return e.Encode(webrings) 97 | } 98 | 99 | width := opts.Width 100 | if width == 0 { 101 | width = env.Terminal.Query().Width 102 | } 103 | 104 | ringPrefix := "part of the " 105 | if width < 50 { 106 | ringPrefix = "" 107 | } 108 | 109 | for i, w := range webrings { 110 | ring := w.Name 111 | if w.Root != "" { 112 | ring = ansi.Link(w.Name, w.Root) 113 | } 114 | 115 | mine := findLink(w) 116 | if mine == nil { 117 | log.Println("diamond is no longer in webring", w.Name) 118 | continue 119 | } 120 | 121 | left, right := w.Ring.SurroundingLinks(*mine) 122 | ansi.PrintAligned(env, width, 123 | "← "+ansi.Link(left.Name, ensureScheme(left.Link)), 124 | "─── "+ringPrefix+ring+" webring ───", 125 | ansi.Link(right.Name, ensureScheme(right.Link))+" →", 126 | ) 127 | 128 | if i != len(webrings)-1 { 129 | env.Println() 130 | } 131 | } 132 | 133 | return nil 134 | } 135 | 136 | func ensureScheme(url string) string { 137 | if !strings.Contains(url, "://") { 138 | return "https://" + url 139 | } 140 | return url 141 | } 142 | 143 | func findLink(webring *libwebring.Data) *libwebring.Link { 144 | for i, link := range webring.Ring { 145 | if link.Name == "diamond" || link.Link == "libdb.so" { 146 | return &webring.Ring[i] 147 | } 148 | } 149 | return nil 150 | } 151 | 152 | func findWebring(webrings []*libwebring.Data, name string) *libwebring.Data { 153 | for _, w := range webrings { 154 | if w.Name == name { 155 | return w 156 | } 157 | } 158 | return nil 159 | } 160 | -------------------------------------------------------------------------------- /vm/fs/httpfs/fs.go: -------------------------------------------------------------------------------- 1 | package httpfs 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/fs" 7 | "log" 8 | "net/http" 9 | "net/url" 10 | "path" 11 | "strings" 12 | "sync" 13 | 14 | "github.com/pkg/errors" 15 | "libdb.so/vm/fs/rwfs" 16 | ) 17 | 18 | // FS is a file system that reads from an HTTP server. 19 | type FS struct { 20 | root FileTreeRoot 21 | client httpClient 22 | } 23 | 24 | var _ fs.FS = (*FS)(nil) 25 | 26 | // New returns a new FS that obeys the given file tree. A cache may optionally 27 | // be provided to cache file contents. 28 | func New(client *http.Client, root FileTreeRoot, basePath string) fs.FS { 29 | u, err := url.Parse(basePath) 30 | if err != nil { 31 | panic(fmt.Errorf("httpfs: invalid base path: %w", err)) 32 | } 33 | 34 | return &FS{ 35 | root: root, 36 | client: httpClient{ 37 | client: client, 38 | url: u, 39 | }, 40 | } 41 | } 42 | 43 | func (h *FS) Open(path string) (fs.File, error) { 44 | parts := rwfs.Split(path) 45 | if len(parts) == 0 { 46 | return fsDir{ 47 | i: dirInfo(path), 48 | d: h.root.Tree, 49 | }, nil 50 | } 51 | 52 | current := h.root.Tree 53 | for i, part := range parts { 54 | entry, ok := current[part] 55 | if !ok { 56 | return nil, fs.ErrNotExist 57 | } 58 | 59 | if i == len(parts)-1 { 60 | switch entry := entry.(type) { 61 | case FileInfo: 62 | r, err := h.client.get(rwfs.JoinAbs(parts), entry) 63 | if err != nil { 64 | return nil, errors.Wrap(err, "httpfs: error getting file") 65 | } 66 | return fsFile{ 67 | i: fileInfo(part, entry), 68 | r: r, 69 | }, nil 70 | case FileTree: 71 | return fsDir{ 72 | i: dirInfo(part), 73 | d: entry, 74 | }, nil 75 | } 76 | 77 | break 78 | } 79 | 80 | dir, ok := entry.(FileTree) 81 | if !ok { 82 | return nil, fs.ErrNotExist 83 | } 84 | 85 | current = dir 86 | } 87 | 88 | return nil, fs.ErrNotExist 89 | } 90 | 91 | type unfetchedFS struct { 92 | getRoot func() *FS 93 | client *http.Client 94 | } 95 | 96 | // NewFromURL returns a new FS that fetches the file tree from the given URL. 97 | // The FS is silently fetched in the background. 98 | func NewFromURL(client *http.Client, fsURL string) fs.FS { 99 | getRoot2 := func() (*FS, error) { 100 | fsURL, err := url.Parse(fsURL) 101 | if err != nil { 102 | return nil, fmt.Errorf("parsing fs URL: %w", err) 103 | } 104 | 105 | resp, err := client.Get(fsURL.String()) 106 | if err != nil { 107 | return nil, fmt.Errorf("fetching file tree: %w", err) 108 | } 109 | defer resp.Body.Close() 110 | 111 | var root FileTreeRoot 112 | if err := json.NewDecoder(resp.Body).Decode(&root); err != nil { 113 | return nil, fmt.Errorf("unmarshaling JSON file tree: %w", err) 114 | } 115 | 116 | u, err := url.Parse(root.BaseURL) 117 | if err != nil { 118 | return nil, fmt.Errorf("parsing base URL: %w", err) 119 | } 120 | 121 | return &FS{ 122 | root: root, 123 | client: httpClient{ 124 | client: client, 125 | url: reconcileURL(*u, *fsURL), 126 | }, 127 | }, nil 128 | } 129 | 130 | getRoot := func() *FS { 131 | fs, err := getRoot2() 132 | if err != nil { 133 | log.Println("fs warning:", err) 134 | } 135 | return fs 136 | } 137 | 138 | getRoot = sync.OnceValue(getRoot) 139 | go getRoot() 140 | 141 | return &unfetchedFS{ 142 | client: client, 143 | getRoot: getRoot, 144 | } 145 | } 146 | 147 | func reconcileURL(base, fs url.URL) *url.URL { 148 | if base.Host == "" { 149 | base.Scheme = fs.Scheme 150 | base.Host = fs.Host 151 | } 152 | 153 | if !strings.HasPrefix(base.Path, "/") { 154 | // base does not contain absolute path, so append it to fsDir. 155 | fsDir := path.Dir(fs.Path) 156 | base.Path = path.Join(fsDir, base.Path) 157 | } 158 | 159 | return &base 160 | } 161 | 162 | func (u *unfetchedFS) Open(path string) (fs.File, error) { 163 | f := u.getRoot() 164 | if f == nil { 165 | return nil, fs.ErrNotExist 166 | } 167 | return f.Open(path) 168 | } 169 | -------------------------------------------------------------------------------- /site/lib/views.ts: -------------------------------------------------------------------------------- 1 | import * as store from "svelte/store"; 2 | import { persisted } from "svelte-persisted-store"; 3 | import { writable } from "svelte/store"; 4 | 5 | export type View = "terminal" | "portfolio"; 6 | 7 | export type Window = { 8 | x: number; 9 | y: number; 10 | width: number; 11 | height: number; 12 | }; 13 | 14 | const zeroRect = { x: 0, y: 0, width: 0, height: 0 }; 15 | 16 | export const viewWindows = writable>({ 17 | portfolio: zeroRect, 18 | terminal: zeroRect, 19 | }); 20 | 21 | export const activeViews = persisted>("active_views", { 22 | portfolio: true, 23 | terminal: false, 24 | }); 25 | export const focusedView = persisted("top_view", "portfolio"); 26 | 27 | export function toggleView(view: View) { 28 | focusedView.update((focusedView) => { 29 | activeViews.update((activeViews) => { 30 | if (!activeViews[view]) { 31 | // Currently not active, so bring to focus. 32 | activeViews[view] = true; 33 | focusedView = view; 34 | } else if (view == focusedView) { 35 | // Already on top, so hide it. 36 | activeViews[view] = false; 37 | // Bring the next visible view to focus. If there is none, then set to 38 | // null. 39 | focusedView = 40 | (Object.keys(activeViews).find( 41 | (view) => activeViews[view as View] 42 | ) as View) || null; 43 | } else { 44 | // Otherwise, bring to focus. 45 | focusedView = view; 46 | } 47 | return activeViews; 48 | }); 49 | return focusedView; 50 | }); 51 | } 52 | 53 | export function bringToFocus(view: View): void { 54 | focusedView.set(view); 55 | } 56 | 57 | export function viewIsActive(view: View): store.Readable { 58 | return store.derived(activeViews, (activeViews) => activeViews[view]); 59 | } 60 | 61 | export function viewIsFocused(view: View): store.Readable { 62 | return store.derived(focusedView, (topView) => topView === view); 63 | } 64 | 65 | // Save some states for Show Desktop. 66 | let savedActiveViews: Record | null = null; 67 | let savedTopView: View | null = null; 68 | let showDesktop = false; 69 | 70 | export function toggleShowDesktop() { 71 | if (showDesktop) { 72 | // Save the current states. 73 | savedActiveViews = store.get(activeViews); 74 | savedTopView = store.get(focusedView); 75 | // Hide all views. 76 | activeViews.set({ portfolio: false, terminal: false }); 77 | focusedView.set(null); 78 | } else if (savedActiveViews && savedTopView) { 79 | // Restore the saved states. 80 | activeViews.set(savedActiveViews); 81 | focusedView.set(savedTopView); 82 | } 83 | showDesktop = !showDesktop; 84 | } 85 | 86 | // DragState is the state of a drag operation. It helps implement window 87 | // dragging using the cursor. 88 | export class DragState { 89 | initialOffsetX: number; 90 | initialOffsetY: number; 91 | 92 | constructor( 93 | // posX is the X offset of the window at the start of the drag. 94 | public posX: number, 95 | // posY is the Y offset of the window at the start of the drag. 96 | public posY: number, 97 | // cursorX is the X coordinate of the cursor at the start of the drag. 98 | public cursorX: number, 99 | // cursorY is the Y coordinate of the cursor at the start of the drag. 100 | public cursorY: number, 101 | // setPosition is the callback to set the position of the window. 102 | // The calculated position is passed as arguments. 103 | public readonly setPosition: (x: number, y: number) => void 104 | ) { 105 | this.initialOffsetX = posX - cursorX; 106 | this.initialOffsetY = posY - cursorY; 107 | } 108 | 109 | // update updates the position of the window based on the current cursor 110 | // position. 111 | update(cursorX: number, cursorY: number) { 112 | this.setPosition( 113 | this.initialOffsetX + cursorX, 114 | this.initialOffsetY + cursorY 115 | ); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /vm/terminal.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | "strings" 8 | 9 | "github.com/pkg/errors" 10 | "golang.org/x/crypto/ssh/terminal" 11 | "libdb.so/vm/internal/syncg" 12 | ) 13 | 14 | // IO defines our needed IO files. 15 | type IO struct { 16 | Stdin io.ReadCloser 17 | Stdout io.Writer 18 | Stderr io.Writer 19 | } 20 | 21 | // NoIO returns a new IO instance that discards all input and output. 22 | func NoIO() IO { 23 | return IO{ 24 | Stdin: io.NopCloser(strings.NewReader("")), 25 | Stdout: io.Discard, 26 | Stderr: io.Discard, 27 | } 28 | } 29 | 30 | // NoIOExceptStderr returns a new IO instance that discards all input and 31 | // standard output, but writes standard error to the given writer. 32 | func NoIOExceptStderr(stderr io.Writer) IO { 33 | io := NoIO() 34 | io.Stderr = stderr 35 | return io 36 | } 37 | 38 | func (io IO) makeRaw() (func() error, error) { 39 | stdin, ok := io.Stdin.(*os.File) 40 | if !ok { 41 | return nil, fmt.Errorf("stdin is not a file but %T", io.Stdin) 42 | } 43 | 44 | oldState, err := terminal.MakeRaw(int(stdin.Fd())) 45 | if err != nil { 46 | return nil, errors.Wrap(err, "failed to put terminal in raw mode") 47 | } 48 | 49 | return func() error { 50 | return terminal.Restore(0, oldState) 51 | }, nil 52 | } 53 | 54 | // TerminalQuery is a query to the terminal. It contains relevant terminal info 55 | // needed for various purposes. For the most part, it's an extension to 56 | // TIOCGWINSZ. 57 | type TerminalQuery struct { 58 | // Width is the width of the terminal. 59 | Width int 60 | // Height is the height of the terminal. 61 | Height int 62 | // XPixel is the width of the terminal in pixels. 63 | XPixel int 64 | // YPixel is the height of the terminal in pixels. 65 | YPixel int 66 | // SIXEL is true if the terminal supports SIXEL. 67 | SIXEL bool 68 | } 69 | 70 | type terminalQueryUpdater struct { 71 | current syncg.AtomicValue[TerminalQuery] 72 | subs syncg.Map[chan<- TerminalQuery, struct{}] 73 | } 74 | 75 | func (u *terminalQueryUpdater) set(q TerminalQuery) { 76 | u.current.Store(q) 77 | u.subs.Range(func(ch chan<- TerminalQuery, _ struct{}) bool { 78 | select { 79 | case ch <- q: 80 | default: 81 | } 82 | return true 83 | }) 84 | } 85 | 86 | func (u *terminalQueryUpdater) subscribe(ch chan<- TerminalQuery) { 87 | u.subs.Store(ch, struct{}{}) 88 | } 89 | 90 | func (u *terminalQueryUpdater) unsubscribe(ch chan<- TerminalQuery) { 91 | u.subs.Delete(ch) 92 | } 93 | 94 | // Terminal describes the current terminal state. It is mostly used by the 95 | // programs. 96 | type Terminal struct { 97 | IO 98 | query *terminalQueryUpdater 99 | } 100 | 101 | // NewTerminal creates a new terminal. 102 | func NewTerminal(io IO, query TerminalQuery) Terminal { 103 | var q terminalQueryUpdater 104 | q.set(query) 105 | return Terminal{ 106 | IO: io, 107 | query: &q, 108 | } 109 | } 110 | 111 | // Query returns the current terminal query. 112 | func (t *Terminal) Query() TerminalQuery { 113 | q, _ := t.query.current.Load() 114 | return q 115 | } 116 | 117 | // UpdateQuery updates the terminal query. 118 | func (t *Terminal) UpdateQuery(q TerminalQuery) { 119 | t.query.set(q) 120 | } 121 | 122 | // Subscribe subscribes the given channel to terminal queries. It returns a 123 | // function that, when called, unsubscribes the given channel from terminal 124 | // queries. Queries that cannot be sent to the channel are dropped. 125 | func (t *Terminal) Subscribe(ch chan<- TerminalQuery) func() { 126 | t.query.subscribe(ch) 127 | return func() { t.query.unsubscribe(ch) } 128 | } 129 | 130 | // Write writes to the terminal's stdout. 131 | func (t *Terminal) Write(b []byte) (int, error) { 132 | return t.Stdout.Write(b) 133 | } 134 | 135 | // Read reads from the terminal's stdin. 136 | func (t *Terminal) Read(b []byte) (int, error) { 137 | return t.Stdin.Read(b) 138 | } 139 | 140 | // WithIO returns a new terminal with the given IO. The terminal query is kept. 141 | func (t Terminal) WithIO(io IO) Terminal { 142 | t.IO = io 143 | return t 144 | } 145 | -------------------------------------------------------------------------------- /site/components/Terminal/index.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 | 62 | 63 | 64 |

{$title ? `${$title} – xterm.js` : "xterm.js"}

65 |
72 | {#await initTerminal()} 73 |

Initializing terminal...

74 | {:catch} 75 |

76 | Failed to initialize terminal. Please check DevTools. 77 |

78 | {/await} 79 |
80 |
81 | 82 | 83 | 138 | -------------------------------------------------------------------------------- /gomod2nix.toml: -------------------------------------------------------------------------------- 1 | schema = 3 2 | 3 | [mod] 4 | [mod."github.com/alecthomas/assert/v2"] 5 | version = "v2.4.0" 6 | hash = "sha256-1urmWJ3lozzuwbA4a28YDRn/TfRHpKI1aQaeqZIA+2A=" 7 | [mod."github.com/alecthomas/repr"] 8 | version = "v0.3.0" 9 | hash = "sha256-Epq2as8dLnFyOBEcMG8eh89cZXY8rbP8PKbMLyu2qeU=" 10 | [mod."github.com/apapsch/go-jsonmerge/v2"] 11 | version = "v2.0.0" 12 | hash = "sha256-xp/1B6XUN2EbddBfoUkTV3oTk+34m4kOZP+66HhfLg4=" 13 | [mod."github.com/cpuguy83/go-md2man/v2"] 14 | version = "v2.0.2" 15 | hash = "sha256-OvWCtDsVrYzM84SMQwOXPLBxnWnMC1hDm+KiI6zm3uk=" 16 | [mod."github.com/davecgh/go-spew"] 17 | version = "v1.1.1" 18 | hash = "sha256-nhzSUrE1fCkN0+RL04N4h8jWmRFPPPWbCuDc7Ss0akI=" 19 | [mod."github.com/fatih/color"] 20 | version = "v1.15.0" 21 | hash = "sha256-7b+scFVQeEUoXfeCDd8X2gS8GMoWA+HxjK8wfbypa5s=" 22 | [mod."github.com/go-chi/chi/v5"] 23 | version = "v5.1.0" 24 | hash = "sha256-DpVfbPlJe4zQNdBrirWq9Q37/awSu5zb3IJgcuyEED8=" 25 | [mod."github.com/google/uuid"] 26 | version = "v1.5.0" 27 | hash = "sha256-DasOte4xANR1VND5XEHKGhpGiyYq74TJmNrgWeIRX4U=" 28 | [mod."github.com/hexops/gotextdiff"] 29 | version = "v1.0.3" 30 | hash = "sha256-wVs5uJs2KHU1HnDCDdSe0vIgNZylvs8oNidDxwA3+O0=" 31 | [mod."github.com/leaanthony/go-ansi-parser"] 32 | version = "v1.6.0" 33 | hash = "sha256-d5J2w/pmipy0+cRfShDoCVz8CjwDvGVlZGh8PRP/tm8=" 34 | [mod."github.com/lucasb-eyer/go-colorful"] 35 | version = "v1.0.2" 36 | hash = "sha256-gp4FDhcLvt5fKgmpd3QjWenUyT4iECkSDXMvgJABLzo=" 37 | [mod."github.com/mattn/go-colorable"] 38 | version = "v0.1.13" 39 | hash = "sha256-qb3Qbo0CELGRIzvw7NVM1g/aayaz4Tguppk9MD2/OI8=" 40 | [mod."github.com/mattn/go-isatty"] 41 | version = "v0.0.20" 42 | hash = "sha256-qhw9hWtU5wnyFyuMbKx+7RB8ckQaFQ8D+8GKPkN3HHQ=" 43 | [mod."github.com/mattn/go-runewidth"] 44 | version = "v0.0.3" 45 | hash = "sha256-70SPSC1DLVOVKuHo5ynvuMTHEvlvBj/2gGf33M1Kg1E=" 46 | [mod."github.com/mattn/go-sixel"] 47 | version = "v0.0.5" 48 | hash = "sha256-G1nzUOVCD0gIHJe4XLHzqbThKxF+5olO/NUH4ht5G40=" 49 | [mod."github.com/oapi-codegen/runtime"] 50 | version = "v1.1.1" 51 | hash = "sha256-GRvzpQngQPAy59KvvcwhjYqjx4gttIhOtQKjqfQcNvI=" 52 | [mod."github.com/pkg/errors"] 53 | version = "v0.9.1" 54 | hash = "sha256-mNfQtcrQmu3sNg/7IwiieKWOgFQOVVe2yXgKBpe/wZw=" 55 | [mod."github.com/rivo/uniseg"] 56 | version = "v0.2.0" 57 | hash = "sha256-GLj0jiGrT03Ept4V6FXCN1yeZ/b6PpS3MEXK6rYQ8Eg=" 58 | [mod."github.com/russross/blackfriday/v2"] 59 | version = "v2.1.0" 60 | hash = "sha256-R+84l1si8az5yDqd5CYcFrTyNZ1eSYlpXKq6nFt4OTQ=" 61 | [mod."github.com/soniakeys/quant"] 62 | version = "v1.0.0" 63 | hash = "sha256-6PzSE8lSOPqO7Y7axqmL+7CioDPap+Rgb2ETmhqBoHg=" 64 | [mod."github.com/urfave/cli/v3"] 65 | version = "v3.0.0-alpha2" 66 | hash = "sha256-43FcAPP7zUXwWtp3SrPiq/dzdgI3OiEzk9HuXqYTzUI=" 67 | [mod."github.com/xrash/smetrics"] 68 | version = "v0.0.0-20201216005158-039620a65673" 69 | hash = "sha256-WGHtW/OkLowkqOYIvXpDOpn9wqdH2+Dyx3+rYwpmvzI=" 70 | [mod."gitlab.com/diamondburned/dotfiles/Scripts/lineprompt"] 71 | version = "v0.0.0-20230407082541-a6924ecdc0d4" 72 | hash = "sha256-XSSQXi6y4kDGl5WeKJAWULVfhNaCnk4JCv9l9nIEIMw=" 73 | [mod."golang.org/x/crypto"] 74 | version = "v0.17.0" 75 | hash = "sha256-/vzBaeD/Ymyc7cpjBvSfJfuZ57zWa9LOaZM7b33eIx0=" 76 | [mod."golang.org/x/image"] 77 | version = "v0.6.0" 78 | hash = "sha256-fZc2Jfh6EqsjmMBUMaGHn8+bSMetuZ7K4AxU8BRXt94=" 79 | [mod."golang.org/x/sync"] 80 | version = "v0.6.0" 81 | hash = "sha256-LLims/wjDZtIqlYCVHREewcUOX4hwRwplEuZKPOJ/HI=" 82 | [mod."golang.org/x/sys"] 83 | version = "v0.17.0" 84 | hash = "sha256-e0qnE+SitE02IzvnJKI4Uzpq9EOZY+zvE8Wf5b2e6Kg=" 85 | [mod."golang.org/x/term"] 86 | version = "v0.17.0" 87 | hash = "sha256-lCo7WPHe8Q9q76f0D8FrfoX90MTvwa21O+Dwr1mOAcA=" 88 | [mod."libdb.so/go-mommy"] 89 | version = "v0.1.1" 90 | hash = "sha256-mtgnNTvV2twnZAJ/oGW53l6AiBX+VhXmwH1gjuNl0pI=" 91 | [mod."libdb.so/libwebring-go"] 92 | version = "v0.0.0-20230521133149-d80b3d3c5163" 93 | hash = "sha256-PQjXuoOp27qbNgEm3VRUV4IoCA/ukvPQdfyDsaUz2kM=" 94 | [mod."mvdan.cc/sh/v3"] 95 | version = "v3.8.0" 96 | hash = "sha256-MJvF/6uC5eGKk4po2syUk4d4tKKSOe++xmR2znxVb5s=" 97 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-compat": { 4 | "locked": { 5 | "lastModified": 1696426674, 6 | "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", 7 | "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", 8 | "revCount": 57, 9 | "type": "tarball", 10 | "url": "https://api.flakehub.com/f/pinned/edolstra/flake-compat/1.0.1/018afb31-abd1-7bff-a5e4-cff7e18efb7a/source.tar.gz" 11 | }, 12 | "original": { 13 | "type": "tarball", 14 | "url": "https://flakehub.com/f/edolstra/flake-compat/1.tar.gz" 15 | } 16 | }, 17 | "flake-utils": { 18 | "inputs": { 19 | "systems": "systems" 20 | }, 21 | "locked": { 22 | "lastModified": 1710146030, 23 | "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", 24 | "owner": "numtide", 25 | "repo": "flake-utils", 26 | "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", 27 | "type": "github" 28 | }, 29 | "original": { 30 | "owner": "numtide", 31 | "repo": "flake-utils", 32 | "type": "github" 33 | } 34 | }, 35 | "gomod2nix": { 36 | "inputs": { 37 | "flake-utils": [ 38 | "flake-utils" 39 | ], 40 | "nixpkgs": [ 41 | "nixpkgs" 42 | ] 43 | }, 44 | "locked": { 45 | "lastModified": 1710154385, 46 | "narHash": "sha256-4c3zQ2YY4BZOufaBJB4v9VBBeN2dH7iVdoJw8SDNCfI=", 47 | "owner": "nix-community", 48 | "repo": "gomod2nix", 49 | "rev": "872b63ddd28f318489c929d25f1f0a3c6039c971", 50 | "type": "github" 51 | }, 52 | "original": { 53 | "owner": "nix-community", 54 | "repo": "gomod2nix", 55 | "type": "github" 56 | } 57 | }, 58 | "nixpkgs": { 59 | "locked": { 60 | "lastModified": 1722630782, 61 | "narHash": "sha256-hMyG9/WlUi0Ho9VkRrrez7SeNlDzLxalm9FwY7n/Noo=", 62 | "owner": "nixos", 63 | "repo": "nixpkgs", 64 | "rev": "d04953086551086b44b6f3c6b7eeb26294f207da", 65 | "type": "github" 66 | }, 67 | "original": { 68 | "owner": "nixos", 69 | "ref": "nixos-unstable", 70 | "repo": "nixpkgs", 71 | "type": "github" 72 | } 73 | }, 74 | "npmlock2nix": { 75 | "flake": false, 76 | "locked": { 77 | "lastModified": 1673447413, 78 | "narHash": "sha256-sJM82Sj8yfQYs9axEmGZ9Evzdv/kDcI9sddqJ45frrU=", 79 | "owner": "nix-community", 80 | "repo": "npmlock2nix", 81 | "rev": "9197bbf397d76059a76310523d45df10d2e4ca81", 82 | "type": "github" 83 | }, 84 | "original": { 85 | "owner": "nix-community", 86 | "repo": "npmlock2nix", 87 | "type": "github" 88 | } 89 | }, 90 | "root": { 91 | "inputs": { 92 | "flake-compat": "flake-compat", 93 | "flake-utils": "flake-utils", 94 | "gomod2nix": "gomod2nix", 95 | "nixpkgs": "nixpkgs", 96 | "npmlock2nix": "npmlock2nix", 97 | "yaml-language-server-src": "yaml-language-server-src" 98 | } 99 | }, 100 | "systems": { 101 | "locked": { 102 | "lastModified": 1681028828, 103 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 104 | "owner": "nix-systems", 105 | "repo": "default", 106 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 107 | "type": "github" 108 | }, 109 | "original": { 110 | "owner": "nix-systems", 111 | "repo": "default", 112 | "type": "github" 113 | } 114 | }, 115 | "yaml-language-server-src": { 116 | "flake": false, 117 | "locked": { 118 | "lastModified": 1710758207, 119 | "narHash": "sha256-IcaNN5NBwOSHoSwqVYZYj9GYqerDF3rO+u4YzDtwt9I=", 120 | "owner": "okybr", 121 | "repo": "yaml-language-server", 122 | "rev": "04bb7d7c835ef61df59a23960c2f07d2b6d66e85", 123 | "type": "github" 124 | }, 125 | "original": { 126 | "owner": "okybr", 127 | "repo": "yaml-language-server", 128 | "type": "github" 129 | } 130 | } 131 | }, 132 | "root": "root", 133 | "version": 7 134 | } 135 | -------------------------------------------------------------------------------- /vm/internal/vars/vars.go: -------------------------------------------------------------------------------- 1 | package vars 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "reflect" 7 | "slices" 8 | "strings" 9 | "syscall/js" 10 | ) 11 | 12 | var localStorage = js.Global().Get("localStorage") 13 | var storageEvent = js.Global().Get("StorageEvent") 14 | var dispatchEvent = js.Global().Get("dispatchEvent") 15 | 16 | var knownVariables = map[string]*VariableInfo{} 17 | 18 | // Variables defined in /site/lib/prefs.ts. 19 | var ( 20 | _ = New[bool]("oneko-cursor").WithDescription("Spawn Sakura who follows your cursor.") 21 | _ = New[bool]("drag-windows").WithDescription("Allow dragging windows.") 22 | ) 23 | 24 | // VariableInfo is the information about a variable. 25 | type VariableInfo struct { 26 | // Key is the key of the variable in local storage. 27 | Key string 28 | // Type is the type of the variable. 29 | Type reflect.Type 30 | // Description is the description of the variable. 31 | Description string 32 | // Hidden is whether the variable should be hidden. 33 | Hidden bool 34 | } 35 | 36 | // Variables returns the list of known variables. 37 | func Variables() []VariableInfo { 38 | vars := make([]VariableInfo, 0, len(knownVariables)) 39 | for _, v := range knownVariables { 40 | if v.Hidden { 41 | continue 42 | } 43 | vars = append(vars, *v) 44 | } 45 | slices.SortFunc(vars, func(i, j VariableInfo) int { 46 | return strings.Compare(i.Key, j.Key) 47 | }) 48 | return vars 49 | } 50 | 51 | // Get gets the variable with the given key. Nil is returned if the variable 52 | // does not exist. 53 | func Get(key string) *VariableInfo { 54 | return knownVariables[key] 55 | } 56 | 57 | // Get gets the value of the variable. If the variable does not exist, false is 58 | // returned. 59 | func (v *VariableInfo) Get(value any) (bool, error) { 60 | s := localStorage.Get(v.Key) 61 | if s.IsUndefined() { 62 | return false, nil 63 | } 64 | 65 | if err := json.Unmarshal([]byte(s.String()), value); err != nil { 66 | return false, err 67 | } 68 | 69 | return true, nil 70 | } 71 | 72 | // Set sets the value of the variable. If the value is not of the correct type, 73 | // an error is returned. 74 | func (v *VariableInfo) Set(value any) error { 75 | if reflect.TypeOf(value) != v.Type { 76 | return fmt.Errorf( 77 | "invalid type for variable %s: expected %s, got %s", 78 | v.Key, v.Type, reflect.TypeOf(value)) 79 | } 80 | 81 | s, err := json.Marshal(value) 82 | if err != nil { 83 | return err 84 | } 85 | 86 | oldValue := localStorage.Get("nsfw-v1") 87 | newValue := js.ValueOf(string(s)) 88 | localStorage.Set(v.Key, newValue) 89 | 90 | event := storageEvent.New("storage", map[string]any{ 91 | "key": v.Key, 92 | "oldValue": oldValue, 93 | "newValue": newValue, 94 | }) 95 | dispatchEvent.Invoke(event) 96 | return nil 97 | } 98 | 99 | // Variable is a variable that can be fetched from local storage. 100 | type Variable[T any] struct { 101 | VariableInfo 102 | } 103 | 104 | // New creates a new variable with the given key. 105 | func New[T any](key string) *Variable[T] { 106 | if _, ok := knownVariables[key]; ok { 107 | panic(fmt.Sprintf("variable %s already exists", key)) 108 | } 109 | 110 | var z T 111 | v := &Variable[T]{VariableInfo: VariableInfo{ 112 | Key: key, 113 | Type: reflect.TypeOf(z), 114 | }} 115 | 116 | knownVariables[key] = &v.VariableInfo 117 | return v 118 | } 119 | 120 | // WithDescription sets the description of the variable. 121 | func (v *Variable[T]) WithDescription(desc string) *Variable[T] { 122 | v.Description = desc 123 | return v 124 | } 125 | 126 | // WithHidden sets whether the variable should be hidden. 127 | func (v *Variable[T]) WithHidden(hidden bool) *Variable[T] { 128 | v.Hidden = hidden 129 | return v 130 | } 131 | 132 | // WithDefault sets the default value of the variable. 133 | func (v *Variable[T]) WithDefault(z T) *Variable[T] { 134 | if _, ok := v.Get(); !ok { 135 | v.Set(z) 136 | } 137 | return v 138 | } 139 | 140 | // Get gets the value of the variable. 141 | func (v *Variable[T]) Get() (T, bool) { 142 | var z T 143 | ok, err := v.VariableInfo.Get(&z) 144 | return z, ok && err == nil 145 | } 146 | 147 | // Getz gets the value of the variable, returning the zero value if it is not set. 148 | func (v *Variable[T]) Getz() T { 149 | z, _ := v.Get() 150 | return z 151 | } 152 | 153 | // Set sets the value of the variable. 154 | func (v *Variable[T]) Set(z T) { 155 | if err := v.VariableInfo.Set(z); err != nil { 156 | panic(err) 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /nix/packages/tinygo/0001-Makefile.patch: -------------------------------------------------------------------------------- 1 | From ef066db7f5cb7f551f88fb218c82fc947e464425 Mon Sep 17 00:00:00 2001 2 | From: =?UTF-8?q?Mustafa=20=C3=87al=C4=B1=C5=9Fkan?= 3 | Date: Sun, 3 Jul 2022 14:30:51 +0300 4 | Subject: [PATCH 1/3] Makefile 5 | 6 | 7 | diff --git a/Makefile b/Makefile 8 | index 60a5a574..904d2db5 100644 9 | --- a/Makefile 10 | +++ b/Makefile 11 | @@ -14,11 +14,6 @@ LLVM_VERSIONS = 14 13 12 11 12 | errifempty = $(if $(1),$(1),$(error $(2))) 13 | detect = $(shell which $(call errifempty,$(firstword $(foreach p,$(2),$(shell command -v $(p) 2> /dev/null && echo $(p)))),failed to locate $(1) at any of: $(2))) 14 | toolSearchPathsVersion = $(1)-$(2) 15 | -ifeq ($(shell uname -s),Darwin) 16 | - # Also explicitly search Brew's copy, which is not in PATH by default. 17 | - BREW_PREFIX := $(shell brew --prefix) 18 | - toolSearchPathsVersion += $(BREW_PREFIX)/opt/llvm@$(2)/bin/$(1)-$(2) $(BREW_PREFIX)/opt/llvm@$(2)/bin/$(1) 19 | -endif 20 | # First search for a custom built copy, then move on to explicitly version-tagged binaries, then just see if the tool is in path with its normal name. 21 | findLLVMTool = $(call detect,$(1),$(abspath llvm-build/bin/$(1)) $(foreach ver,$(LLVM_VERSIONS),$(call toolSearchPathsVersion,$(1),$(ver))) $(1)) 22 | CLANG ?= $(call findLLVMTool,clang) 23 | @@ -707,9 +702,8 @@ endif 24 | wasmtest: 25 | $(GO) test ./tests/wasm 26 | 27 | -build/release: tinygo gen-device wasi-libc $(if $(filter 1,$(USE_SYSTEM_BINARYEN)),,binaryen) 28 | +build/release: 29 | @mkdir -p build/release/tinygo/bin 30 | - @mkdir -p build/release/tinygo/lib/clang/include 31 | @mkdir -p build/release/tinygo/lib/CMSIS/CMSIS 32 | @mkdir -p build/release/tinygo/lib/macos-minimal-sdk 33 | @mkdir -p build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common 34 | @@ -721,15 +715,8 @@ build/release: tinygo gen-device wasi-libc $(if $(filter 1,$(USE_SYSTEM_BINARYEN 35 | @mkdir -p build/release/tinygo/lib/picolibc/newlib/libc 36 | @mkdir -p build/release/tinygo/lib/picolibc/newlib/libm 37 | @mkdir -p build/release/tinygo/lib/wasi-libc 38 | - @mkdir -p build/release/tinygo/pkg/thumbv6m-unknown-unknown-eabi-cortex-m0 39 | - @mkdir -p build/release/tinygo/pkg/thumbv6m-unknown-unknown-eabi-cortex-m0plus 40 | - @mkdir -p build/release/tinygo/pkg/thumbv7em-unknown-unknown-eabi-cortex-m4 41 | @echo copying source files 42 | @cp -p build/tinygo$(EXE) build/release/tinygo/bin 43 | -ifneq ($(USE_SYSTEM_BINARYEN),1) 44 | - @cp -p build/wasm-opt$(EXE) build/release/tinygo/bin 45 | -endif 46 | - @cp -p $(abspath $(CLANG_SRC))/lib/Headers/*.h build/release/tinygo/lib/clang/include 47 | @cp -rp lib/CMSIS/CMSIS/Include build/release/tinygo/lib/CMSIS/CMSIS 48 | @cp -rp lib/CMSIS/README.md build/release/tinygo/lib/CMSIS 49 | @cp -rp lib/macos-minimal-sdk/* build/release/tinygo/lib/macos-minimal-sdk 50 | @@ -768,16 +755,9 @@ endif 51 | @cp -rp lib/picolibc/newlib/libm/common build/release/tinygo/lib/picolibc/newlib/libm 52 | @cp -rp lib/picolibc-stdio.c build/release/tinygo/lib 53 | @cp -rp lib/wasi-libc/sysroot build/release/tinygo/lib/wasi-libc/sysroot 54 | - @cp -rp llvm-project/compiler-rt/lib/builtins build/release/tinygo/lib/compiler-rt-builtins 55 | - @cp -rp llvm-project/compiler-rt/LICENSE.TXT build/release/tinygo/lib/compiler-rt-builtins 56 | + @cp -rp lib/compiler-rt-builtins build/release/tinygo/lib/compiler-rt-builtins 57 | @cp -rp src build/release/tinygo/src 58 | @cp -rp targets build/release/tinygo/targets 59 | - ./build/release/tinygo/bin/tinygo build-library -target=cortex-m0 -o build/release/tinygo/pkg/thumbv6m-unknown-unknown-eabi-cortex-m0/compiler-rt compiler-rt 60 | - ./build/release/tinygo/bin/tinygo build-library -target=cortex-m0plus -o build/release/tinygo/pkg/thumbv6m-unknown-unknown-eabi-cortex-m0plus/compiler-rt compiler-rt 61 | - ./build/release/tinygo/bin/tinygo build-library -target=cortex-m4 -o build/release/tinygo/pkg/thumbv7em-unknown-unknown-eabi-cortex-m4/compiler-rt compiler-rt 62 | - ./build/release/tinygo/bin/tinygo build-library -target=cortex-m0 -o build/release/tinygo/pkg/thumbv6m-unknown-unknown-eabi-cortex-m0/picolibc picolibc 63 | - ./build/release/tinygo/bin/tinygo build-library -target=cortex-m0plus -o build/release/tinygo/pkg/thumbv6m-unknown-unknown-eabi-cortex-m0plus/picolibc picolibc 64 | - ./build/release/tinygo/bin/tinygo build-library -target=cortex-m4 -o build/release/tinygo/pkg/thumbv7em-unknown-unknown-eabi-cortex-m4/picolibc picolibc 65 | 66 | release: 67 | tar -czf build/release.tar.gz -C build/release tinygo 68 | -- 69 | 2.37.2 70 | 71 | -------------------------------------------------------------------------------- /site/lib/vm.ts: -------------------------------------------------------------------------------- 1 | import "./wasm_exec.js"; 2 | import consoleBlob from "#/libdb.so/build/vm.wasm?url"; 3 | import type * as xterm from "@xterm/xterm"; 4 | 5 | declare global { 6 | function vm_write_stdin(data: string): void; 7 | function vm_update_terminal(_: { 8 | row: number; 9 | col: number; 10 | xpixel: number; 11 | ypixel: number; 12 | sixel: boolean; 13 | }): void; 14 | function vm_start(): void; 15 | function vm_stop(): void; 16 | function vm_set_public_fs(json: string, basePath: string): void; 17 | function vm_add_public_fs(json: string, basePath: string): void; 18 | function vm_add_public_fs_url(url: string): void; 19 | var console_write: null | ((fd: number, bytes: Uint8Array) => void); 20 | } 21 | 22 | class TerminalProxy { 23 | private onDataDisposer: xterm.IDisposable; 24 | private onResizeDisposer: xterm.IDisposable; 25 | 26 | constructor(public readonly terminal: xterm.Terminal) { 27 | const lineBuffer: number[] = []; 28 | 29 | globalThis.console_write = (fd: number, bytes: Uint8Array) => { 30 | switch (fd) { 31 | case 1: // stdout 32 | this.terminal.write(bytes); 33 | break; 34 | case 2: // stderr 35 | this.terminal.write(bytes); 36 | while (true) { 37 | const index = lineBuffer.indexOf("\n".charCodeAt(0)); 38 | if (index === -1) { 39 | break; 40 | } 41 | console.log("vm:", fd, bytes); 42 | lineBuffer.splice(0, lineBuffer.length); 43 | } 44 | break; 45 | default: 46 | console.log("unknown fd", fd, bytes); 47 | } 48 | }; 49 | 50 | this.onDataDisposer = this.terminal.onData(this.onData.bind(this)); 51 | this.onResizeDisposer = this.terminal.onResize(this.onResize.bind(this)); 52 | } 53 | 54 | reset() { 55 | globalThis.console_write = null; 56 | this.onDataDisposer.dispose(); 57 | this.onResizeDisposer.dispose(); 58 | } 59 | 60 | updateQuery() { 61 | this.onResize(this.terminal); 62 | } 63 | 64 | private onData(data: string) { 65 | const write_stdin = globalThis.vm_write_stdin; 66 | if (write_stdin) { 67 | write_stdin(data); 68 | } else { 69 | console.log("write_stdin is not ready yet"); 70 | } 71 | } 72 | 73 | private onResize(termsz: { rows: number; cols: number }) { 74 | if (globalThis.vm_update_terminal) { 75 | globalThis.vm_update_terminal({ 76 | row: termsz.rows, 77 | col: termsz.cols, 78 | xpixel: 0, 79 | ypixel: 0, 80 | sixel: true, 81 | }); 82 | } else { 83 | console.log("update_terminal is not ready yet"); 84 | } 85 | } 86 | } 87 | 88 | let spawned = false; 89 | let wasmInstance: Promise | null = null; 90 | 91 | export async function start( 92 | terminal: xterm.Terminal, 93 | opts: { 94 | // A list of additional public FS URLs to be added. 95 | publicFSURLs?: string[]; 96 | } = {}, 97 | ) { 98 | if (spawned) { 99 | console.warn("Tried to start VM while it was already running (unsupported)"); 100 | return; 101 | } 102 | 103 | try { 104 | spawned = true; 105 | 106 | // @ts-ignore 107 | const go = new globalThis.Go(); 108 | const proxy = new TerminalProxy(terminal); 109 | 110 | const resp = await fetch(consoleBlob); 111 | const module = await WebAssembly.compileStreaming(resp); 112 | const instance = await WebAssembly.instantiate(module, go.importObject); 113 | 114 | console.log("loaded wasm blob from", consoleBlob); 115 | 116 | console.log("starting wasm..."); 117 | wasmInstance = go.run(instance).catch((err: any) => { 118 | console.error("error running wasm blob", err); 119 | }); 120 | 121 | // Block until vm_start is present. 122 | while (!globalThis.vm_start) { 123 | await new Promise((resolve) => setTimeout(resolve, 0)); 124 | } 125 | 126 | if (opts.publicFSURLs) { 127 | console.log("initialize public httpfs"); 128 | for (const publicFSURL of opts.publicFSURLs) { 129 | globalThis.vm_add_public_fs_url(publicFSURL); 130 | } 131 | } 132 | 133 | console.log("starting console..."); 134 | proxy.updateQuery(); 135 | globalThis.vm_start(); 136 | 137 | console.log("done"); 138 | } catch (err) { 139 | console.error("error starting vm", err); 140 | terminal.write(`Error starting VM: ${err}\r\n`); 141 | 142 | spawned = false; 143 | } 144 | } 145 | 146 | export async function stop() { 147 | if (!spawned) { 148 | return; 149 | } 150 | 151 | globalThis.vm_stop(); 152 | await wasmInstance; 153 | } 154 | -------------------------------------------------------------------------------- /vm/programs/sixel/img2sixel.go: -------------------------------------------------------------------------------- 1 | package sixel 2 | 3 | import ( 4 | "fmt" 5 | "image" 6 | "strings" 7 | 8 | _ "image/jpeg" 9 | _ "image/png" 10 | 11 | "github.com/mattn/go-sixel" 12 | "github.com/pkg/errors" 13 | "github.com/urfave/cli/v3" 14 | "golang.org/x/image/draw" 15 | "libdb.so/vm" 16 | "libdb.so/vm/internal/cliprog" 17 | "libdb.so/vm/programs" 18 | ) 19 | 20 | func init() { 21 | programs.Register(cliprog.Wrap(img2sixel)) 22 | } 23 | 24 | func drawScaler(name string) draw.Scaler { 25 | switch name { 26 | case "nearest": 27 | return draw.NearestNeighbor 28 | case "bilinear": 29 | return draw.BiLinear 30 | case "approx-bilinear": 31 | return draw.ApproxBiLinear 32 | case "cubic", "catmull-rom": 33 | return draw.CatmullRom 34 | default: 35 | return nil 36 | } 37 | } 38 | 39 | var scalerNames = []string{ 40 | "nearest", 41 | "bilinear", 42 | "approx-bilinear", 43 | "cubic", 44 | "catmull-rom (same as cubic)", 45 | } 46 | 47 | var img2sixel = cli.App{ 48 | Name: "img2sixel", 49 | Usage: "convert JPG/PNG to SIXEL", 50 | UsageText: `img2sixel [options...] `, 51 | Flags: []cli.Flag{ 52 | &cli.IntFlag{ 53 | Name: "width", 54 | Usage: "width of the output image in pixels without upscaling", 55 | Value: 400, 56 | }, 57 | &cli.IntFlag{ 58 | Name: "height", 59 | Usage: "height of the output image in pixels without upscaling", 60 | Value: 400, 61 | }, 62 | &cli.BoolFlag{ 63 | Name: "keep-aspect", 64 | Usage: "keep the aspect ratio of the original image, " + 65 | "making --width and --height the maximum values", 66 | Value: true, 67 | }, 68 | &cli.StringFlag{ 69 | Name: "scaler", 70 | Usage: "scaler to use when resizing the image. " + 71 | "one of: " + strings.Join(scalerNames, ", "), 72 | Value: "approx-bilinear", 73 | Action: func(ctx *cli.Context, v string) error { 74 | if drawScaler(v) == nil { 75 | return fmt.Errorf("invalid interpolator: %q", v) 76 | } 77 | return nil 78 | }, 79 | }, 80 | &cli.BoolFlag{ 81 | Name: "dither", 82 | Usage: "enable dithering", 83 | }, 84 | &cli.IntFlag{ 85 | Name: "colors", 86 | Usage: "number of colors to use in the palette, 2-4096", 87 | Value: 256, 88 | }, 89 | }, 90 | Action: func(c *cli.Context) error { 91 | env := vm.EnvironmentFromContext(c.Context) 92 | 93 | path := c.Args().First() 94 | if path == "" { 95 | return &vm.UsageError{Usage: "Usage: " + c.App.UsageText} 96 | } 97 | 98 | colors := c.Int("colors") 99 | if colors < 2 || colors > 4096 { 100 | return errors.Errorf("invalid number of colors: %d", colors) 101 | } 102 | 103 | f, err := env.Open(path) 104 | if err != nil { 105 | return errors.Wrap(err, "open") 106 | } 107 | defer f.Close() 108 | 109 | img, _, err := image.Decode(f) 110 | if err != nil { 111 | return errors.Wrap(err, "image decode") 112 | } 113 | 114 | if c.Int("width") != 0 || c.Int("height") != 0 { 115 | scaler := drawScaler(c.String("scaler")) 116 | 117 | img, err = resize(img, scaler, c.Int("width"), c.Int("height"), c.Bool("keep-aspect")) 118 | if err != nil { 119 | return errors.Wrap(err, "resize") 120 | } 121 | } 122 | 123 | sixelEnc := sixel.NewEncoder(env.Terminal.Stdout) 124 | sixelEnc.Dither = c.Bool("dither") 125 | sixelEnc.Colors = colors 126 | 127 | if err := sixelEnc.Encode(img); err != nil { 128 | return errors.Wrap(err, "error during sixel encode") 129 | } 130 | 131 | fmt.Fprintln(env.Terminal.Stdout) 132 | return nil 133 | }, 134 | } 135 | 136 | func widthFromHeight(original image.Rectangle, height int) int { 137 | return original.Dx() * height / original.Dy() 138 | } 139 | 140 | func heightFromWidth(original image.Rectangle, width int) int { 141 | return original.Dy() * width / original.Dx() 142 | } 143 | 144 | func resize(img image.Image, scaler draw.Scaler, maxW, maxH int, keepAspect bool) (image.Image, error) { 145 | w := img.Bounds().Dx() 146 | h := img.Bounds().Dy() 147 | 148 | if keepAspect { 149 | if w <= maxW && h <= maxH { 150 | return img, nil 151 | } 152 | if w > maxW { 153 | w = maxW 154 | h = heightFromWidth(img.Bounds(), w) 155 | } 156 | if h > maxH { 157 | h = maxH 158 | w = widthFromHeight(img.Bounds(), h) 159 | } 160 | } else { 161 | if w <= maxW && h <= maxH { 162 | return img, nil 163 | } 164 | if maxW == 0 || maxH == 0 { 165 | return nil, errors.New("invalid --width or --height") 166 | } 167 | w = maxW 168 | h = maxH 169 | } 170 | 171 | dst := image.NewRGBA(image.Rect(0, 0, w, h)) 172 | scaler.Scale(dst, dst.Bounds(), img, img.Bounds(), draw.Over, nil) 173 | return dst, nil 174 | } 175 | -------------------------------------------------------------------------------- /vm/programs/resume/resume.json: -------------------------------------------------------------------------------- 1 | { 2 | "selectedTemplate": 6, 3 | "basics": { 4 | "email": "jobs@libdb.so", 5 | "name": "Diamond Dinh", 6 | "website": "libdb.so", 7 | "phone": "(XXX) XXX-XXXX", 8 | "location": { 9 | "address": "Fullerton, CA" 10 | } 11 | }, 12 | "education": [ 13 | { 14 | "institution": "California State University, Fullerton", 15 | "location": "Fullerton, CA", 16 | "studyType": "Bachelors of Science", 17 | "area": "Computer Science", 18 | "startDate": "Aug 2020", 19 | "endDate": "May 2024" 20 | } 21 | ], 22 | "work": [ 23 | { 24 | "highlights": [ 25 | "Developed a high-performance, parallel, and distributed pipeline using Go and Apache Beam to reconcile massive internal log databases with Certificate Transparency logs.", 26 | "Improved the security of the certificate issuance pipeline by ensuring the authenticity of certificates issued by Google and preventing missing entries in Certificate Transparency logs.", 27 | "Achieved exceptional efficiency, with the pipeline processing millions of log entries in under a minute and running at regular intervals for ongoing security assurance." 28 | ], 29 | "company": "Google", 30 | "position": "Software Engineer Intern", 31 | "location": "Sunnyvale, CA", 32 | "startDate": "May 2023", 33 | "endDate": "August 2023" 34 | }, 35 | { 36 | "highlights": [ 37 | "Rewrote and modernized a Kubernetes node monitoring tool in Go and integrated it with internal infrastructure and telemetry collector for real-time tracking and performance analysis.", 38 | "Led the redesign and development, collaborating with the original team to ensure the objectives were met.", 39 | "Achieved more reliable system monitoring with better error handling, consistent status reporting to multiple outputs, and valuable telemetry for internal dashboards, reducing on-call resolution time." 40 | ], 41 | "company": "Google", 42 | "position": "STEP Intern", 43 | "location": "Seattle, WA", 44 | "startDate": "May 2022", 45 | "endDate": "August 2022" 46 | }, 47 | { 48 | "highlights": [ 49 | "Restructured and refactored Go codebase for improved maintainability and adherence to style guides.", 50 | "Designed and implemented an automatic CI/CD pipeline for deploying latest releases to AWS EC2 instances using Nix, enhancing development speed and enabling rapid testing by other teams." 51 | ], 52 | "company": "ShiHoYa Inc.", 53 | "position": "Backend Developer", 54 | "location": "Remote", 55 | "startDate": "Feb 2020", 56 | "endDate": "Sep 2019" 57 | } 58 | ], 59 | "skills": [ 60 | { 61 | "level": "", 62 | "keywords": [ 63 | "Go, Bash, JavaScript, TypeScript, Nix, C++, SQL, HTML, CSS" 64 | ], 65 | "name": "Languages" 66 | }, 67 | { 68 | "keywords": [ 69 | "Linux, Git, Nix, Docker, Kubernetes, PostgreSQL, SQLite, CI/CD, OpenAPI, Apache Beam, Grafana, Telegraf, InfluxDB" 70 | ], 71 | "name": "Technologies" 72 | } 73 | ], 74 | "projects": [ 75 | { 76 | "description": "GUI chat apps made by reverse-engineering Discord with over 1100 stars on GitHub.", 77 | "keywords": [ 78 | "C", 79 | "Go", 80 | "GTK4" 81 | ], 82 | "name": "gtkcord4", 83 | "url": "libdb.so/gtkcord4" 84 | }, 85 | { 86 | "keywords": [ 87 | "Terraform", 88 | "NixOS", 89 | "GitHub Actions" 90 | ], 91 | "name": "acm-aws", 92 | "description": "Infrastructure for ACM at CSUF with CI/CD pipelines and automatic deployment.", 93 | "url": "libdb.so/acm-aws" 94 | }, 95 | { 96 | "keywords": [ 97 | "Go" 98 | ], 99 | "name": "arikawa", 100 | "description": "Discord library with idiomatic Go and modular structure, having over 400 stars on GitHub.", 101 | "url": "libdb.so/arikawa" 102 | }, 103 | { 104 | "keywords": [ 105 | "Python", 106 | "FastAPI", 107 | "OpenAPI", 108 | "Flutter" 109 | ], 110 | "name": "Layover Party", 111 | "url": "layover.party", 112 | "description": "Mobile app to match people with layover flights, won LAHacks 2023 Travel category." 113 | } 114 | ], 115 | "awards": [ 116 | { 117 | "summary": "Won the Travel category with Layover Party: a mobile app to match people with long layover flights for partying and cheaper flights.", 118 | "title": "LA Hacks Winner", 119 | "date": "April 2023" 120 | } 121 | ], 122 | "sections": [ 123 | "templates", 124 | "profile", 125 | "education", 126 | "work", 127 | "skills", 128 | "projects", 129 | "awards" 130 | ] 131 | } 132 | -------------------------------------------------------------------------------- /site/components/Portfolio/index.svelte: -------------------------------------------------------------------------------- 1 | 25 | 26 | 27 |

About

28 | 29 |
30 |
31 | 32 |
33 |
34 | 35 |
36 |
37 | Hey!! You should totally check out the xterm.js window underneath! 38 |
39 | 40 | 41 | 42 | 43 | 44 | {#await resume} 45 | Hang on, I'm loading the rest of the resume! 46 | {:then resume} 47 | 48 | {:catch} 49 | 50 | I couldn't load my resume {":("} 51 |
52 | Maybe the console can help? 53 |
54 | {/await} 55 | 56 | {#await jsonldDoc} 57 | Give me a bit, I'm loading the rest! 58 | {:then doc} 59 | 60 | 61 | {:catch} 62 | 63 | I couldn't load my JSON card-LD information either {":("} 64 |
65 | Maybe the console can help? 66 |
67 | {/await} 68 |
69 |
70 | 71 | 181 | -------------------------------------------------------------------------------- /vm/programs/coreutils/ls.go: -------------------------------------------------------------------------------- 1 | package coreutils 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/fs" 7 | "log" 8 | "path" 9 | "path/filepath" 10 | "slices" 11 | "strings" 12 | "text/tabwriter" 13 | "time" 14 | 15 | stderrors "errors" 16 | 17 | "github.com/fatih/color" 18 | "github.com/pkg/errors" 19 | "github.com/urfave/cli/v3" 20 | "libdb.so/vm" 21 | "libdb.so/vm/internal/ansi" 22 | "libdb.so/vm/internal/cliprog" 23 | "libdb.so/vm/internal/vmutil" 24 | "libdb.so/vm/programs" 25 | ) 26 | 27 | func init() { 28 | programs.Register(cliprog.Wrap(ls)) 29 | } 30 | 31 | var ls = cli.App{ 32 | Name: "ls", 33 | Usage: "list directory contents", 34 | UsageText: `ls [OPTION]... [FILE]...`, 35 | Flags: []cli.Flag{ 36 | &cli.BoolFlag{ 37 | Name: "all", 38 | Aliases: []string{"a"}, 39 | Usage: "do not ignore entries starting with .", 40 | }, 41 | &cli.BoolFlag{ 42 | Name: "long", 43 | Aliases: []string{"l"}, 44 | Usage: "use a long listing format", 45 | }, 46 | &cli.BoolFlag{ 47 | Name: "json", 48 | Usage: "use a JSON listing format", 49 | }, 50 | }, 51 | Action: func(c *cli.Context) error { 52 | args := c.Args().Slice() 53 | if len(args) == 0 { 54 | args = []string{"."} 55 | } 56 | 57 | var errs []error 58 | for _, arg := range args { 59 | if err := ls_(c, arg, len(args) > 1); err != nil { 60 | errs = append(errs, err) 61 | } 62 | } 63 | 64 | return stderrors.Join(errs...) 65 | }, 66 | } 67 | 68 | func ls_(c *cli.Context, arg string, multiple bool) error { 69 | env := vm.EnvironmentFromContext(c.Context) 70 | path := path.Join(env.Cwd, arg) 71 | 72 | stat, err := fs.Stat(env.Filesystem, path) 73 | if err != nil { 74 | return errors.Wrap(err, "stat") 75 | } 76 | 77 | var ents []fs.DirEntry 78 | 79 | if stat.IsDir() { 80 | ents, err = fs.ReadDir(env.Filesystem, path) 81 | if err != nil { 82 | return errors.Wrap(err, "readdir") 83 | } 84 | } else { 85 | ents = []fs.DirEntry{ 86 | fakeDirEntry{stat}, 87 | } 88 | } 89 | 90 | // Put directories first, then files, sorted by name. 91 | slices.SortFunc(ents, func(a, b fs.DirEntry) int { 92 | if a.IsDir() && !b.IsDir() { 93 | return -1 94 | } 95 | if !a.IsDir() && b.IsDir() { 96 | return 1 97 | } 98 | return strings.Compare(a.Name(), b.Name()) 99 | }) 100 | 101 | if multiple { 102 | fmt.Fprintln(env.Terminal.Stdout, arg+":") 103 | } 104 | 105 | if !c.Bool("all") { 106 | filtered := ents[:0] 107 | for _, ent := range ents { 108 | if strings.HasPrefix(ent.Name(), ".") { 109 | continue 110 | } 111 | filtered = append(filtered, ent) 112 | } 113 | ents = filtered 114 | } 115 | 116 | if c.Bool("json") { 117 | lsEnts := make([]lsEntry, len(ents)) 118 | for i, ent := range ents { 119 | lsEnts[i] = lsEntry{ 120 | Name: ent.Name(), 121 | Type: ent.Type(), 122 | IsDir: ent.IsDir(), 123 | } 124 | } 125 | 126 | enc := json.NewEncoder(c.App.Writer) 127 | enc.SetIndent("", " ") 128 | return enc.Encode(lsEnts) 129 | } 130 | 131 | if c.Bool("long") { 132 | w := tabwriter.NewWriter(c.App.Writer, 0, 0, 1, ' ', 0) 133 | 134 | for _, ent := range ents { 135 | var modTime time.Time 136 | var mode fs.FileMode 137 | var size int64 138 | 139 | s, err := ent.Info() 140 | if err != nil { 141 | log.Println("stat:", err) 142 | } else { 143 | modTime = s.ModTime() 144 | mode = s.Mode() 145 | size = s.Size() 146 | } 147 | 148 | fmt.Fprintf(w, 149 | "%s\t%d\t%s\t%s\n", 150 | printPerm(mode), size, printTime(modTime), printName(env, path, ent), 151 | ) 152 | } 153 | 154 | return w.Flush() 155 | } 156 | 157 | for _, ent := range ents { 158 | fmt.Fprintln(c.App.Writer, printName(env, path, ent)) 159 | } 160 | 161 | return nil 162 | } 163 | 164 | func printName(env vm.Environment, base string, dirEntry fs.DirEntry) string { 165 | name := dirEntry.Name() 166 | path := filepath.Join(base, name) 167 | if env.HasTerminal { 168 | // TODO: skip either cd or cat if there is already input in the command 169 | // prompt. This would require exposing *prompter in vm.Environment. 170 | var cmd string 171 | if dirEntry.IsDir() { 172 | cmd = "cd " + path 173 | } else { 174 | cmd = "cat " + path 175 | } 176 | name = ansi.Link(name, vmutil.MakeTerminalWriteURI(cmd)) 177 | if dirEntry.IsDir() { 178 | return color.New(color.FgBlue, color.Bold).Sprint(name) 179 | } 180 | } 181 | return name 182 | } 183 | 184 | func printPerm(mode fs.FileMode) string { 185 | var str [9]byte 186 | perm := mode.Perm() 187 | for i := 0; i < 9; i++ { 188 | if perm&(1<