Loading NOTES.md +2 −0 Original line number Diff line number Diff line Loading @@ -34,6 +34,8 @@ sqlite trigger tracing: `.trace stdout --row --profile --stmt --expanded --plain files? How would it work? * Cleanup help output. * Remove the node related flags. * Database * trigger to limit values for 'visibility'; ## Module links Loading dclish/dclish.go +95 −17 Original line number Diff line number Diff line Loading @@ -4,6 +4,7 @@ package dclish import ( "fmt" "strings" "unicode" ) // ActionFunc is the function that a command runs. Loading @@ -11,10 +12,10 @@ type ActionFunc func(*Command) error // Flag is a flag for a command. type Flag struct { OptArg bool Value string Default string Description string // TODO: Toggle bool } // Flags is the list of flags. Loading @@ -34,10 +35,81 @@ type Command struct { // Commands is the full list of commands. type Commands map[string]*Command func split(line string) []string { words := []string{} buf := strings.Builder{} state := "start" for _, c := range line { switch state { case "start": if unicode.IsSpace(c) { continue } if c == '"' { state = "dquote" } else if c == '\'' { state = "squote" } else { state = "raw" buf.WriteRune(c) } case "dquote": if c == '"' { words = append(words, buf.String()) buf.Reset() state = "start" } else { buf.WriteRune(c) } case "squote": if c == '\'' { words = append(words, buf.String()) buf.Reset() state = "start" } else { buf.WriteRune(c) } case "dquote-raw": if c == '"' { state = "raw" } buf.WriteRune(c) case "squote-raw": if c == '\'' { state = "raw" } buf.WriteRune(c) case "raw": if unicode.IsSpace(c) { words = append(words, buf.String()) buf.Reset() state = "start" } else if c == '/' { words = append(words, buf.String()) buf.Reset() state = "raw" buf.WriteRune(c) } else if c == '"' { state = "dquote-raw" buf.WriteRune(c) } else if c == '\'' { state = "squote-raw" buf.WriteRune(c) } else { buf.WriteRune(c) } } } if len(buf.String()) > 0 { words = append(words, buf.String()) } return words } // ParseAndRun parses a command line and runs the command. func (c Commands) ParseAndRun(line string) error { // TODO: this doesn't handle a DCL command line completely. words := strings.Fields(line) words := split(line) cmd, ok := c[strings.ToUpper(words[0])] if !ok { wordup := strings.ToUpper(words[0]) Loading Loading @@ -75,27 +147,33 @@ func (c Commands) ParseAndRun(line string) error { for i := range args { if strings.HasPrefix(args[i], "/") { flag, val, assigned := strings.Cut(args[i], "=") var wordup string if assigned { wordup := strings.ToUpper(flag) wordup = strings.ToUpper(flag) } else { wordup = args[i] } toggleValue := "true" flg, ok := cmd.Flags[wordup] if !ok { wordup = strings.Replace(wordup, "/NO", "/", 1) flg, ok = cmd.Flags[wordup] if !ok { fmt.Printf("ERROR: Flag '%s' not recognised.\n", args[i]) return nil } flg.Value = val } else { wordup := strings.ToUpper(args[i]) value := "true" if strings.HasPrefix(wordup, "/NO") { wordup = strings.Replace(wordup, "/NO", "/", 1) value = "false" toggleValue = "false" } flg, ok := cmd.Flags[wordup] if !ok { fmt.Printf("ERROR: Flag '%s' not recognised.\n", args[i]) if !flg.OptArg && assigned { fmt.Printf("ERROR: Flag '%s' is a toggle.\n", args[i]) return nil } flg.Value = value if flg.OptArg { if assigned { flg.Value = strings.Trim(val, "\"'") } } else { flg.Value = toggleValue } } else { if len(cmd.Args) == cmd.MaxArgs { Loading folders/folders.go +4 −3 Original line number Diff line number Diff line Loading @@ -2,7 +2,6 @@ package folders import ( "database/sql" "embed" "errors" "os" Loading @@ -11,6 +10,7 @@ import ( "github.com/adrg/xdg" "github.com/golang-migrate/migrate/v4" "github.com/golang-migrate/migrate/v4/source/iofs" "github.com/jmoiron/sqlx" // Included to connect to sqlite. _ "github.com/golang-migrate/migrate/v4/database/sqlite" Loading @@ -23,7 +23,7 @@ var fs embed.FS // Store is the store for folders. type Store struct { user string db *sql.DB db *sqlx.DB } // Open opens the folders database. Loading @@ -35,6 +35,7 @@ func Open(user string) (*Store, error) { } fdb := path.Join(fdir, "bboard.db") // Run db migrations if needed. sqldir, err := iofs.New(fs, "sql") if err != nil { return nil, err Loading @@ -50,7 +51,7 @@ func Open(user string) (*Store, error) { m.Close() store := &Store{user: user} store.db, err = sql.Open("sqlite", "file://"+fdb+"?_pragma=foreign_keys(1)") store.db, err = sqlx.Connect("sqlite", "file://"+fdb+"?_pragma=foreign_keys(1)") if err != nil { return nil, errors.New("bulletin database problem") } Loading folders/manage-folders.go 0 → 100644 +75 −0 Original line number Diff line number Diff line // Package folders are all the routines and sql for managing folders. package folders import ( "errors" "fmt" ) // FolderVisibility is the folder visibility level. type FolderVisibility string // Values for FolderVisibility. const ( FolderPublic FolderVisibility = "public" FolderSemiPrivate = "semi-private" FolderPrivate = "private" ) // FolderOptions are a list of folder options. type FolderOptions struct { Always int Brief int Description string Notify int Owner string Readnew int Shownew int System int Expire int Visibility FolderVisibility } // CreateFolder creates a new folder. func (s *Store) CreateFolder(name string, options FolderOptions) error { _, err := s.db.Exec( `INSERT INTO folders (name, always, brief, description, notify, owner, readnew, shownew, system, expire, visibility) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)`, name, options.Always, options.Brief, options.Description, options.Notify, options.Owner, options.Readnew, options.Shownew, options.System, options.Expire, options.Visibility, ) // TODO: process this error a bit more to give a better error message. return err } // DeleteFolder creates a new folder. func (s *Store) DeleteFolder(name string) error { results, err := s.db.Exec("DELETE FROM folders WHERE name=$1", name) // TODO: process this error a bit more to give a better error message. if err != nil { return err } rows, err := results.RowsAffected() if err != nil { return err } if rows == 0 { return errors.New("No such folder found") } if rows != 1 { return fmt.Errorf("Unexpected number (%d) of folders removed", rows) } return nil } folders/sql/1_create_table.up.sql +1 −1 Original line number Diff line number Diff line Loading @@ -86,7 +86,7 @@ INSERT INTO folders (name, description, system, shownew, owner) VALUES ('GENERAL', 'Default general bulletin folder.', 1, 1, 'SYSTEM'); CREATE TABLE co_owners ( folder VARCHAR(25) REFERENCES folders(name) ON UPDATE CASCADE, folder VARCHAR(25) REFERENCES folders(name) ON DELETE CASCADE ON UPDATE CASCADE, owner VARCHAR(25) REFERENCES users(login) ON UPDATE CASCADE, create_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, update_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, Loading Loading
NOTES.md +2 −0 Original line number Diff line number Diff line Loading @@ -34,6 +34,8 @@ sqlite trigger tracing: `.trace stdout --row --profile --stmt --expanded --plain files? How would it work? * Cleanup help output. * Remove the node related flags. * Database * trigger to limit values for 'visibility'; ## Module links Loading
dclish/dclish.go +95 −17 Original line number Diff line number Diff line Loading @@ -4,6 +4,7 @@ package dclish import ( "fmt" "strings" "unicode" ) // ActionFunc is the function that a command runs. Loading @@ -11,10 +12,10 @@ type ActionFunc func(*Command) error // Flag is a flag for a command. type Flag struct { OptArg bool Value string Default string Description string // TODO: Toggle bool } // Flags is the list of flags. Loading @@ -34,10 +35,81 @@ type Command struct { // Commands is the full list of commands. type Commands map[string]*Command func split(line string) []string { words := []string{} buf := strings.Builder{} state := "start" for _, c := range line { switch state { case "start": if unicode.IsSpace(c) { continue } if c == '"' { state = "dquote" } else if c == '\'' { state = "squote" } else { state = "raw" buf.WriteRune(c) } case "dquote": if c == '"' { words = append(words, buf.String()) buf.Reset() state = "start" } else { buf.WriteRune(c) } case "squote": if c == '\'' { words = append(words, buf.String()) buf.Reset() state = "start" } else { buf.WriteRune(c) } case "dquote-raw": if c == '"' { state = "raw" } buf.WriteRune(c) case "squote-raw": if c == '\'' { state = "raw" } buf.WriteRune(c) case "raw": if unicode.IsSpace(c) { words = append(words, buf.String()) buf.Reset() state = "start" } else if c == '/' { words = append(words, buf.String()) buf.Reset() state = "raw" buf.WriteRune(c) } else if c == '"' { state = "dquote-raw" buf.WriteRune(c) } else if c == '\'' { state = "squote-raw" buf.WriteRune(c) } else { buf.WriteRune(c) } } } if len(buf.String()) > 0 { words = append(words, buf.String()) } return words } // ParseAndRun parses a command line and runs the command. func (c Commands) ParseAndRun(line string) error { // TODO: this doesn't handle a DCL command line completely. words := strings.Fields(line) words := split(line) cmd, ok := c[strings.ToUpper(words[0])] if !ok { wordup := strings.ToUpper(words[0]) Loading Loading @@ -75,27 +147,33 @@ func (c Commands) ParseAndRun(line string) error { for i := range args { if strings.HasPrefix(args[i], "/") { flag, val, assigned := strings.Cut(args[i], "=") var wordup string if assigned { wordup := strings.ToUpper(flag) wordup = strings.ToUpper(flag) } else { wordup = args[i] } toggleValue := "true" flg, ok := cmd.Flags[wordup] if !ok { wordup = strings.Replace(wordup, "/NO", "/", 1) flg, ok = cmd.Flags[wordup] if !ok { fmt.Printf("ERROR: Flag '%s' not recognised.\n", args[i]) return nil } flg.Value = val } else { wordup := strings.ToUpper(args[i]) value := "true" if strings.HasPrefix(wordup, "/NO") { wordup = strings.Replace(wordup, "/NO", "/", 1) value = "false" toggleValue = "false" } flg, ok := cmd.Flags[wordup] if !ok { fmt.Printf("ERROR: Flag '%s' not recognised.\n", args[i]) if !flg.OptArg && assigned { fmt.Printf("ERROR: Flag '%s' is a toggle.\n", args[i]) return nil } flg.Value = value if flg.OptArg { if assigned { flg.Value = strings.Trim(val, "\"'") } } else { flg.Value = toggleValue } } else { if len(cmd.Args) == cmd.MaxArgs { Loading
folders/folders.go +4 −3 Original line number Diff line number Diff line Loading @@ -2,7 +2,6 @@ package folders import ( "database/sql" "embed" "errors" "os" Loading @@ -11,6 +10,7 @@ import ( "github.com/adrg/xdg" "github.com/golang-migrate/migrate/v4" "github.com/golang-migrate/migrate/v4/source/iofs" "github.com/jmoiron/sqlx" // Included to connect to sqlite. _ "github.com/golang-migrate/migrate/v4/database/sqlite" Loading @@ -23,7 +23,7 @@ var fs embed.FS // Store is the store for folders. type Store struct { user string db *sql.DB db *sqlx.DB } // Open opens the folders database. Loading @@ -35,6 +35,7 @@ func Open(user string) (*Store, error) { } fdb := path.Join(fdir, "bboard.db") // Run db migrations if needed. sqldir, err := iofs.New(fs, "sql") if err != nil { return nil, err Loading @@ -50,7 +51,7 @@ func Open(user string) (*Store, error) { m.Close() store := &Store{user: user} store.db, err = sql.Open("sqlite", "file://"+fdb+"?_pragma=foreign_keys(1)") store.db, err = sqlx.Connect("sqlite", "file://"+fdb+"?_pragma=foreign_keys(1)") if err != nil { return nil, errors.New("bulletin database problem") } Loading
folders/manage-folders.go 0 → 100644 +75 −0 Original line number Diff line number Diff line // Package folders are all the routines and sql for managing folders. package folders import ( "errors" "fmt" ) // FolderVisibility is the folder visibility level. type FolderVisibility string // Values for FolderVisibility. const ( FolderPublic FolderVisibility = "public" FolderSemiPrivate = "semi-private" FolderPrivate = "private" ) // FolderOptions are a list of folder options. type FolderOptions struct { Always int Brief int Description string Notify int Owner string Readnew int Shownew int System int Expire int Visibility FolderVisibility } // CreateFolder creates a new folder. func (s *Store) CreateFolder(name string, options FolderOptions) error { _, err := s.db.Exec( `INSERT INTO folders (name, always, brief, description, notify, owner, readnew, shownew, system, expire, visibility) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)`, name, options.Always, options.Brief, options.Description, options.Notify, options.Owner, options.Readnew, options.Shownew, options.System, options.Expire, options.Visibility, ) // TODO: process this error a bit more to give a better error message. return err } // DeleteFolder creates a new folder. func (s *Store) DeleteFolder(name string) error { results, err := s.db.Exec("DELETE FROM folders WHERE name=$1", name) // TODO: process this error a bit more to give a better error message. if err != nil { return err } rows, err := results.RowsAffected() if err != nil { return err } if rows == 0 { return errors.New("No such folder found") } if rows != 1 { return fmt.Errorf("Unexpected number (%d) of folders removed", rows) } return nil }
folders/sql/1_create_table.up.sql +1 −1 Original line number Diff line number Diff line Loading @@ -86,7 +86,7 @@ INSERT INTO folders (name, description, system, shownew, owner) VALUES ('GENERAL', 'Default general bulletin folder.', 1, 1, 'SYSTEM'); CREATE TABLE co_owners ( folder VARCHAR(25) REFERENCES folders(name) ON UPDATE CASCADE, folder VARCHAR(25) REFERENCES folders(name) ON DELETE CASCADE ON UPDATE CASCADE, owner VARCHAR(25) REFERENCES users(login) ON UPDATE CASCADE, create_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, update_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, Loading