add fs and notifications to stdlib

This commit is contained in:
grngxd 2025-06-17 12:49:23 +01:00
parent f858daff59
commit 20f413508f
7 changed files with 163 additions and 35 deletions

View file

@ -5,7 +5,7 @@ tmp_dir = "tmp"
[build] [build]
args_bin = [] args_bin = []
bin = "tmp\\main.exe" bin = "tmp\\main.exe"
cmd = "go build -ldflags='-H windowsgui' -o ./tmp/main.exe ./example" cmd = "go build -o ./tmp/main.exe ./example"
delay = 1000 delay = 1000
exclude_dir = ["assets", "tmp", "vendor", "testdata", "runtime/out", "node_modules", "dist"] exclude_dir = ["assets", "tmp", "vendor", "testdata", "runtime/out", "node_modules", "dist"]
exclude_file = [] exclude_file = []

View file

@ -4,7 +4,8 @@
<button onclick="greet()">Greet</button> <button onclick="greet()">Greet</button>
<button onclick="file()">Read File</button> <button onclick="file()">Read File</button>
<button onclick="directory()">List Directory</button> <button onclick="directory()">List Directory</button>
<button onclick="notify()">Notify</button>
<script> <script>
function greet() { function greet() {
tiramisu.invoke("hello", "world") tiramisu.invoke("hello", "world")
@ -27,6 +28,14 @@
.then(files => alert("Files: " + files.join(", "))) .then(files => alert("Files: " + files.join(", ")))
.catch(error => alert("Error listing directory: " + error.message)); .catch(error => alert("Error listing directory: " + error.message));
} }
function notify() {
const message = prompt("Enter the notification message:");
if (!message) return;
tiramisu.notifications.notify(message)
.then(() => alert("Notification sent!"))
.catch(error => alert("Error sending notification: " + error.message));
}
</script> </script>
</body> </body>
</html> </html>

16
go.mod
View file

@ -2,4 +2,18 @@ module git.iwakura.rip/grng/tiramisu
go 1.23.4 go 1.23.4
require github.com/webview/webview_go v0.0.0-20240831120633-6173450d4dd6 // indirect require (
git.sr.ht/~jackmordaunt/go-toast v1.1.2 // indirect
github.com/electricbubble/go-toast v0.3.0 // indirect
github.com/esiqveland/notify v0.13.3 // indirect
github.com/gen2brain/beeep v0.11.1 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/jackmordaunt/icns/v3 v3.0.1 // indirect
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
github.com/sergeymakinen/go-bmp v1.0.0 // indirect
github.com/sergeymakinen/go-ico v1.0.0-beta.0 // indirect
github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af // indirect
github.com/webview/webview_go v0.0.0-20240831120633-6173450d4dd6 // indirect
golang.org/x/sys v0.33.0 // indirect
)

37
go.sum
View file

@ -1,2 +1,39 @@
git.sr.ht/~jackmordaunt/go-toast v1.1.2 h1:/yrfI55LRt1M7H1vkaw+NaH1+L1CDxrqDltwm5euVuE=
git.sr.ht/~jackmordaunt/go-toast v1.1.2/go.mod h1:jA4OqHKTQ4AFBdwrSnwnskUIIS3HYzlJSgdzCKqfavo=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/electricbubble/go-toast v0.3.0 h1:e/PtFkpYNdLANI/utTXzoYQuMYG/N8oHT0Rwal4z/sI=
github.com/electricbubble/go-toast v0.3.0/go.mod h1:6k4ufXmV/AS32EugdLeIXa+2jv6oYbWzv2+UZhB49FI=
github.com/esiqveland/notify v0.13.3 h1:QCMw6o1n+6rl+oLUfg8P1IIDSFsDEb2WlXvVvIJbI/o=
github.com/esiqveland/notify v0.13.3/go.mod h1:hesw/IRYTO0x99u1JPweAl4+5mwXJibQVUcP0Iu5ORE=
github.com/gen2brain/beeep v0.11.1 h1:EbSIhrQZFDj1K2fzlMpAYlFOzV8YuNe721A58XcCTYI=
github.com/gen2brain/beeep v0.11.1/go.mod h1:jQVvuwnLuwOcdctHn/uyh8horSBNJ8uGb9Cn2W4tvoc=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/jackmordaunt/icns/v3 v3.0.1 h1:xxot6aNuGrU+lNgxz5I5H0qSeCjNKp8uTXB1j8D4S3o=
github.com/jackmordaunt/icns/v3 v3.0.1/go.mod h1:5sHL59nqTd2ynTnowxB/MDQFhKNqkK8X687uKNygaSQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sergeymakinen/go-bmp v1.0.0 h1:SdGTzp9WvCV0A1V0mBeaS7kQAwNLdVJbmHlqNWq0R+M=
github.com/sergeymakinen/go-bmp v1.0.0/go.mod h1:/mxlAQZRLxSvJFNIEGGLBE/m40f3ZnUifpgVDlcUIEY=
github.com/sergeymakinen/go-ico v1.0.0-beta.0 h1:m5qKH7uPKLdrygMWxbamVn+tl2HfiA3K6MFJw4GfZvQ=
github.com/sergeymakinen/go-ico v1.0.0-beta.0/go.mod h1:wQ47mTczswBO5F0NoDt7O0IXgnV4Xy3ojrroMQzyhUk=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af h1:6yITBqGTE2lEeTPG04SN9W+iWHCRyHqlVYILiSXziwk=
github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af/go.mod h1:4F09kP5F+am0jAwlQLddpoMDM+iewkxxt6nxUQ5nq5o=
github.com/webview/webview_go v0.0.0-20240831120633-6173450d4dd6 h1:VQpB2SpK88C6B5lPHTuSZKb2Qee1QWwiFlC5CKY4AW0= github.com/webview/webview_go v0.0.0-20240831120633-6173450d4dd6 h1:VQpB2SpK88C6B5lPHTuSZKb2Qee1QWwiFlC5CKY4AW0=
github.com/webview/webview_go v0.0.0-20240831120633-6173450d4dd6/go.mod h1:yE65LFCeWf4kyWD5re+h4XNvOHJEXOCOuJZ4v8l5sgk= github.com/webview/webview_go v0.0.0-20240831120633-6173450d4dd6/go.mod h1:yE65LFCeWf4kyWD5re+h4XNvOHJEXOCOuJZ4v8l5sgk=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

15
runtime/index.d.ts vendored
View file

@ -1,14 +1,25 @@
declare global { declare global {
interface Window { interface Window {
// internal
__TIRAMISU_INTERNAL_invoke: (name: string, ...args: any[]) => Promise<any>; __TIRAMISU_INTERNAL_invoke: (name: string, ...args: any[]) => Promise<any>;
__TIRAMISU_INTERNAL_readFile: (path: string) => Promise<string>;
__TIRAMISU_INTERNAL_readDir: (path: string) => Promise<string[]>; // filesystem
__TIRAMISU_FILESYSTEM_readFile: (path: string) => Promise<string>;
__TIRAMISU_FILESYSTEM_readDir: (path: string) => Promise<string[]>;
__TIRAMISU_INTERNAL_exists: (path: string) => Promise<boolean>;
// notifications
__TIRAMISU_NOTIFICATIONS_notify: (message: string, ico?: string) => Promise<void>;
tiramisu: { tiramisu: {
invoke: (name: string, ...args: any[]) => Promise<any>; invoke: (name: string, ...args: any[]) => Promise<any>;
fs: { fs: {
readFile(path: string): Promise<string>; readFile(path: string): Promise<string>;
readDir(path: string): Promise<string[]>; readDir(path: string): Promise<string[]>;
exists(path: string): Promise<boolean>;
},
notifications: {
notify(message: string, ico?: string): Promise<void>;
} }
}; };
} }

View file

@ -1,9 +1,13 @@
const tiramisu = { const tiramisu = {
invoke: window.__TIRAMISU_INTERNAL_invoke, invoke: window.__TIRAMISU_INTERNAL_invoke,
fs: { fs: {
readFile: window.__TIRAMISU_INTERNAL_readFile, readFile: window.__TIRAMISU_FILESYSTEM_readFile,
readDir: window.__TIRAMISU_INTERNAL_readDir readDir: window.__TIRAMISU_FILESYSTEM_readDir,
} exists: window.__TIRAMISU_INTERNAL_exists,
},
notifications: {
notify: window.__TIRAMISU_NOTIFICATIONS_notify,
},
} }
window.tiramisu = tiramisu window.tiramisu = tiramisu

View file

@ -2,9 +2,11 @@ package tiramisu
import ( import (
"embed" "embed"
"encoding/json"
"fmt" "fmt"
"os" "os"
toast "github.com/electricbubble/go-toast"
wv "github.com/webview/webview_go" wv "github.com/webview/webview_go"
) )
@ -19,6 +21,7 @@ type TiramisuOptions struct {
type FuncHandler func(args ...any) (any, error) type FuncHandler func(args ...any) (any, error)
type Tiramisu struct { type Tiramisu struct {
o TiramisuOptions
w wv.WebView w wv.WebView
funcs map[string]FuncHandler funcs map[string]FuncHandler
} }
@ -29,6 +32,7 @@ func New(o TiramisuOptions) *Tiramisu {
w.SetTitle(o.Title) w.SetTitle(o.Title)
t := &Tiramisu{ t := &Tiramisu{
o: o,
w: w, w: w,
funcs: make(map[string]FuncHandler), funcs: make(map[string]FuncHandler),
} }
@ -104,27 +108,25 @@ func (t *Tiramisu) loadJSRuntime() {
} }
func (t *Tiramisu) loadGoRuntime() { func (t *Tiramisu) loadGoRuntime() {
// internal
t.bind("__TIRAMISU_INTERNAL_invoke", func(args ...any) (any, error) { t.bind("__TIRAMISU_INTERNAL_invoke", func(args ...any) (any, error) {
name, ok := args[0].(string) name, err := ArgAs[string](args, 0)
if !ok { if err != nil {
return nil, fmt.Errorf("first argument must be a string, got %T", args[0]) return nil, err
} }
// pass through any extra args
if len(args) == 1 { if len(args) == 1 {
return t.invoke(name) return t.invoke(name)
} }
return t.invoke(name, args[1:]...) return t.invoke(name, args[1:]...)
}) })
t.bind("__TIRAMISU_INTERNAL_readFile", func(args ...any) (any, error) { // filesystem
if len(args) != 1 { t.bind("__TIRAMISU_FILESYSTEM_readFile", func(args ...any) (any, error) {
return nil, fmt.Errorf("readFile expects exactly one argument, got %d", len(args)) filename, err := ArgAs[string](args, 0)
if err != nil {
return nil, err
} }
filename, ok := args[0].(string)
if !ok {
return nil, fmt.Errorf("readFile expects a string argument, got %T", args[0])
}
data, err := os.ReadFile(filename) data, err := os.ReadFile(filename)
if err != nil { if err != nil {
return nil, fmt.Errorf("error reading file %s: %w", filename, err) return nil, fmt.Errorf("error reading file %s: %w", filename, err)
@ -132,25 +134,76 @@ func (t *Tiramisu) loadGoRuntime() {
return string(data), nil return string(data), nil
}) })
t.bind("__TIRAMISU_INTERNAL_readDir", func(args ...any) (any, error) { t.bind("__TIRAMISU_FILESYSTEM_readDir", func(args ...any) (any, error) {
if len(args) != 1 { dirname, err := ArgAs[string](args, 0)
return nil, fmt.Errorf("readDir expects exactly one argument, got %d", len(args)) if err != nil {
return nil, err
} }
entries, err := os.ReadDir(dirname)
dirname, ok := args[0].(string)
if !ok {
return nil, fmt.Errorf("readDir expects a string argument, got %T", args[0])
}
files, err := os.ReadDir(dirname)
if err != nil { if err != nil {
return nil, fmt.Errorf("error reading directory %s: %w", dirname, err) return nil, fmt.Errorf("error reading directory %s: %w", dirname, err)
} }
var names []string
var fileNames []string for _, e := range entries {
for _, file := range files { names = append(names, e.Name())
fileNames = append(fileNames, file.Name())
} }
return fileNames, nil return names, nil
})
t.bind("__TIRAMISU_INTERNAL_exists", func(args ...any) (any, error) {
path, err := ArgAs[string](args, 0)
if err != nil {
return nil, err
}
_, err = os.Stat(path)
if err != nil {
if os.IsNotExist(err) {
return false, nil
}
return nil, fmt.Errorf("error checking existence of %s: %w", path, err)
}
return true, nil
})
// notifications
t.bind("__TIRAMISU_NOTIFICATIONS_notify", func(args ...any) (any, error) {
msg, err := ArgAs[string](args, 0)
if err != nil {
return nil, err
}
if err := toast.Push(msg); err != nil {
return nil, fmt.Errorf("error sending notification: %w", err)
}
return nil, nil
}) })
} }
func Arg(args []any, i int) (any, error) {
if i < 0 || i >= len(args) {
return nil, fmt.Errorf("arg %d out of range [0..%d]", i, len(args)-1)
}
return args[i], nil
}
func ArgAs[T any](args []any, i int) (T, error) {
var t T
arg, err := Arg(args, i)
if err != nil {
return t, err
}
if v, ok := arg.(T); ok {
return v, nil
}
j, err := json.Marshal(arg)
if err != nil {
return t, fmt.Errorf("cannot marshal arg[%d]: %w", i, err)
}
if err := json.Unmarshal(j, &t); err != nil {
return t, fmt.Errorf("cannot unmarshal arg[%d] -> %T: %w", i, t, err)
}
return t, nil
}