Unverified Commit eb357867 authored by Kevin Lyda's avatar Kevin Lyda
Browse files

added create and remove commands

parent 4f280b4e
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -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

+95 −17
Original line number Diff line number Diff line
@@ -4,6 +4,7 @@ package dclish
import (
	"fmt"
	"strings"
	"unicode"
)

// ActionFunc is the function that a command runs.
@@ -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.
@@ -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])
@@ -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 {
+4 −3
Original line number Diff line number Diff line
@@ -2,7 +2,6 @@
package folders

import (
	"database/sql"
	"embed"
	"errors"
	"os"
@@ -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"
@@ -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.
@@ -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
@@ -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")
	}
+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
}
+1 −1
Original line number Diff line number Diff line
@@ -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