Add set verb and message subcommand

This commit is contained in:
Blallo 2021-03-18 23:45:33 +01:00
parent f6a764ab7a
commit 31287f03e8
No known key found for this signature in database
GPG key ID: 0CBE577C9B72DC3F
3 changed files with 450 additions and 0 deletions

View file

@ -17,8 +17,10 @@ var Log *log.Logger
func init() {
info := InfoCmd{}
set := SetCmd{}
Session.Info.Commands = cli.CommandMap{
"info": info,
"set": set,
}
Session.Info.Name = "papero"
flag.Usage = func() { cli.Usage(os.Stdout, Session.Info) }

215
cli/papero/set.go Normal file
View file

@ -0,0 +1,215 @@
package main
import (
"errors"
"flag"
"fmt"
"io"
"os"
"strconv"
"strings"
"git.lattuga.net/blallo/papero/cli"
"git.lattuga.net/blallo/papero/imaputils"
"github.com/emersion/go-imap"
)
var setVerbs cli.CommandMap
func init() {
setVerbs = cli.CommandMap{
"message": SetMessageCmd{},
// "subscribe": SetSubscribedCmd{},
// "unsubscribe": SetUnsubscribedCmd{},
}
}
type SetCmd struct{}
func (s SetCmd) Func(args []string) error {
flagset := flag.NewFlagSet(args[0], flag.ExitOnError)
flagset.Usage = func() { s.Help(os.Stdout, flagset) }
flagset.Parse(args[1:])
subArgs := flagset.Args()
if len(subArgs) == 0 {
s.Help(os.Stderr, flagset)
os.Exit(1)
}
cmd := subArgs[0]
if Session.Info.Opts.Debug {
Log.Debug("set verb:", cmd)
}
return setVerbs[cmd].Func(subArgs)
}
func (s SetCmd) Help(w io.Writer, set *flag.FlagSet) {
fmt.Fprintf(w, "USAGE: %s set VERB [verb opts]\n", Session.Info.Name)
fmt.Fprintf(w, "\nVERBS:\n")
for verb := range setVerbs {
fmt.Fprintf(w, "\t%s\n", verb)
}
}
// FLAG UTILS
type pair struct {
set, unset bool
}
type pairFlag struct {
*pair
}
func newPairFlag() pairFlag {
return pairFlag{
&pair{false, false},
}
}
func (p pair) String() string {
switch p {
case pair{true, false}:
return "set"
case pair{false, true}:
return "unset"
default:
return "undef"
}
}
func (pf pairFlag) String() string {
return pf.pair.String()
}
func (pf pairFlag) Set(s string) error {
Log.Debugf("into Set: %s\n", s)
if s == "set" {
Log.Debug("setting")
v := pair{true, false}
*pf.pair = v
return nil
}
if s == "unset" {
Log.Debug("unsetting")
v := pair{false, true}
*pf.pair = v
return nil
}
Log.Debug(fmt.Sprintf("unacceptable value %s\n", s))
return errors.New("only `set` or `unset` are allowed")
}
// VERBS
type SetMessageCmd struct{}
func (sm SetMessageCmd) Func(args []string) error {
if Session.Info.Opts.Debug {
Log.Debug("enter set message")
}
flags := imaputils.NewFlagsStatus()
var opts imaputils.SetFlagsOpts
flagSeen := newPairFlag()
flagAnswered := newPairFlag()
flagFlagged := newPairFlag()
flagDeleted := newPairFlag()
flagDraft := newPairFlag()
flagset := flag.NewFlagSet(args[0], flag.ExitOnError)
flagset.Var(&flagSeen, "seen", "Set or unset `seen` flag on message")
flagset.Var(&flagAnswered, "answered", "Set or unset `answered` flag on message")
flagset.Var(&flagFlagged, "flagged", "Set or unset `flagged` flag on message")
flagset.Var(&flagDeleted, "deleted", "Set or unset `deleted` flag on message")
flagset.Var(&flagDraft, "draft", "Set or unset `draft` flag on message")
flagset.Usage = func() {
sm.Help(os.Stdout, flagset)
}
flagset.Parse(args[1:])
subArgs := flagset.Args()
if len(subArgs) < 2 {
Log.Debugf("Too few arguments: %s\n", subArgs)
sm.Help(os.Stderr, flagset)
os.Exit(1)
}
opts.Mailbox = subArgs[0]
seq := new(imap.SeqSet)
for _, msgId := range subArgs[1:] {
if err := parseMessageSeq(seq, msgId); err != nil {
return err
}
}
opts.MessageSeq = seq
opts.Debug = Session.Info.Opts.Debug
if err := assignFlag(*flagSeen.pair, flags.SetSeen, flags.UnsetSeen); err != nil {
return err
}
if err := assignFlag(*flagAnswered.pair, flags.SetAnswered, flags.UnsetAnswered); err != nil {
return err
}
if err := assignFlag(*flagFlagged.pair, flags.SetFlagged, flags.UnsetFlagged); err != nil {
return err
}
if err := assignFlag(*flagDeleted.pair, flags.SetDeleted, flags.UnsetDeleted); err != nil {
return err
}
if err := assignFlag(*flagDraft.pair, flags.SetDraft, flags.UnsetDraft); err != nil {
return err
}
Log.Debugf("Setting flags: %s\n", flags)
opts.Flags = flags
return imaputils.SetFlags(Session.Config, &opts)
}
func (sm SetMessageCmd) Help(w io.Writer, set *flag.FlagSet) {
fmt.Fprintf(w, "USAGE: %s set message [opts] MAILBOX MESSAGE_ID_OR_RANGE [MESSAGE_ID_OR_RANGE [MESSAGE_ID_OR_RANGE ...]]\n", Session.Info.Name)
fmt.Fprintln(w, "\nMESSAGE_ID_OR_RANGE being a single message id or a git-like range")
fmt.Fprintln(w, "Example:")
fmt.Fprintln(w, "\t12345\t- A single message")
fmt.Fprintln(w, "\t123...258\t- A range from id 123 to id 258")
fmt.Fprintf(w, "\nOPTS:\n")
set.PrintDefaults()
}
func parseMessageSeq(seq *imap.SeqSet, data string) error {
if strings.Contains(data, "..") {
split := strings.Split(data, "..")
start, err := strconv.ParseUint(split[0], 10, 0)
if err != nil {
return err
}
stop, err := strconv.ParseUint(split[2], 10, 0)
if err != nil {
return err
}
seq.AddRange(uint32(start), uint32(stop))
} else {
msg, err := strconv.ParseUint(data, 10, 0)
if err != nil {
return err
}
seq.AddNum(uint32(msg))
}
return nil
}
func assignFlag(flagOp pair, setCallback, unsetCallback func()) error {
switch flagOp {
case pair{true, false}:
setCallback()
case pair{false, true}:
unsetCallback()
default:
return nil
}
return nil
}

233
imaputils/modify.go Normal file
View file

@ -0,0 +1,233 @@
package imaputils
import (
"errors"
"fmt"
"git.lattuga.net/blallo/papero/config"
"github.com/emersion/go-imap"
)
type tristate int
const (
undef tristate = 0
doSet tristate = 1
doUnset tristate = 2
)
func (t tristate) IsSet() bool {
switch t {
case undef:
return false
case doSet:
return true
case doUnset:
return false
default:
panic(errors.New(fmt.Sprintf("unexpected tristate value %d", t)))
}
}
func (t tristate) IsUnset() bool {
switch t {
case undef:
return false
case doSet:
return false
case doUnset:
return true
default:
panic(errors.New(fmt.Sprintf("unexpected tristate value %d", t)))
}
}
func (t tristate) String() string {
switch t {
case undef:
return "undef"
case doSet:
return "set"
case doUnset:
return "unset"
}
panic("unexpected value for tristate")
}
type FlagsStatus struct {
FlagSeen tristate
FlagAnswered tristate
FlagFlagged tristate
FlagDeleted tristate
FlagDraft tristate
//FlagUnknown bool
}
func NewFlagsStatus() *FlagsStatus {
return &FlagsStatus{
undef,
undef,
undef,
undef,
undef,
}
}
func (f *FlagsStatus) IntoSetFlagList() []interface{} {
var result []interface{}
switch {
case f.FlagSeen.IsSet():
result = append(result, imap.SeenFlag)
case f.FlagAnswered.IsSet():
result = append(result, imap.AnsweredFlag)
case f.FlagFlagged.IsSet():
result = append(result, imap.FlaggedFlag)
case f.FlagDeleted.IsSet():
result = append(result, imap.DeletedFlag)
case f.FlagDraft.IsSet():
result = append(result, imap.DraftFlag)
}
return result
}
func (f *FlagsStatus) IntoUnsetFlagList() []interface{} {
var result []interface{}
switch {
case f.FlagSeen.IsUnset():
result = append(result, imap.SeenFlag)
case f.FlagAnswered.IsUnset():
result = append(result, imap.AnsweredFlag)
case f.FlagFlagged.IsUnset():
result = append(result, imap.FlaggedFlag)
case f.FlagDeleted.IsUnset():
result = append(result, imap.DeletedFlag)
case f.FlagDraft.IsUnset():
result = append(result, imap.DraftFlag)
}
return result
}
func (f *FlagsStatus) WillSet() bool {
switch {
case f.FlagSeen.IsSet():
return true
case f.FlagAnswered.IsSet():
return true
case f.FlagFlagged.IsSet():
return true
case f.FlagDeleted.IsSet():
return true
case f.FlagDraft.IsSet():
return true
}
return false
}
func (f *FlagsStatus) WillUnset() bool {
switch {
case f.FlagSeen.IsUnset():
return true
case f.FlagAnswered.IsUnset():
return true
case f.FlagFlagged.IsUnset():
return true
case f.FlagDeleted.IsUnset():
return true
case f.FlagDraft.IsUnset():
return true
}
return false
}
func (f *FlagsStatus) SetSeen() {
f.FlagSeen = doSet
}
func (f *FlagsStatus) UnsetSeen() {
f.FlagSeen = doUnset
}
func (f *FlagsStatus) SetAnswered() {
f.FlagAnswered = doSet
}
func (f *FlagsStatus) UnsetAnswered() {
f.FlagAnswered = doUnset
}
func (f *FlagsStatus) SetFlagged() {
f.FlagFlagged = doSet
}
func (f *FlagsStatus) UnsetFlagged() {
f.FlagFlagged = doUnset
}
func (f *FlagsStatus) SetDeleted() {
f.FlagDeleted = doSet
}
func (f *FlagsStatus) UnsetDeleted() {
f.FlagDeleted = doUnset
}
func (f *FlagsStatus) SetDraft() {
f.FlagDraft = doSet
}
func (f *FlagsStatus) UnsetDraft() {
f.FlagDraft = doUnset
}
//func (f *FlagsStatus) SetUnknown() {
// f.FlagUnknown = doSet
//}
//func (f *FlagsStatus) UnsetUnknown() {
// f.FlagUnknown = doUnset
//}
type SetFlagsOpts struct {
Mailbox string
MessageSeq *imap.SeqSet
Flags *FlagsStatus
Debug bool
}
// SetFlags changes the flags on the specified message in the specified mailbox.
// First it adds the new flags set, then in removes the flags explicitly unset.
// Unfortunately, this operation is not atomic.
func SetFlags(conf *config.AccountData, opts *SetFlagsOpts) error {
conn := NewConnection(conf)
err := conn.Start(opts.Debug)
if err != nil {
return err
}
defer conn.Close()
_, err = conn.client.Select(opts.Mailbox, false)
if err != nil {
return err
}
if opts.Flags.WillSet() {
item := imap.FormatFlagsOp(imap.AddFlags, true)
flags := opts.Flags.IntoSetFlagList()
err = conn.client.Store(opts.MessageSeq, item, flags, nil)
if err != nil {
return err
}
}
if opts.Flags.WillUnset() {
item := imap.FormatFlagsOp(imap.RemoveFlags, true)
flags := opts.Flags.IntoUnsetFlagList()
err = conn.client.Store(opts.MessageSeq, item, flags, nil)
if err != nil {
return err
}
}
return nil
}