From 2112f5d826e913d7d7481fdaf36ebafe4e9929ec Mon Sep 17 00:00:00 2001 From: Kevin Lyda <kevin@lyda.ie> Date: Tue, 6 May 2025 08:40:13 +0100 Subject: [PATCH] Implement quit and exit --- NOTES.md | 11 +++++++ accounts/accounts.go | 76 +++++++++++++++++++++++++++++++++++++++++--- dclish/dclish.go | 12 ++++--- go.mod | 20 ++++++++++-- go.sum | 25 +++++++++++++++ main.go | 2 +- repl/command.go | 2 ++ repl/misc.go | 28 ++++++++++++++++ 8 files changed, 164 insertions(+), 12 deletions(-) create mode 100644 repl/misc.go diff --git a/NOTES.md b/NOTES.md index 4e0fc81..5b09ab7 100644 --- a/NOTES.md +++ b/NOTES.md @@ -4,10 +4,21 @@ These are the development notes for the Go version. The idea is to use the help files to implement BULLETIN. +ssh `authorized_keys` should look like this: + +``` +command="/home/bulletin/bin/bulletin -u alice",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty KEY-TYPE KEY +``` + +Have readline store a history file for the user. Configure at the `accounts.Open` step. + +Look up how godoc does references to other things. + ## Things to do * Implement a better dclish parser. * Implement each command. + * Next: HELP - move some commands to a Help array. * Decide on how the boards are managed. ## Module links diff --git a/accounts/accounts.go b/accounts/accounts.go index beb1c48..126fd7b 100644 --- a/accounts/accounts.go +++ b/accounts/accounts.go @@ -1,17 +1,85 @@ // Package accounts manages accounts. package accounts -import "errors" +import ( + "database/sql" + "errors" + "fmt" + "os" + "path" + "strings" -// Verify verifies that an account exists. -func Verify(acc string) error { + "github.com/adrg/xdg" + _ "modernc.org/sqlite" // Loads sqlite driver. +) + +// UserData is the type for holding user data. Things like preferences, +// unread message counts, signatures, etc. +type UserData struct { + Account string + FullName string + pref *sql.DB + bull *sql.DB +} + +// User is the user for this process. It is loaded by the `Verify` function. +var User *UserData + +// ValidName makes sure that an account name is a valid name. +func ValidName(acc string) error { if acc == "" { - return errors.New("Empty account is invalid") + return errors.New("empty account is invalid") + } + if strings.ContainsAny(acc, "./") { + return fmt.Errorf("account name '%s' is invalid", acc) + } + return nil +} + +// Open verifies that an account exists. +func Open(acc string) error { + err := ValidName(acc) + if err != nil { + return err + } + User = &UserData{ + Account: acc, } + + prefdir := path.Join(xdg.ConfigHome, "BULLETIN") + err = os.MkdirAll(prefdir, 0700) + if err != nil { + return errors.New("account preference directory problem") + } + User.pref, err = sql.Open("sqlite", path.Join(prefdir, acc, ".db")) + if err != nil { + return errors.New("account preference database problem") + } + + bulldir := path.Join(xdg.ConfigHome, "BULLETIN") + err = os.MkdirAll(bulldir, 0700) + if err != nil { + return errors.New("bulletin directory problem") + } + User.bull, err = sql.Open("sqlite", path.Join(bulldir, acc, ".db")) + if err != nil { + return errors.New("bulletin database problem") + } + return nil } +// Close closes the resources open for the account. +func (u *UserData) Close() { + u.pref.Close() + u.bull.Close() +} + // IsAdmin returns true if the user is an admin func IsAdmin(acc string) bool { + if acc == "admin" { + return true + } + // TODO: Look up account otherwise. return false } diff --git a/dclish/dclish.go b/dclish/dclish.go index aae2845..f7fbe85 100644 --- a/dclish/dclish.go +++ b/dclish/dclish.go @@ -7,7 +7,7 @@ import ( ) // ActionFunc is the function that a command runs. -type ActionFunc func(string, *Command) error +type ActionFunc func(*Command) error // Flag is a flag for a command. type Flag struct { @@ -36,12 +36,16 @@ type Commands []*Command func (c Commands) ParseAndRun(line string) error { // TODO: this doesn't handle a DCL command line completely. words := strings.Fields(line) - fmt.Printf("TODO ParseAndRun sees: %s\n", words) + fmt.Printf("TODO ParseAndRun need to parse flags: %s\n", words) cmd := strings.ToUpper(words[0]) for i := range c { if c[i].Command == cmd { - fmt.Printf("Command help:\n%s\n", c[i].Description) - return nil + if c[i].Action == nil { + fmt.Printf("Command not implemented:\n%s\n", c[i].Description) + return nil + } + err := c[i].Action(c[i]) + return err } } fmt.Printf("ERROR: Unknown command '%s'\n", cmd) diff --git a/go.mod b/go.mod index a7c9849..f7faa44 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,21 @@ module git.lyda.ie/kevin/bulletin go 1.24.2 require ( - github.com/chzyer/readline v1.5.1 // indirect - github.com/urfave/cli/v3 v3.3.2 // indirect - golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 // indirect + github.com/adrg/xdg v0.5.3 + github.com/chzyer/readline v1.5.1 + github.com/urfave/cli/v3 v3.3.2 + modernc.org/sqlite v1.37.0 +) + +require ( + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/ncruces/go-strftime v0.1.9 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect + golang.org/x/sys v0.33.0 // indirect + modernc.org/libc v1.65.0 // indirect + modernc.org/mathutil v1.7.1 // indirect + modernc.org/memory v1.10.0 // indirect ) diff --git a/go.sum b/go.sum index e3a5fcc..cd2ef07 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,33 @@ +github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78= +github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ= github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI= github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/urfave/cli/v3 v3.3.2 h1:BYFVnhhZ8RqT38DxEYVFPPmGFTEf7tJwySTXsVRrS/o= github.com/urfave/cli/v3 v3.3.2/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZtpYpo= +golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI= +golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 h1:y/woIyUBFbpQGKS0u1aHF/40WUDnek3fPOyD08H5Vng= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.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= +modernc.org/libc v1.65.0 h1:e183gLDnAp9VJh6gWKdTy0CThL9Pt7MfcR/0bgb7Y1Y= +modernc.org/libc v1.65.0/go.mod h1:7m9VzGq7APssBTydds2zBcxGREwvIGpuUBaKTXdm2Qs= +modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= +modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= +modernc.org/memory v1.10.0 h1:fzumd51yQ1DxcOxSO+S6X7+QTuVU+n8/Aj7swYjFfC4= +modernc.org/memory v1.10.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/sqlite v1.37.0 h1:s1TMe7T3Q3ovQiK2Ouz4Jwh7dw4ZDqbebSDTlSJdfjI= +modernc.org/sqlite v1.37.0/go.mod h1:5YiWv+YviqGMuGw4V+PNplcyaJ5v+vQd7TQOgkACoJM= diff --git a/main.go b/main.go index be02f5d..2ea43e3 100644 --- a/main.go +++ b/main.go @@ -29,7 +29,7 @@ func main() { }, Action: func(_ context.Context, cmd *cli.Command) error { user := cmd.String("user") - err := accounts.Verify(user) + err := accounts.Open(user) if err != nil { return err } diff --git a/repl/command.go b/repl/command.go index dd0a2f9..e1297ea 100644 --- a/repl/command.go +++ b/repl/command.go @@ -874,6 +874,7 @@ is used. Command: "EXIT", Description: `Exits the BULLETIN program. `, + Action: ActionExit, }, { Command: "EXTRACT", @@ -1740,6 +1741,7 @@ as the subject preceeded by "RE: ". Command: "QUIT", Description: `Exits the BULLETIN program. `, + Action: ActionQuit, }, { Command: "SEARCH", diff --git a/repl/misc.go b/repl/misc.go new file mode 100644 index 0000000..f8bb289 --- /dev/null +++ b/repl/misc.go @@ -0,0 +1,28 @@ +// Package repl implements the main event loop. +package repl + +import ( + "fmt" + "os" + + "git.lyda.ie/kevin/bulletin/accounts" + "git.lyda.ie/kevin/bulletin/dclish" +) + +// ActionQuit handles the `QUIT` command. +func ActionQuit(_ *dclish.Command) error { + accounts.User.Close() + fmt.Println("QUIT") + // TODO: IIRC, quit should not update unread data. Check. + os.Exit(0) + return nil +} + +// ActionExit handles the `EXIT` command. +func ActionExit(_ *dclish.Command) error { + accounts.User.Close() + fmt.Println("EXIT") + // TODO: update unread data. + os.Exit(0) + return nil +} -- GitLab