Unverified Commit 99d38a4a authored by Kevin Lyda's avatar Kevin Lyda
Browse files

Implement several commands

Implement SEEN, UNSEEN, FIRST and PREV.  Can now parse number
lists used by several commands.
parent 3050e18f
Loading
Loading
Loading
Loading
+9 −9
Original line number Diff line number Diff line
@@ -30,14 +30,10 @@ repl.commands?
  * Run [godoc](http://localhost:6060/) and then review where the help text is lacking.
  * ~~Move to a storage layer.~~
  * this.Folder should be a storage.Folder.
  * Implement batch jobs
     * ~~Have install populate the database with some test data.~~
     * ~~reboot~~
     * ~~expire~~
  * Implement each command.
    * Next: folder commands - ~~CREATE~~, ~~REMOVE~~, MODIFY, ~~INDEX~~, ~~SELECT~~
    * Messages: ~~ADD~~, CURRENT, ~~DIRECTORY~~, BACK, CHANGE,
                FIRST, REMOVE, ~~NEXT~~, ~~READ~~
    * Messages: ~~ADD~~, ~~CURRENT~~, ~~DIRECTORY~~, ~~BACK~~, CHANGE,
                ~~FIRST~~, ~~NEXT~~, ~~READ~~, DELETE
    * Messages edit: CHANGE, REPLY, FORWARD
    * Moving messages: COPY, MOVE
    * Compound commands: SET and SHOW - make HELP work for them.
@@ -53,15 +49,12 @@ repl.commands?
    * trigger to limit values for 'visibility'?
  * Add some of the early announcements from the sources - see the
    conversion branch - to the GENERAL folder.
  * Add a pager
  * Add commands:
    * A way to add / delete ssh keys.
    * A way to manage files?
    * Commands for a local mail system?
    * Commands to connect to Mattermost or mastodon?
    * Commands to manage users.
  * ~~SHOW VERSION~~
  * Check db version; notify user if it changes; refuse to write to db if it has.

Done:

@@ -72,6 +65,13 @@ Done:
    * Using giu, a [text-editor](https://serge-hulne.medium.com/coding-a-simple-text-editor-in-go-using-giu-quick-and-dirty-b9b97ab41e4a) (needs cgo, no)
    * [bubbletea](https://github.com/charmbracelet/bubbletea) seems to be the tui that's winning
    * Another option is tview - [simpler](https://github.com/rivo/tview).
  * ~~Implement batch jobs~~
     * ~~Have install populate the database with some test data.~~
     * ~~reboot~~
     * ~~expire~~
  * ~~Add a pager~~
  * ~~SHOW VERSION~~
  * Check db version; notify user if it changes; refuse to write to db if it has.

## Module links

+56 −0
Original line number Diff line number Diff line
@@ -60,6 +60,34 @@ func ReadMessage(login, folder string, msgid int64) (*storage.Message, error) {
	return &msg, nil
}

// MarkSeen marks a list of messages as seen.
func MarkSeen(msgids []int64) error {
	ctx := storage.Context()

	for _, msgid := range msgids {
		this.Q.SetMessageSeen(ctx, storage.SetMessageSeenParams{
			Login:  this.User.Login,
			Folder: this.Folder,
			Msgid:  msgid,
		})
	}
	return nil
}

// MarkUnseen marks a list of messages as unseen.
func MarkUnseen(msgids []int64) error {
	ctx := storage.Context()

	for _, msgid := range msgids {
		this.Q.UnsetMessageSeen(ctx, storage.UnsetMessageSeenParams{
			Login:  this.User.Login,
			Folder: this.Folder,
			Msgid:  msgid,
		})
	}
	return nil
}

// NextMsgid gets the next message id.
func NextMsgid(login, folder string, msgid int64) int64 {
	ctx := storage.Context()
@@ -74,6 +102,34 @@ func NextMsgid(login, folder string, msgid int64) int64 {
	return newid
}

// PrevMsgid gets the next message id.
func PrevMsgid(login, folder string, msgid int64) int64 {
	ctx := storage.Context()
	newid, err := this.Q.PrevMsgid(ctx, storage.PrevMsgidParams{
		Folder: folder,
		Login:  login,
		ID:     msgid,
	})
	if err != nil {
		return 0
	}
	return newid
}

// FirstMessage gets the first message in a folder.
func FirstMessage(folder string) int64 {
	ctx := storage.Context()
	first, err := this.Q.NextMsgidIgnoringSeen(ctx,
		storage.NextMsgidIgnoringSeenParams{
			Folder: folder,
			ID:     0,
		})
	if err != nil {
		return 0
	}
	return first
}

// ListMessages lists messages.
func ListMessages(folder string) ([]storage.Message, error) {
	ctx := storage.Context()

repl/args.go

0 → 100644
+43 −0
Original line number Diff line number Diff line
package repl

import (
	"fmt"
	"strconv"
	"strings"
)

// ParseNumberList takes a string with a number list
// like "1,8,3-6,10,20-30" and returns a number list like
// []int64{1,8,3,4,5,6,10,20,21,...,30}.  Well, also an error since
// somethings can go wrong here.
func ParseNumberList(input string) ([]int64, error) {
	var result []int64
	segments := strings.Split(input, ",")

	for _, segment := range segments {
		segment = strings.TrimSpace(segment)
		if strings.Contains(segment, "-") {
			// Handle range
			parts := strings.SplitN(segment, "-", 2)
			if len(parts) != 2 {
				return nil, fmt.Errorf("invalid range format: %s", segment)
			}
			start, err1 := strconv.ParseInt(parts[0], 10, 64)
			end, err2 := strconv.ParseInt(parts[1], 10, 64)
			if err1 != nil || err2 != nil || start > end {
				return nil, fmt.Errorf("invalid range: %s", segment)
			}
			for i := start; i <= end; i++ {
				result = append(result, i)
			}
		} else {
			// Handle single number
			num, err := strconv.ParseInt(segment, 10, 64)
			if err != nil {
				return nil, fmt.Errorf("invalid number: %s", segment)
			}
			result = append(result, num)
		}
	}
	return result, nil
}
+2 −2
Original line number Diff line number Diff line
@@ -1139,16 +1139,16 @@ automatically be set as being SEEN when they are read.
If you  have used  the SEEN  command and wish  to disable  the automatic
marking of messages in regular folders  as SEEN when they are read, type
the command SEEN/NOREAD. To reenable, simply use the SEEN command again.`,
		MinArgs: 1,
		MaxArgs: 1,
		Action:  ActionSeen,
	},
	"UNSEEN": {
		Description: `Sets the current or message-id message as unseen.

  Format:
    UNSEEN [message-number or numbers]`,
		MinArgs: 1,
		MaxArgs: 1,
		Action:  ActionUnseen,
	},
	"SELECT": {
		Description: `Selects a folder of messages.  See HELP Folders for a description  of  a
+76 −12
Original line number Diff line number Diff line
@@ -148,14 +148,36 @@ func ActionAdd(cmd *dclish.Command) error {
}

// ActionCurrent handles the `CURRENT` command.
func ActionCurrent(cmd *dclish.Command) error {
	fmt.Printf("TODO: unimplemented...\n%s\n", cmd.Description)
func ActionCurrent(_ *dclish.Command) error {
	// TODO: handle flags.
	msg, err := folders.ReadMessage(this.User.Login, this.Folder, this.MsgID)
	if err != nil {
		return err
	}
	lines := strings.Split(msg.String(), "\n")
	if len(lines) > 10 {
		lines = lines[:10]
	}
	fmt.Printf("%s\n", strings.Join(lines, "\n"))
	return nil
}

// ActionBack handles the `BACK` command.
func ActionBack(cmd *dclish.Command) error {
	fmt.Printf("TODO: unimplemented...\n%s\n", cmd.Description)
func ActionBack(_ *dclish.Command) error {
	// TODO: handle flags.
	msgid := folders.PrevMsgid(this.User.Login, this.Folder, this.MsgID)
	if msgid == 0 {
		fmt.Println("No previous messages")
		return nil
	}
	msg, err := folders.ReadMessage(this.User.Login, this.Folder, msgid)
	if err != nil {
		return err
	}
	// TODO: pager needs to report if the whole message was read
	// and only increment if not.
	pager.Pager(msg.String())
	this.MsgID = msgid
	return nil
}

@@ -166,15 +188,24 @@ func ActionChange(cmd *dclish.Command) error {
}

// ActionFirst handles the `FIRST` command.
func ActionFirst(cmd *dclish.Command) error {
	fmt.Printf("TODO: unimplemented...\n%s\n", cmd.Description)
func ActionFirst(_ *dclish.Command) error {
	msgid := folders.FirstMessage(this.Folder)
	if msgid == 0 {
		fmt.Println("No messages in folder")
		return nil
	}
	this.MsgID = msgid
	return nil
}

// ActionNext handles the `NEXT` command.
func ActionNext(cmd *dclish.Command) error {
func ActionNext(_ *dclish.Command) error {
	// TODO: handle flags.
	msgid := max(folders.NextMsgid(this.User.Login, this.Folder, this.MsgID), this.MsgID)
	msgid := folders.NextMsgid(this.User.Login, this.Folder, this.MsgID)
	if msgid == 0 {
		fmt.Println("No next messages")
		return nil
	}
	msg, err := folders.ReadMessage(this.User.Login, this.Folder, msgid)
	if err != nil {
		return err
@@ -182,8 +213,7 @@ func ActionNext(cmd *dclish.Command) error {
	// TODO: pager needs to report if the whole message was read
	// and only increment if not.
	pager.Pager(msg.String())
	msgid = folders.NextMsgid(this.User.Login, this.Folder, msgid)
	this.MsgID = max(this.MsgID, msgid)
	this.MsgID = msgid
	return nil
}

@@ -192,11 +222,11 @@ func ActionRead(cmd *dclish.Command) error {
	// TODO: handle flags.
	msgid := this.MsgID
	if len(cmd.Args) == 1 {
		id, err := strconv.Atoi(cmd.Args[0])
		var err error
		msgid, err = strconv.ParseInt(cmd.Args[0], 10, 64)
		if err != nil {
			return err
		}
		msgid = int64(id)
	}
	msg, err := folders.ReadMessage(this.User.Login, this.Folder, msgid)
	if err != nil {
@@ -221,3 +251,37 @@ func ActionForward(cmd *dclish.Command) error {
	fmt.Printf("TODO: unimplemented...\n%s\n", cmd.Description)
	return nil
}

// ActionSeen handles the `SEEN` command.
func ActionSeen(cmd *dclish.Command) error {
	var err error
	msgids := []int64{this.MsgID}
	if len(cmd.Args) == 1 {
		msgids, err = ParseNumberList(cmd.Args[0])
		if err != nil {
			return err
		}
	}
	err = folders.MarkSeen(msgids)
	if err != nil {
		fmt.Printf("ERROR: %s.\n", err)
	}
	return nil
}

// ActionUnseen handles the `UNSEEN` command.
func ActionUnseen(cmd *dclish.Command) error {
	var err error
	msgids := []int64{this.MsgID}
	if len(cmd.Args) == 1 {
		msgids, err = ParseNumberList(cmd.Args[0])
		if err != nil {
			return err
		}
	}
	err = folders.MarkUnseen(msgids)
	if err != nil {
		fmt.Printf("ERROR: %s.\n", err)
	}
	return nil
}
Loading