diff options
author | Robin Jarry <robin@jarry.cc> | 2023-10-03 22:12:10 +0200 |
---|---|---|
committer | Robin Jarry <robin@jarry.cc> | 2023-10-28 19:24:49 +0200 |
commit | e54486ee40c9cedde9d4dae3e89d8b5f932188ee (patch) | |
tree | 254f032ef94920c28fbb9be8f918e61082a2015d /commands/msg | |
parent | 9a4518476d8c8f28340c6b44cd808e6d58fbeb98 (diff) | |
download | aerc-e54486ee40c9cedde9d4dae3e89d8b5f932188ee.tar.gz |
commands: parse arguments with go-opt
Use the argument parsing framework introduced earlier to unify the
parsing of (almost) all command options. Remove custom parsing code and
to avoid extraneous types, add fields with `opt` tags on command structs
that have options and arguments. Commands that take no argument do not
need anything.
Since the command objects now carry data, create a new temporary
instance of them before passing them to opt.ArgsToStruct when executing
a command.
A few of the commands use specific semantics for parsing (:choose), or
are delegating argument parsing to another function (:sort, :search,
:filter). For these commands, simply add a dummy "-" passthrough
argument. Since all commands still have the argument list (after split)
nothing needs to be changed in this area.
There should be no functional change besides the Usage strings and
reported errors which are now generated automatically.
Signed-off-by: Robin Jarry <robin@jarry.cc>
Reviewed-by: Koni Marti <koni.marti@gmail.com>
Tested-by: Moritz Poldrack <moritz@poldrack.dev>
Tested-by: Inwit <inwit@sindominio.net>
Diffstat (limited to 'commands/msg')
-rw-r--r-- | commands/msg/archive.go | 24 | ||||
-rw-r--r-- | commands/msg/copy.go | 28 | ||||
-rw-r--r-- | commands/msg/delete.go | 5 | ||||
-rw-r--r-- | commands/msg/envelope.go | 29 | ||||
-rw-r--r-- | commands/msg/fold.go | 4 | ||||
-rw-r--r-- | commands/msg/forward.go | 54 | ||||
-rw-r--r-- | commands/msg/invite.go | 25 | ||||
-rw-r--r-- | commands/msg/mark.go | 68 | ||||
-rw-r--r-- | commands/msg/modify-labels.go | 14 | ||||
-rw-r--r-- | commands/msg/move.go | 29 | ||||
-rw-r--r-- | commands/msg/pipe.go | 80 | ||||
-rw-r--r-- | commands/msg/read.go | 132 | ||||
-rw-r--r-- | commands/msg/recall.go | 34 | ||||
-rw-r--r-- | commands/msg/reply.go | 65 | ||||
-rw-r--r-- | commands/msg/toggle-thread-context.go | 5 | ||||
-rw-r--r-- | commands/msg/toggle-threads.go | 5 | ||||
-rw-r--r-- | commands/msg/unsubscribe.go | 26 |
17 files changed, 202 insertions, 425 deletions
diff --git a/commands/msg/archive.go b/commands/msg/archive.go index f326d0c6..f4d6e3be 100644 --- a/commands/msg/archive.go +++ b/commands/msg/archive.go @@ -1,7 +1,6 @@ package msg import ( - "errors" "fmt" "strings" "sync" @@ -19,7 +18,21 @@ const ( ARCHIVE_MONTH = "month" ) -type Archive struct{} +var ARCHIVE_TYPES = []string{ARCHIVE_FLAT, ARCHIVE_YEAR, ARCHIVE_MONTH} + +type Archive struct { + Type string `opt:"type" action:"ParseArchiveType" metavar:"flat|year|month"` +} + +func (a *Archive) ParseArchiveType(arg string) error { + for _, t := range ARCHIVE_TYPES { + if t == arg { + a.Type = arg + return nil + } + } + return fmt.Errorf("invalid archive type") +} func init() { register(Archive{}) @@ -34,16 +47,13 @@ func (Archive) Complete(args []string) []string { return commands.CompletionFromList(valid, args) } -func (Archive) Execute(args []string) error { - if len(args) != 2 { - return errors.New("Usage: archive <flat|year|month>") - } +func (a Archive) Execute(args []string) error { h := newHelper() msgs, err := h.messages() if err != nil { return err } - err = archive(msgs, args[1]) + err = archive(msgs, a.Type) return err } diff --git a/commands/msg/copy.go b/commands/msg/copy.go index 1a902772..4109ef99 100644 --- a/commands/msg/copy.go +++ b/commands/msg/copy.go @@ -1,18 +1,17 @@ package msg import ( - "errors" - "strings" "time" - "git.sr.ht/~sircmpwn/getopt" - "git.sr.ht/~rjarry/aerc/app" "git.sr.ht/~rjarry/aerc/commands" "git.sr.ht/~rjarry/aerc/worker/types" ) -type Copy struct{} +type Copy struct { + CreateFolders bool `opt:"-p"` + Folder string `opt:"..." metavar:"<folder>"` +} func init() { register(Copy{}) @@ -26,20 +25,7 @@ func (Copy) Complete(args []string) []string { return commands.GetFolders(args) } -func (Copy) Execute(args []string) error { - if len(args) == 1 { - return errors.New("Usage: cp [-p] <folder>") - } - opts, optind, err := getopt.Getopts(args, "p") - if err != nil { - return err - } - var createParents bool - for _, opt := range opts { - if opt.Option == 'p' { - createParents = true - } - } +func (c Copy) Execute(args []string) error { h := newHelper() uids, err := h.markedOrSelectedUids() if err != nil { @@ -49,8 +35,8 @@ func (Copy) Execute(args []string) error { if err != nil { return err } - store.Copy(uids, strings.Join(args[optind:], " "), - createParents, func( + store.Copy(uids, c.Folder, + c.CreateFolders, func( msg types.WorkerMessage, ) { switch msg := msg.(type) { diff --git a/commands/msg/delete.go b/commands/msg/delete.go index 107c8a3f..49463abc 100644 --- a/commands/msg/delete.go +++ b/commands/msg/delete.go @@ -1,7 +1,6 @@ package msg import ( - "errors" "time" "git.sr.ht/~rjarry/aerc/app" @@ -27,10 +26,6 @@ func (Delete) Complete(args []string) []string { } func (Delete) Execute(args []string) error { - if len(args) != 1 { - return errors.New("Usage: :delete") - } - h := newHelper() store, err := h.store() if err != nil { diff --git a/commands/msg/envelope.go b/commands/msg/envelope.go index 3a388c12..6da82a1e 100644 --- a/commands/msg/envelope.go +++ b/commands/msg/envelope.go @@ -9,11 +9,13 @@ import ( "git.sr.ht/~rjarry/aerc/lib/format" "git.sr.ht/~rjarry/aerc/log" "git.sr.ht/~rjarry/aerc/models" - "git.sr.ht/~sircmpwn/getopt" "github.com/emersion/go-message/mail" ) -type Envelope struct{} +type Envelope struct { + Header bool `opt:"-h"` + Format string `opt:"-s" default:"%-20.20s: %s"` +} func init() { register(Envelope{}) @@ -27,22 +29,7 @@ func (Envelope) Complete(args []string) []string { return nil } -func (Envelope) Execute(args []string) error { - header := false - fmtStr := "%-20.20s: %s" - opts, _, err := getopt.Getopts(args, "hs:") - if err != nil { - return err - } - for _, opt := range opts { - switch opt.Option { - case 's': - fmtStr = opt.Value - case 'h': - header = true - } - } - +func (e Envelope) Execute(args []string) error { acct := app.SelectedAccount() if acct == nil { return errors.New("No account selected") @@ -53,10 +40,10 @@ func (Envelope) Execute(args []string) error { return err } else { if msg != nil { - if header { - list = parseHeader(msg, fmtStr) + if e.Header { + list = parseHeader(msg, e.Format) } else { - list = parseEnvelope(msg, fmtStr, + list = parseEnvelope(msg, e.Format, acct.UiConfig().TimestampFormat) } } else { diff --git a/commands/msg/fold.go b/commands/msg/fold.go index 1d40b90a..0621c8c3 100644 --- a/commands/msg/fold.go +++ b/commands/msg/fold.go @@ -2,7 +2,6 @@ package msg import ( "errors" - "fmt" "strings" "git.sr.ht/~rjarry/aerc/lib/ui" @@ -23,9 +22,6 @@ func (Fold) Complete(args []string) []string { } func (Fold) Execute(args []string) error { - if len(args) != 1 { - return fmt.Errorf("Usage: %s", args[0]) - } h := newHelper() store, err := h.store() if err != nil { diff --git a/commands/msg/forward.go b/commands/msg/forward.go index 68f162cc..e6e386a9 100644 --- a/commands/msg/forward.go +++ b/commands/msg/forward.go @@ -20,11 +20,16 @@ import ( "git.sr.ht/~rjarry/aerc/models" "git.sr.ht/~rjarry/aerc/worker/types" "github.com/emersion/go-message/mail" - - "git.sr.ht/~sircmpwn/getopt" ) -type forward struct{} +type forward struct { + AttachAll bool `opt:"-A"` + AttachFull bool `opt:"-F"` + Edit bool `opt:"-e"` + NoEdit bool `opt:"-E"` + Template string `opt:"-T"` + To []string `opt:"..." required:"false"` +} func init() { register(forward{}) @@ -38,36 +43,11 @@ func (forward) Complete(args []string) []string { return nil } -func (forward) Execute(args []string) error { - opts, optind, err := getopt.Getopts(args, "AFT:eE") - if err != nil { - return err - } - if len(args) != optind { - return errors.New("Usage: forward [-A|-F] [-T <template>] [-e|-E]") - } - attachAll := false - attachFull := false - template := "" - editHeaders := config.Compose.EditHeaders - for _, opt := range opts { - switch opt.Option { - case 'A': - attachAll = true - case 'F': - attachFull = true - case 'T': - template = opt.Value - case 'e': - editHeaders = true - case 'E': - editHeaders = false - } - } - - if attachAll && attachFull { +func (f forward) Execute(args []string) error { + if f.AttachAll && f.AttachFull { return errors.New("Options -A and -F are mutually exclusive") } + editHeaders := (config.Compose.EditHeaders || f.Edit) && !f.NoEdit widget := app.SelectedTabContent().(app.ProvidesMessage) acct := widget.SelectedAccount() @@ -89,7 +69,7 @@ func (forward) Execute(args []string) error { h.SetSubject(subject) var tolist []*mail.Address - to := strings.Join(args[optind:], ", ") + to := strings.Join(f.To, ", ") if strings.Contains(to, "@") { tolist, err = mail.ParseAddressList(to) if err != nil { @@ -109,7 +89,7 @@ func (forward) Execute(args []string) error { addTab := func() (*app.Composer, error) { composer, err := app.NewComposer(acct, acct.AccountConfig(), acct.Worker(), editHeaders, - template, h, &original, nil) + f.Template, h, &original, nil) if err != nil { app.PushError("Error: " + err.Error()) return nil, err @@ -124,7 +104,7 @@ func (forward) Execute(args []string) error { return composer, nil } - if attachFull { + if f.AttachFull { tmpDir, err := os.MkdirTemp("", "aerc-tmp-attachment") if err != nil { return err @@ -158,8 +138,8 @@ func (forward) Execute(args []string) error { }) }) } else { - if template == "" { - template = config.Templates.Forwards + if f.Template == "" { + f.Template = config.Templates.Forwards } part := lib.FindPlaintext(msg.BodyStructure, nil) @@ -186,7 +166,7 @@ func (forward) Execute(args []string) error { } // add attachments - if attachAll { + if f.AttachAll { var mu sync.Mutex parts := lib.FindAllNonMultipart(msg.BodyStructure, nil, nil) for _, p := range parts { diff --git a/commands/msg/invite.go b/commands/msg/invite.go index 60107480..5b8558b0 100644 --- a/commands/msg/invite.go +++ b/commands/msg/invite.go @@ -12,11 +12,13 @@ import ( "git.sr.ht/~rjarry/aerc/lib/format" "git.sr.ht/~rjarry/aerc/log" "git.sr.ht/~rjarry/aerc/models" - "git.sr.ht/~sircmpwn/getopt" "github.com/emersion/go-message/mail" ) -type invite struct{} +type invite struct { + Edit bool `opt:"-e"` + NoEdit bool `opt:"-E"` +} func init() { register(invite{}) @@ -30,7 +32,7 @@ func (invite) Complete(args []string) []string { return nil } -func (invite) Execute(args []string) error { +func (i invite) Execute(args []string) error { acct := app.SelectedAccount() if acct == nil { return errors.New("no account selected") @@ -49,22 +51,7 @@ func (invite) Execute(args []string) error { return fmt.Errorf("no invitation found (missing text/calendar)") } - editHeaders := config.Compose.EditHeaders - opts, optind, err := getopt.Getopts(args, "eE") - if err != nil { - return err - } - if len(args) != optind { - return errors.New("Usage: accept|accept-tentative|decline [-e|-E]") - } - for _, opt := range opts { - switch opt.Option { - case 'e': - editHeaders = true - case 'E': - editHeaders = false - } - } + editHeaders := (config.Compose.EditHeaders || i.Edit) && !i.NoEdit subject := trimLocalizedRe(msg.Envelope.Subject, acct.AccountConfig().LocalizedRe) switch args[0] { diff --git a/commands/msg/mark.go b/commands/msg/mark.go index eeaa5485..c2c21cf2 100644 --- a/commands/msg/mark.go +++ b/commands/msg/mark.go @@ -2,11 +2,15 @@ package msg import ( "fmt" - - "git.sr.ht/~sircmpwn/getopt" ) -type Mark struct{} +type Mark struct { + All bool `opt:"-a" aliases:"mark,unmark"` + Toggle bool `opt:"-t" aliases:"mark,unmark"` + Visual bool `opt:"-v" aliases:"mark,unmark"` + VisualClear bool `opt:"-V" aliases:"mark,unmark"` + Thread bool `opt:"-T" aliases:"mark,unmark"` +} func init() { register(Mark{}) @@ -20,7 +24,7 @@ func (Mark) Complete(args []string) []string { return nil } -func (Mark) Execute(args []string) error { +func (m Mark) Execute(args []string) error { h := newHelper() OnSelectedMessage := func(fn func(uint32)) error { if fn == nil { @@ -38,63 +42,38 @@ func (Mark) Execute(args []string) error { return err } marker := store.Marker() - opts, _, err := getopt.Getopts(args, "atvVT") - if err != nil { - return err - } - var all bool - var toggle bool - var visual bool - var clearVisual bool - var thread bool - for _, opt := range opts { - switch opt.Option { - case 'a': - all = true - case 'v': - visual = true - clearVisual = true - case 'V': - visual = true - case 't': - toggle = true - case 'T': - thread = true - } - } - if thread && all { + if m.Thread && m.All { return fmt.Errorf("-a and -T are mutually exclusive") } - if thread && visual { + if m.Thread && (m.Visual || m.VisualClear) { return fmt.Errorf("-v and -T are mutually exclusive") } + if m.Visual && m.All { + return fmt.Errorf("-a and -v are mutually exclusive") + } switch args[0] { case "mark": - if all && visual { - return fmt.Errorf("-a and -v are mutually exclusive") - } - var modFunc func(uint32) - if toggle { + if m.Toggle { modFunc = marker.ToggleMark } else { modFunc = marker.Mark } switch { - case all: + case m.All: uids := store.Uids() for _, uid := range uids { modFunc(uid) } return nil - case visual: - marker.ToggleVisualMark(clearVisual) + case m.Visual || m.VisualClear: + marker.ToggleVisualMark(m.VisualClear) return nil default: - if thread { + if m.Thread { threadPtr, err := store.SelectedThread() if err != nil { return err @@ -109,22 +88,22 @@ func (Mark) Execute(args []string) error { } case "unmark": - if visual { + if m.Visual || m.VisualClear { return fmt.Errorf("visual mode not supported for this command") } switch { - case all && toggle: + case m.All && m.Toggle: uids := store.Uids() for _, uid := range uids { marker.ToggleMark(uid) } return nil - case all && !toggle: + case m.All && !m.Toggle: marker.ClearVisualMark() return nil default: - if thread { + if m.Thread { threadPtr, err := store.SelectedThread() if err != nil { return err @@ -138,9 +117,6 @@ func (Mark) Execute(args []string) error { return nil } case "remark": - if all || visual || toggle || thread { - return fmt.Errorf("Usage: :remark") - } marker.Remark() return nil } diff --git a/commands/msg/modify-labels.go b/commands/msg/modify-labels.go index d219a57e..6fdbeac4 100644 --- a/commands/msg/modify-labels.go +++ b/commands/msg/modify-labels.go @@ -1,7 +1,6 @@ package msg import ( - "errors" "time" "git.sr.ht/~rjarry/aerc/app" @@ -9,7 +8,9 @@ import ( "git.sr.ht/~rjarry/aerc/worker/types" ) -type ModifyLabels struct{} +type ModifyLabels struct { + Labels []string `opt:"..." metavar:"[+-]<label>"` +} func init() { register(ModifyLabels{}) @@ -23,12 +24,7 @@ func (ModifyLabels) Complete(args []string) []string { return commands.GetLabels(args) } -func (ModifyLabels) Execute(args []string) error { - changes := args[1:] - if len(changes) == 0 { - return errors.New("Usage: modify-labels <[+-]label> ...") - } - +func (m ModifyLabels) Execute(args []string) error { h := newHelper() store, err := h.store() if err != nil { @@ -40,7 +36,7 @@ func (ModifyLabels) Execute(args []string) error { } var add, remove []string - for _, l := range changes { + for _, l := range m.Labels { switch l[0] { case '+': add = append(add, l[1:]) diff --git a/commands/msg/move.go b/commands/msg/move.go index 5ef9390a..1dd68d35 100644 --- a/commands/msg/move.go +++ b/commands/msg/move.go @@ -1,8 +1,6 @@ package msg import ( - "errors" - "strings" "time" "git.sr.ht/~rjarry/aerc/app" @@ -12,10 +10,12 @@ import ( "git.sr.ht/~rjarry/aerc/lib/ui" "git.sr.ht/~rjarry/aerc/models" "git.sr.ht/~rjarry/aerc/worker/types" - "git.sr.ht/~sircmpwn/getopt" ) -type Move struct{} +type Move struct { + CreateFolders bool `opt:"-p"` + Folder string `opt:"..." metavar:"<folder>"` +} func init() { register(Move{}) @@ -29,21 +29,7 @@ func (Move) Complete(args []string) []string { return commands.GetFolders(args) } -func (Move) Execute(args []string) error { - if len(args) == 1 { - return errors.New("Usage: mv [-p] <folder>") - } - opts, optind, err := getopt.Getopts(args, "p") - if err != nil { - return err - } - var createParents bool - for _, opt := range opts { - if opt.Option == 'p' { - createParents = true - } - } - +func (m Move) Execute(args []string) error { h := newHelper() acct, err := h.account() if err != nil { @@ -64,14 +50,13 @@ func (Move) Execute(args []string) error { marker := store.Marker() marker.ClearVisualMark() next := findNextNonDeleted(uids, store) - joinedArgs := strings.Join(args[optind:], " ") - store.Move(uids, joinedArgs, createParents, func( + store.Move(uids, m.Folder, m.CreateFolders, func( msg types.WorkerMessage, ) { switch msg := msg.(type) { case *types.Done: - handleDone(acct, next, "Messages moved to "+joinedArgs, store) + handleDone(acct, next, "Messages moved to "+m.Folder, store) case *types.Error: app.PushError(msg.Error.Error()) marker.Remark() diff --git a/commands/msg/pipe.go b/commands/msg/pipe.go index c9d88f40..75b63b10 100644 --- a/commands/msg/pipe.go +++ b/commands/msg/pipe.go @@ -14,11 +14,14 @@ import ( "git.sr.ht/~rjarry/aerc/log" mboxer "git.sr.ht/~rjarry/aerc/worker/mbox" "git.sr.ht/~rjarry/aerc/worker/types" - - "git.sr.ht/~sircmpwn/getopt" ) -type Pipe struct{} +type Pipe struct { + Background bool `opt:"-b"` + Full bool `opt:"-m"` + Part bool `opt:"-p"` + Command []string `opt:"..."` +} func init() { register(Pipe{}) @@ -32,44 +35,17 @@ func (Pipe) Complete(args []string) []string { return nil } -func (Pipe) Execute(args []string) error { - var ( - background bool - pipeFull bool - pipePart bool - ) - // TODO: let user specify part by index or preferred mimetype - opts, optind, err := getopt.Getopts(args, "bmp") - if err != nil { - return err - } - for _, opt := range opts { - switch opt.Option { - case 'b': - background = true - case 'm': - if pipePart { - return errors.New("-m and -p are mutually exclusive") - } - pipeFull = true - case 'p': - if pipeFull { - return errors.New("-m and -p are mutually exclusive") - } - pipePart = true - } - } - cmd := args[optind:] - if len(cmd) == 0 { - return errors.New("Usage: pipe [-mp] <cmd> [args...]") +func (p Pipe) Execute(args []string) error { + if p.Full && p.Part { + return errors.New("-m and -p are mutually exclusive") } provider := app.SelectedTabContent().(app.ProvidesMessage) - if !pipeFull && !pipePart { + if !p.Full && !p.Part { if _, ok := provider.(*app.MessageViewer); ok { - pipePart = true + p.Part = true } else if _, ok := provider.(*app.AccountView); ok { - pipeFull = true + p.Full = true } else { return errors.New( "Neither -m nor -p specified and cannot infer default") @@ -77,7 +53,7 @@ func (Pipe) Execute(args []string) error { } doTerm := func(reader io.Reader, name string) { - term, err := commands.QuickTerm(cmd, reader) + term, err := commands.QuickTerm(p.Command, reader) if err != nil { app.PushError(err.Error()) return @@ -86,7 +62,7 @@ func (Pipe) Execute(args []string) error { } doExec := func(reader io.Reader) { - ecmd := exec.Command(cmd[0], cmd[1:]...) + ecmd := exec.Command(p.Command[0], p.Command[1:]...) pipe, err := ecmd.StdinPipe() if err != nil { return @@ -106,17 +82,19 @@ func (Pipe) Execute(args []string) error { } else { if ecmd.ProcessState.ExitCode() != 0 { app.PushError(fmt.Sprintf( - "%s: completed with status %d", cmd[0], + "%s: completed with status %d", p.Command[0], ecmd.ProcessState.ExitCode())) } else { app.PushStatus(fmt.Sprintf( - "%s: completed with status %d", cmd[0], + "%s: completed with status %d", p.Command[0], ecmd.ProcessState.ExitCode()), 10*time.Second) } } } - if pipeFull { + app.PushStatus("Fetching messages ...", 10*time.Second) + + if p.Full { var uids []uint32 var title string @@ -125,12 +103,12 @@ func (Pipe) Execute(args []string) error { if err != nil { if mv, ok := provider.(*app.MessageViewer); ok { mv.MessageView().FetchFull(func(reader io.Reader) { - if background { + if p.Background { doExec(reader) } else { doTerm(reader, fmt.Sprintf("%s <%s", - cmd[0], title)) + p.Command[0], title)) } }) return nil @@ -202,27 +180,27 @@ func (Pipe) Execute(args []string) error { } reader := newMessagesReader(messages, len(messages) > 1) - if background { + if p.Background { doExec(reader) } else { - doTerm(reader, fmt.Sprintf("%s <%s", cmd[0], title)) + doTerm(reader, fmt.Sprintf("%s <%s", p.Command[0], title)) } }() - } else if pipePart { + } else if p.Part { mv, ok := provider.(*app.MessageViewer) if !ok { return fmt.Errorf("can only pipe message part from a message view") } - p := provider.SelectedMessagePart() - if p == nil { + part := provider.SelectedMessagePart() + if part == nil { return fmt.Errorf("could not fetch message part") } - mv.MessageView().FetchBodyPart(p.Index, func(reader io.Reader) { - if background { + mv.MessageView().FetchBodyPart(part.Index, func(reader io.Reader) { + if p.Background { doExec(reader) } else { name := fmt.Sprintf("%s <%s/[%d]", - cmd[0], p.Msg.Envelope.Subject, p.Index) + p.Command[0], part.Msg.Envelope.Subject, part.Index) doTerm(reader, name) } }) diff --git a/commands/msg/read.go b/commands/msg/read.go index bac2ceb3..e55ed00e 100644 --- a/commands/msg/read.go +++ b/commands/msg/read.go @@ -2,16 +2,20 @@ package msg import ( "fmt" + "strings" "time" - "git.sr.ht/~sircmpwn/getopt" - "git.sr.ht/~rjarry/aerc/app" "git.sr.ht/~rjarry/aerc/models" "git.sr.ht/~rjarry/aerc/worker/types" ) -type FlagMsg struct{} +type FlagMsg struct { + Toggle bool `opt:"-t"` + Answered bool `opt:"-a" aliases:"flag,unflag"` + Flag models.Flags `opt:"-x" aliases:"flag,unflag" action:"ParseFlag"` + FlagName string +} func init() { register(FlagMsg{}) @@ -25,6 +29,23 @@ func (FlagMsg) Complete(args []string) []string { return nil } +func (f *FlagMsg) ParseFlag(arg string) error { + switch strings.ToLower(arg) { + case "seen": + f.Flag = models.SeenFlag + f.FlagName = "seen" + case "answered": + f.Flag = models.AnsweredFlag + f.FlagName = "answered" + case "flagged": + f.Flag = models.FlaggedFlag + f.FlagName = "flagged" + default: + return fmt.Errorf("Unknown flag %q", arg) + } + return nil +} + // If this was called as 'flag' or 'unflag', without the toggle (-t) // option, then it will flag the corresponding messages with the given // flag. If the toggle option was given, it will individually toggle @@ -32,85 +53,20 @@ func (FlagMsg) Complete(args []string) []string { // // If this was called as 'read' or 'unread', it has the same effect as // 'flag' or 'unflag', respectively, but the 'Seen' flag is affected. -func (FlagMsg) Execute(args []string) error { - // The flag to change - var flag models.Flags - // User-readable name of the flag to change - var flagName string - // Whether to toggle the flag (true) or to enable/disable it (false) - var toggle bool - // Whether to enable (true) or disable (false) the flag - enable := (args[0] == "read" || args[0] == "flag") +func (f FlagMsg) Execute(args []string) error { // User-readable name for the action being performed var actionName string - // Getopt option string, varies by command name - var getoptString string - // Help message to provide on parsing failure - var helpMessage string - // Used during parsing to prevent choosing a flag muliple times - // A default flag will be used if this is false - flagChosen := false - - if args[0] == "read" || args[0] == "unread" { - flag = models.SeenFlag - flagName = "read" - getoptString = "t" - helpMessage = "Usage: " + args[0] + " [-t]" - } else { // 'flag' / 'unflag' - flag = models.FlaggedFlag - flagName = "flagged" - getoptString = "tax:" - helpMessage = "Usage: " + args[0] + " [-t] [-a | -x <flag>]" - } - opts, optind, err := getopt.Getopts(args, getoptString) - if err != nil { - return err - } - for _, opt := range opts { - switch opt.Option { - case 't': - toggle = true - case 'a': - if flagChosen { - return fmt.Errorf("Cannot choose a flag multiple times! " + helpMessage) - } - flag = models.AnsweredFlag - flagName = "answered" - flagChosen = true - case 'x': - if flagChosen { - return fmt.Errorf("Cannot choose a flag multiple times! " + helpMessage) - } - // TODO: Support all flags? - switch opt.Value { - case "Seen": - flag = models.SeenFlag - flagName = "seen" - case "Answered": - flag = models.AnsweredFlag - flagName = "answered" - case "Flagged": - flag = models.FlaggedFlag - flagName = "flagged" - default: - return fmt.Errorf("Unknown / Prohibited flag \"%v\"", opt.Value) - } - flagChosen = true + switch args[0] { + case "read", "unread": + f.Flag = models.SeenFlag + f.FlagName = "seen" + case "flag", "unflag": + if f.Flag == 0 { + f.Flag = models.FlaggedFlag + f.FlagName = "flagged" } } - switch { - case toggle: - actionName = "Toggling" - case enable: - actionName = "Setting" - default: - actionName = "Unsetting" - } - if optind != len(args) { - // Any non-option arguments: Error - return fmt.Errorf(helpMessage) - } h := newHelper() store, err := h.store() @@ -122,7 +78,7 @@ func (FlagMsg) Execute(args []string) error { var toEnable []uint32 var toDisable []uint32 - if toggle { + if f.Toggle { // If toggling, split messages into those that need to // be enabled / disabled. msgs, err := h.messages() @@ -130,29 +86,35 @@ func (FlagMsg) Execute(args []string) error { return err } for _, m := range msgs { - if m.Flags.Has(flag) { + if m.Flags.Has(f.Flag) { toDisable = append(toDisable, m.Uid) } else { toEnable = append(toEnable, m.Uid) } } + actionName = "Toggling" } else { msgUids, err := h.markedOrSelectedUids() if err != nil { return err } - if enable { + switch args[0] { + case "read", "flag": toEnable = msgUids - } else { + actionName = "Setting" + default: toDisable = msgUids + actionName = "Unsetting" } } + status := fmt.Sprintf("%s flag %q successful", actionName, f.FlagName) + if len(toEnable) != 0 { - store.Flag(toEnable, flag, true, func(msg types.WorkerMessage) { + store.Flag(toEnable, f.Flag, true, func(msg types.WorkerMessage) { switch msg := msg.(type) { case *types.Done: - app.PushStatus(actionName+" flag '"+flagName+"' successful", 10*time.Second) + app.PushStatus(status, 10*time.Second) store.Marker().ClearVisualMark() case *types.Error: app.PushError(msg.Error.Error()) @@ -160,10 +122,10 @@ func (FlagMsg) Execute(args []string) error { }) } if len(toDisable) != 0 { - store.Flag(toDisable, flag, false, func(msg types.WorkerMessage) { + store.Flag(toDisable, f.Flag, false, func(msg types.WorkerMessage) { switch msg := msg.(type) { case *types.Done: - app.PushStatus(actionName+" flag '"+flagName+"' successful", 10*time.Second) + app.PushStatus(status, 10*time.Second) store.Marker().ClearVisualMark() case *types.Error: app.PushError(msg.Error.Error()) diff --git a/commands/msg/recall.go b/commands/msg/recall.go index 2d999b1d..3b78a763 100644 --- a/commands/msg/recall.go +++ b/commands/msg/recall.go @@ -15,10 +15,13 @@ import ( "git.sr.ht/~rjarry/aerc/lib" "git.sr.ht/~rjarry/aerc/log" "git.sr.ht/~rjarry/aerc/worker/types" - "git.sr.ht/~sircmpwn/getopt" ) -type Recall struct{} +type Recall struct { + Force bool `opt:"-f"` + Edit bool `opt:"-e"` + NoEdit bool `opt:"-E"` +} func init() { register(Recall{}) @@ -32,34 +35,15 @@ func (Recall) Complete(args []string) []string { return nil } -func (Recall) Execute(args []string) error { - force := false - editHeaders := config.Compose.EditHeaders - - opts, optind, err := getopt.Getopts(args, "feE") - if err != nil { - return err - } - for _, opt := range opts { - switch opt.Option { - case 'f': - force = true - case 'e': - editHeaders = true - case 'E': - editHeaders = false - } - } - if len(args) != optind { - return errors.New("Usage: recall [-f] [-e|-E]") - } +func (r Recall) Execute(args []string) error { + editHeaders := (config.Compose.EditHeaders || r.Edit) && !r.NoEdit widget := app.SelectedTabContent().(app.ProvidesMessage) acct := widget.SelectedAccount() if acct == nil { return errors.New("No account selected") } - if acct.SelectedDirectory() != acct.AccountConfig().Postpone && !force { + if acct.SelectedDirectory() != acct.AccountConfig().Postpone && !r.Force { return errors.New("Use -f to recall from outside the " + acct.AccountConfig().Postpone + " directory.") } @@ -164,7 +148,7 @@ func (Recall) Execute(args []string) error { }) } - if force { + if r.Force { composer.SetRecalledFrom(acct.SelectedDirectory()) } diff --git a/commands/msg/reply.go b/commands/msg/reply.go index fff90fff..2ab9e9f8 100644 --- a/commands/msg/reply.go +++ b/commands/msg/reply.go @@ -9,8 +9,6 @@ import ( "strings" "time" - "git.sr.ht/~sircmpwn/getopt" - "git.sr.ht/~rjarry/aerc/app" "git.sr.ht/~rjarry/aerc/commands/account" "git.sr.ht/~rjarry/aerc/config" @@ -24,7 +22,14 @@ import ( "github.com/emersion/go-message/mail" ) -type reply struct{} +type reply struct { + All bool `opt:"-a"` + Close bool `opt:"-c"` + Quote bool `opt:"-q"` + Template string `opt:"-T"` + Edit bool `opt:"-e"` + NoEdit bool `opt:"-E"` +} func init() { register(reply{}) @@ -38,37 +43,8 @@ func (reply) Complete(args []string) []string { return nil } -func (reply) Execute(args []string) error { - opts, optind, err := getopt.Getopts(args, "acqT:eE") - if err != nil { - return err - } - if optind != len(args) { - return errors.New("Usage: reply [-acq -T <template>] [-e|-E]") - } - var ( - quote bool - replyAll bool - closeOnReply bool - template string - ) - editHeaders := config.Compose.EditHeaders - for _, opt := range opts { - switch opt.Option { - case 'a': - replyAll = true - case 'c': - closeOnReply = true - case 'q': - quote = true - case 'T': - template = opt.Value - case 'e': - editHeaders = true - case 'E': - editHeaders = false - } - } +func (r reply) Execute(args []string) error { + editHeaders := (config.Compose.EditHeaders || r.Edit) && !r.NoEdit widget := app.SelectedTabContent().(app.ProvidesMessage) acct := widget.SelectedAccount() @@ -116,7 +92,7 @@ func (reply) Execute(args []string) error { recSet.AddList(to) - if replyAll { + if r.All { // order matters, due to the deduping // in order of importance, first parse the To, then the Cc header @@ -165,12 +141,12 @@ func (reply) Execute(args []string) error { addTab := func() error { composer, err := app.NewComposer(acct, acct.AccountConfig(), acct.Worker(), editHeaders, - template, h, &original, nil) + r.Template, h, &original, nil) if err != nil { app.PushError("Error: " + err.Error()) return err } - if mv != nil && closeOnReply { + if mv != nil && r.Close { app.RemoveTab(mv, true) } @@ -190,18 +166,19 @@ func (reply) Execute(args []string) error { } case c.Sent(): store.Answered([]uint32{msg.Uid}, true, nil) - case mv != nil && closeOnReply: + case mv != nil && r.Close: + view := account.ViewMessage{Peek: true} //nolint:errcheck // who cares? - account.ViewMessage{}.Execute([]string{"-p"}) + view.Execute([]string{"view", "-p"}) } }) return nil } - if quote { - if template == "" { - template = config.Templates.QuotedReply + if r.Quote { + if r.Template == "" { + r.Template = config.Templates.QuotedReply } if crypto.IsEncrypted(msg.BodyStructure) { @@ -256,8 +233,8 @@ func (reply) Execute(args []string) error { }) return nil } else { - if template == "" { - template = config.Templates.NewMessage + if r.Template == "" { + r.Template = config.Templates.NewMessage } return addTab() } diff --git a/commands/msg/toggle-thread-context.go b/commands/msg/toggle-thread-context.go index 7530bc9e..0ef6778f 100644 --- a/commands/msg/toggle-thread-context.go +++ b/commands/msg/toggle-thread-context.go @@ -1,8 +1,6 @@ package msg import ( - "errors" - "git.sr.ht/~rjarry/aerc/lib/ui" ) @@ -21,9 +19,6 @@ func (ToggleThreadContext) Complete(args []string) []string { } func (ToggleThreadContext) Execute(args []string) error { - if len(args) != 1 { - return errors.New("Usage: toggle-entire-thread") - } h := newHelper() store, err := h.store() if err != nil { diff --git a/commands/msg/toggle-threads.go b/commands/msg/toggle-threads.go index 1d38685a..88cc763f 100644 --- a/commands/msg/toggle-threads.go +++ b/commands/msg/toggle-threads.go @@ -1,8 +1,6 @@ package msg import ( - "errors" - "git.sr.ht/~rjarry/aerc/lib/state" "git.sr.ht/~rjarry/aerc/lib/ui" ) @@ -22,9 +20,6 @@ func (ToggleThreads) Complete(args []string) []string { } func (ToggleThreads) Execute(args []string) error { - if len(args) != 1 { - return errors.New("Usage: toggle-threads") - } h := newHelper() acct, err := h.account() if err != nil { diff --git a/commands/msg/unsubscribe.go b/commands/msg/unsubscribe.go index 069aedab..a489b3b9 100644 --- a/commands/msg/unsubscribe.go +++ b/commands/msg/unsubscribe.go @@ -12,13 +12,15 @@ import ( "git.sr.ht/~rjarry/aerc/config" "git.sr.ht/~rjarry/aerc/lib" "git.sr.ht/~rjarry/aerc/log" - "git.sr.ht/~sircmpwn/getopt" "github.com/emersion/go-message/mail" ) // Unsubscribe helps people unsubscribe from mailing lists by way of the // List-Unsubscribe header. -type Unsubscribe struct{} +type Unsubscribe struct { + Edit bool `opt:"-e"` + NoEdit bool `opt:"-E"` +} func init() { register(Unsubscribe{}) @@ -35,23 +37,9 @@ func (Unsubscribe) Complete(args []string) []string { } // Execute runs the Unsubscribe command -func (Unsubscribe) Execute(args []string) error { - editHeaders := config.Compose.EditHeaders - opts, optind, err := getopt.Getopts(args, "eE") - if err != nil { - return err - } - if len(args) != optind { - return errors.New("Usage: unsubscribe [-e|-E]") - } - for _, opt := range opts { - switch opt.Option { - case 'e': - editHeaders = true - case 'E': - editHeaders = false - } - } +func (u Unsubscribe) Execute(args []string) error { + editHeaders := (config.Compose.EditHeaders || u.Edit) && !u.NoEdit + widget := app.SelectedTabContent().(app.ProvidesMessage) msg, err := widget.SelectedMessage() if err != nil { |