Add set verb and message subcommand
This commit is contained in:
parent
f6a764ab7a
commit
31287f03e8
3 changed files with 450 additions and 0 deletions
|
@ -17,8 +17,10 @@ var Log *log.Logger
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
info := InfoCmd{}
|
info := InfoCmd{}
|
||||||
|
set := SetCmd{}
|
||||||
Session.Info.Commands = cli.CommandMap{
|
Session.Info.Commands = cli.CommandMap{
|
||||||
"info": info,
|
"info": info,
|
||||||
|
"set": set,
|
||||||
}
|
}
|
||||||
Session.Info.Name = "papero"
|
Session.Info.Name = "papero"
|
||||||
flag.Usage = func() { cli.Usage(os.Stdout, Session.Info) }
|
flag.Usage = func() { cli.Usage(os.Stdout, Session.Info) }
|
||||||
|
|
215
cli/papero/set.go
Normal file
215
cli/papero/set.go
Normal 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
233
imaputils/modify.go
Normal 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
|
||||||
|
}
|
Loading…
Reference in a new issue