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() {
|
||||
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
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