aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobin Jarry <robin@jarry.cc>2023-10-03 22:12:10 +0200
committerRobin Jarry <robin@jarry.cc>2023-10-28 19:24:49 +0200
commite54486ee40c9cedde9d4dae3e89d8b5f932188ee (patch)
tree254f032ef94920c28fbb9be8f918e61082a2015d
parent9a4518476d8c8f28340c6b44cd808e6d58fbeb98 (diff)
downloadaerc-e54486ee40c9cedde9d4dae3e89d8b5f932188ee.tar.gz
aerc-e54486ee40c9cedde9d4dae3e89d8b5f932188ee.zip
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>
-rw-r--r--commands/account/cf.go16
-rw-r--r--commands/account/clear.go25
-rw-r--r--commands/account/compose.go85
-rw-r--r--commands/account/connection.go2
-rw-r--r--commands/account/expand-folder.go8
-rw-r--r--commands/account/export-mbox.go25
-rw-r--r--commands/account/import-mbox.go19
-rw-r--r--commands/account/mkdir.go14
-rw-r--r--commands/account/next-folder.go29
-rw-r--r--commands/account/next-result.go8
-rw-r--r--commands/account/next.go57
-rw-r--r--commands/account/recover.go56
-rw-r--r--commands/account/rmdir.go28
-rw-r--r--commands/account/search.go4
-rw-r--r--commands/account/select.go22
-rw-r--r--commands/account/sort.go4
-rw-r--r--commands/account/split.go63
-rw-r--r--commands/account/view.go24
-rw-r--r--commands/cd.go18
-rw-r--r--commands/choose.go4
-rw-r--r--commands/commands.go8
-rw-r--r--commands/compose/abort.go7
-rw-r--r--commands/compose/attach-key.go7
-rw-r--r--commands/compose/attach.go81
-rw-r--r--commands/compose/cc-bcc.go16
-rw-r--r--commands/compose/detach.go18
-rw-r--r--commands/compose/edit.go27
-rw-r--r--commands/compose/encrypt.go7
-rw-r--r--commands/compose/header.go40
-rw-r--r--commands/compose/multipart.go36
-rw-r--r--commands/compose/next-field.go9
-rw-r--r--commands/compose/postpone.go22
-rw-r--r--commands/compose/send.go75
-rw-r--r--commands/compose/sign.go5
-rw-r--r--commands/compose/switch.go42
-rw-r--r--commands/ct.go21
-rw-r--r--commands/eml.go14
-rw-r--r--commands/exec.go13
-rw-r--r--commands/help.go30
-rw-r--r--commands/move-tab.go38
-rw-r--r--commands/msg/archive.go24
-rw-r--r--commands/msg/copy.go28
-rw-r--r--commands/msg/delete.go5
-rw-r--r--commands/msg/envelope.go29
-rw-r--r--commands/msg/fold.go4
-rw-r--r--commands/msg/forward.go54
-rw-r--r--commands/msg/invite.go25
-rw-r--r--commands/msg/mark.go68
-rw-r--r--commands/msg/modify-labels.go14
-rw-r--r--commands/msg/move.go29
-rw-r--r--commands/msg/pipe.go80
-rw-r--r--commands/msg/read.go132
-rw-r--r--commands/msg/recall.go34
-rw-r--r--commands/msg/reply.go65
-rw-r--r--commands/msg/toggle-thread-context.go5
-rw-r--r--commands/msg/toggle-threads.go5
-rw-r--r--commands/msg/unsubscribe.go26
-rw-r--r--commands/msgview/close.go5
-rw-r--r--commands/msgview/next-part.go28
-rw-r--r--commands/msgview/next.go30
-rw-r--r--commands/msgview/open-link.go22
-rw-r--r--commands/msgview/open.go24
-rw-r--r--commands/msgview/save.go83
-rw-r--r--commands/msgview/toggle-headers.go9
-rw-r--r--commands/msgview/toggle-key-passthrough.go5
-rw-r--r--commands/new-account.go19
-rw-r--r--commands/next-tab.go28
-rw-r--r--commands/pin-tab.go6
-rw-r--r--commands/prompt.go17
-rw-r--r--commands/pwd.go4
-rw-r--r--commands/quit.go23
-rw-r--r--commands/send-keys.go13
-rw-r--r--commands/term.go21
-rw-r--r--commands/terminal/close.go5
-rw-r--r--commands/z.go23
75 files changed, 673 insertions, 1346 deletions
diff --git a/commands/account/cf.go b/commands/account/cf.go
index cd93ed29..59203a89 100644
--- a/commands/account/cf.go
+++ b/commands/account/cf.go
@@ -2,7 +2,6 @@ package account
import (
"errors"
- "strings"
"git.sr.ht/~rjarry/aerc/app"
"git.sr.ht/~rjarry/aerc/commands"
@@ -11,7 +10,9 @@ import (
var history map[string]string
-type ChangeFolder struct{}
+type ChangeFolder struct {
+ Folder string `opt:"..." metavar:"<folder>"`
+}
func init() {
history = make(map[string]string)
@@ -26,24 +27,21 @@ func (ChangeFolder) Complete(args []string) []string {
return commands.GetFolders(args)
}
-func (ChangeFolder) Execute(args []string) error {
- if len(args) == 1 {
- return errors.New("Usage: cf <folder>")
- }
+func (c ChangeFolder) Execute(args []string) error {
acct := app.SelectedAccount()
if acct == nil {
return errors.New("No account selected")
}
previous := acct.Directories().Selected()
- joinedArgs := strings.Join(args[1:], " ")
- if joinedArgs == "-" {
+
+ if c.Folder == "-" {
if dir, ok := history[acct.Name()]; ok {
acct.Directories().Select(dir)
} else {
return errors.New("No previous folder to return to")
}
} else {
- acct.Directories().Select(joinedArgs)
+ acct.Directories().Select(c.Folder)
}
history[acct.Name()] = previous
diff --git a/commands/account/clear.go b/commands/account/clear.go
index b67454dd..fd209d07 100644
--- a/commands/account/clear.go
+++ b/commands/account/clear.go
@@ -5,10 +5,11 @@ import (
"git.sr.ht/~rjarry/aerc/app"
"git.sr.ht/~rjarry/aerc/lib/state"
- "git.sr.ht/~sircmpwn/getopt"
)
-type Clear struct{}
+type Clear struct {
+ Selected bool `opt:"-s"`
+}
func init() {
register(Clear{})
@@ -22,7 +23,7 @@ func (Clear) Complete(args []string) []string {
return nil
}
-func (Clear) Execute(args []string) error {
+func (c Clear) Execute(args []string) error {
acct := app.SelectedAccount()
if acct == nil {
return errors.New("No account selected")
@@ -32,23 +33,7 @@ func (Clear) Execute(args []string) error {
return errors.New("Cannot perform action. Messages still loading")
}
- clearSelected := false
- opts, optind, err := getopt.Getopts(args, "s")
- if err != nil {
- return err
- }
-
- for _, opt := range opts {
- if opt.Option == 's' {
- clearSelected = true
- }
- }
-
- if len(args) != optind {
- return errors.New("Usage: clear [-s]")
- }
-
- if clearSelected {
+ if c.Selected {
defer store.Select(0)
}
store.ApplyClear()
diff --git a/commands/account/compose.go b/commands/account/compose.go
index 56079868..81eb3de0 100644
--- a/commands/account/compose.go
+++ b/commands/account/compose.go
@@ -12,15 +12,31 @@ import (
"git.sr.ht/~rjarry/aerc/app"
"git.sr.ht/~rjarry/aerc/config"
- "git.sr.ht/~sircmpwn/getopt"
)
-type Compose struct{}
+type Compose struct {
+ Headers string `opt:"-H" action:"ParseHeader"`
+ Template string `opt:"-T"`
+ Edit bool `opt:"-e"`
+ NoEdit bool `opt:"-E"`
+ Body string `opt:"..." required:"false"`
+}
func init() {
register(Compose{})
}
+func (c *Compose) ParseHeader(arg string) error {
+ if strings.Contains(arg, ":") {
+ // ensure first colon is followed by a single space
+ re := regexp.MustCompile(`^(.*?):\s*(.*)`)
+ c.Headers += re.ReplaceAllString(arg, "$1: $2\r\n")
+ } else {
+ c.Headers += arg + ":\r\n"
+ }
+ return nil
+}
+
func (Compose) Aliases() []string {
return []string{"compose"}
}
@@ -29,20 +45,25 @@ func (Compose) Complete(args []string) []string {
return nil
}
-func (Compose) Execute(args []string) error {
- body, template, editHeaders, err := buildBody(args)
- if err != nil {
- return err
+func (c Compose) Execute(args []string) error {
+ if c.Headers != "" {
+ if c.Body != "" {
+ c.Body = c.Headers + "\r\n" + c.Body
+ } else {
+ c.Body = c.Headers + "\r\n\r\n"
+ }
}
+ if c.Template == "" {
+ c.Template = config.Templates.NewMessage
+ }
+ editHeaders := (config.Compose.EditHeaders || c.Edit) && !c.NoEdit
+
acct := app.SelectedAccount()
if acct == nil {
return errors.New("No account selected")
}
- if template == "" {
- template = config.Templates.NewMessage
- }
- msg, err := gomail.ReadMessage(strings.NewReader(body))
+ msg, err := gomail.ReadMessage(strings.NewReader(c.Body))
if errors.Is(err, io.EOF) { // completely empty
msg = &gomail.Message{Body: strings.NewReader("")}
} else if err != nil {
@@ -52,52 +73,10 @@ func (Compose) Execute(args []string) error {
composer, err := app.NewComposer(acct,
acct.AccountConfig(), acct.Worker(), editHeaders,
- template, &headers, nil, msg.Body)
+ c.Template, &headers, nil, msg.Body)
if err != nil {
return err
}
composer.Tab = app.NewTab(composer, "New email")
return nil
}
-
-func buildBody(args []string) (string, string, bool, error) {
- var body, template, headers string
- editHeaders := config.Compose.EditHeaders
- opts, optind, err := getopt.Getopts(args, "H:T:eE")
- if err != nil {
- return "", "", false, err
- }
- for _, opt := range opts {
- switch opt.Option {
- case 'H':
- if strings.Contains(opt.Value, ":") {
- // ensure first colon is followed by a single space
- re := regexp.MustCompile(`^(.*?):\s*(.*)`)
- headers += re.ReplaceAllString(opt.Value, "$1: $2") + "\n"
- } else {
- headers += opt.Value + ":\n"
- }
- case 'T':
- template = opt.Value
- case 'e':
- editHeaders = true
- case 'E':
- editHeaders = false
- }
- }
- posargs := args[optind:]
- if len(posargs) > 1 {
- return "", "", false, errors.New("Usage: compose [-H header] [-T template] [-e|-E] [body]")
- }
- if len(posargs) == 1 {
- body = posargs[0]
- }
- if headers != "" {
- if len(body) > 0 {
- body = headers + "\n" + body
- } else {
- body = headers + "\n\n"
- }
- }
- return body, template, editHeaders, nil
-}
diff --git a/commands/account/connection.go b/commands/account/connection.go
index 3b30dbda..b9cd887b 100644
--- a/commands/account/connection.go
+++ b/commands/account/connection.go
@@ -22,7 +22,7 @@ func (Connection) Complete(args []string) []string {
return nil
}
-func (Connection) Execute(args []string) error {
+func (c Connection) Execute(args []string) error {
acct := app.SelectedAccount()
if acct == nil {
return errors.New("No account selected")
diff --git a/commands/account/expand-folder.go b/commands/account/expand-folder.go
index 29203adc..f26da70a 100644
--- a/commands/account/expand-folder.go
+++ b/commands/account/expand-folder.go
@@ -2,7 +2,6 @@ package account
import (
"errors"
- "fmt"
"git.sr.ht/~rjarry/aerc/app"
)
@@ -22,9 +21,6 @@ func (ExpandCollapseFolder) Complete(args []string) []string {
}
func (ExpandCollapseFolder) Execute(args []string) error {
- if len(args) > 1 {
- return expandCollapseFolderUsage(args[0])
- }
acct := app.SelectedAccount()
if acct == nil {
return errors.New("No account selected")
@@ -36,7 +32,3 @@ func (ExpandCollapseFolder) Execute(args []string) error {
}
return nil
}
-
-func expandCollapseFolderUsage(cmd string) error {
- return fmt.Errorf("Usage: %s", cmd)
-}
diff --git a/commands/account/export-mbox.go b/commands/account/export-mbox.go
index a17b8a29..00e03ca6 100644
--- a/commands/account/export-mbox.go
+++ b/commands/account/export-mbox.go
@@ -16,7 +16,9 @@ import (
"git.sr.ht/~rjarry/aerc/worker/types"
)
-type ExportMbox struct{}
+type ExportMbox struct {
+ Filename string `opt:"filename"`
+}
func init() {
register(ExportMbox{})
@@ -30,12 +32,7 @@ func (ExportMbox) Complete(args []string) []string {
return commands.CompletePath(filepath.Join(args...))
}
-func (ExportMbox) Execute(args []string) error {
- if len(args) != 2 {
- return exportFolderUsage(args[0])
- }
- filename := args[1]
-
+func (e ExportMbox) Execute(args []string) error {
acct := app.SelectedAccount()
if acct == nil {
return errors.New("No account selected")
@@ -45,16 +42,16 @@ func (ExportMbox) Execute(args []string) error {
return errors.New("No message store selected")
}
- fi, err := os.Stat(filename)
+ fi, err := os.Stat(e.Filename)
if err == nil && fi.IsDir() {
if path := acct.SelectedDirectory(); path != "" {
if f := filepath.Base(path); f != "" {
- filename += f + ".mbox"
+ e.Filename = filepath.Join(e.Filename, f+".mbox")
}
}
}
- app.PushStatus("Exporting to "+filename, 10*time.Second)
+ app.PushStatus("Exporting to "+e.Filename, 10*time.Second)
// uids of messages to export
var uids []uint32
@@ -85,7 +82,7 @@ func (ExportMbox) Execute(args []string) error {
go func() {
defer log.PanicHandler()
- file, err := os.Create(filename)
+ file, err := os.Create(e.Filename)
if err != nil {
log.Errorf("failed to create file: %v", err)
app.PushError(err.Error())
@@ -147,7 +144,7 @@ func (ExportMbox) Execute(args []string) error {
}
retries++
}
- statusInfo := fmt.Sprintf("Exported %d of %d messages to %s.", ctr, total, filename)
+ statusInfo := fmt.Sprintf("Exported %d of %d messages to %s.", ctr, total, e.Filename)
app.PushStatus(statusInfo, 10*time.Second)
log.Debugf(statusInfo)
}()
@@ -155,10 +152,6 @@ func (ExportMbox) Execute(args []string) error {
return nil
}
-func exportFolderUsage(cmd string) error {
- return fmt.Errorf("Usage: %s <filename>", cmd)
-}
-
func sortMarkedUids(marked []uint32, store *lib.MessageStore) ([]uint32, error) {
lookup := map[uint32]bool{}
for _, uid := range marked {
diff --git a/commands/account/import-mbox.go b/commands/account/import-mbox.go
index 4ede8c26..b3ad2a08 100644
--- a/commands/account/import-mbox.go
+++ b/commands/account/import-mbox.go
@@ -18,7 +18,9 @@ import (
"git.sr.ht/~rjarry/aerc/worker/types"
)
-type ImportMbox struct{}
+type ImportMbox struct {
+ Filename string `opt:"filename"`
+}
func init() {
register(ImportMbox{})
@@ -32,12 +34,7 @@ func (ImportMbox) Complete(args []string) []string {
return commands.CompletePath(filepath.Join(args...))
}
-func (ImportMbox) Execute(args []string) error {
- if len(args) != 2 {
- return importFolderUsage(args[0])
- }
- filename := args[1]
-
+func (i ImportMbox) Execute(args []string) error {
acct := app.SelectedAccount()
if acct == nil {
return errors.New("No account selected")
@@ -54,10 +51,10 @@ func (ImportMbox) Execute(args []string) error {
importFolder := func() {
defer log.PanicHandler()
- statusInfo := fmt.Sprintln("Importing", filename, "to folder", folder)
+ statusInfo := fmt.Sprintln("Importing", i.Filename, "to folder", folder)
app.PushStatus(statusInfo, 10*time.Second)
log.Debugf(statusInfo)
- f, err := os.Open(filename)
+ f, err := os.Open(i.Filename)
if err != nil {
app.PushError(err.Error())
return
@@ -147,7 +144,3 @@ func (ImportMbox) Execute(args []string) error {
return nil
}
-
-func importFolderUsage(cmd string) error {
- return fmt.Errorf("Usage: %s <filename>", cmd)
-}
diff --git a/commands/account/mkdir.go b/commands/account/mkdir.go
index 7a1f7b9e..9beeb01a 100644
--- a/commands/account/mkdir.go
+++ b/commands/account/mkdir.go
@@ -9,7 +9,9 @@ import (
"git.sr.ht/~rjarry/aerc/worker/types"
)
-type MakeDir struct{}
+type MakeDir struct {
+ Folder string `opt:"..." metavar:"<folder>"`
+}
func init() {
register(MakeDir{})
@@ -41,22 +43,18 @@ func (MakeDir) Complete(args []string) []string {
return inboxes
}
-func (MakeDir) Execute(args []string) error {
- if len(args) == 0 {
- return errors.New("Usage: :mkdir <name>")
- }
+func (m MakeDir) Execute(args []string) error {
acct := app.SelectedAccount()
if acct == nil {
return errors.New("No account selected")
}
- name := strings.Join(args[1:], " ")
acct.Worker().PostAction(&types.CreateDirectory{
- Directory: name,
+ Directory: m.Folder,
}, func(msg types.WorkerMessage) {
switch msg := msg.(type) {
case *types.Done:
app.PushStatus("Directory created.", 10*time.Second)
- acct.Directories().Select(name)
+ acct.Directories().Select(m.Folder)
case *types.Error:
app.PushError(msg.Error.Error())
}
diff --git a/commands/account/next-folder.go b/commands/account/next-folder.go
index 2b23e6da..1b5651c8 100644
--- a/commands/account/next-folder.go
+++ b/commands/account/next-folder.go
@@ -2,13 +2,13 @@ package account
import (
"errors"
- "fmt"
- "strconv"
"git.sr.ht/~rjarry/aerc/app"
)
-type NextPrevFolder struct{}
+type NextPrevFolder struct {
+ Offset int `opt:"n" default:"1"`
+}
func init() {
register(NextPrevFolder{})
@@ -22,32 +22,15 @@ func (NextPrevFolder) Complete(args []string) []string {
return nil
}
-func (NextPrevFolder) Execute(args []string) error {
- if len(args) > 2 {
- return nextPrevFolderUsage(args[0])
- }
- var (
- n int = 1
- err error
- )
- if len(args) > 1 {
- n, err = strconv.Atoi(args[1])
- if err != nil {
- return nextPrevFolderUsage(args[0])
- }
- }
+func (np NextPrevFolder) Execute(args []string) error {
acct := app.SelectedAccount()
if acct == nil {
return errors.New("No account selected")
}
if args[0] == "prev-folder" {
- acct.Directories().NextPrev(-n)
+ acct.Directories().NextPrev(-np.Offset)
} else {
- acct.Directories().NextPrev(n)
+ acct.Directories().NextPrev(np.Offset)
}
return nil
}
-
-func nextPrevFolderUsage(cmd string) error {
- return fmt.Errorf("Usage: %s [n]", cmd)
-}
diff --git a/commands/account/next-result.go b/commands/account/next-result.go
index ef4078da..e841899f 100644
--- a/commands/account/next-result.go
+++ b/commands/account/next-result.go
@@ -2,7 +2,6 @@ package account
import (
"errors"
- "fmt"
"git.sr.ht/~rjarry/aerc/app"
"git.sr.ht/~rjarry/aerc/lib/ui"
@@ -23,9 +22,6 @@ func (NextPrevResult) Complete(args []string) []string {
}
func (NextPrevResult) Execute(args []string) error {
- if len(args) > 1 {
- return nextPrevResultUsage(args[0])
- }
acct := app.SelectedAccount()
if acct == nil {
return errors.New("No account selected")
@@ -45,7 +41,3 @@ func (NextPrevResult) Execute(args []string) error {
}
return nil
}
-
-func nextPrevResultUsage(cmd string) error {
- return fmt.Errorf("Usage: %s [<n>[%%]]", cmd)
-}
diff --git a/commands/account/next.go b/commands/account/next.go
index fff36bd1..14679b1b 100644
--- a/commands/account/next.go
+++ b/commands/account/next.go
@@ -2,7 +2,6 @@ package account
import (
"errors"
- "fmt"
"strconv"
"strings"
@@ -10,12 +9,28 @@ import (
"git.sr.ht/~rjarry/aerc/lib/ui"
)
-type NextPrevMsg struct{}
+type NextPrevMsg struct {
+ Amount int `opt:"n" default:"1" metavar:"<n>[%]" action:"ParseAmount"`
+ Percent bool
+}
func init() {
register(NextPrevMsg{})
}
+func (np *NextPrevMsg) ParseAmount(arg string) error {
+ if strings.HasSuffix(arg, "%") {
+ np.Percent = true
+ arg = strings.TrimSuffix(arg, "%")
+ }
+ i, err := strconv.ParseInt(arg, 10, 64)
+ if err != nil {
+ return err
+ }
+ np.Amount = int(i)
+ return nil
+}
+
func (NextPrevMsg) Aliases() []string {
return []string{"next", "next-message", "prev", "prev-message"}
}
@@ -24,42 +39,14 @@ func (NextPrevMsg) Complete(args []string) []string {
return nil
}
-func (NextPrevMsg) Execute(args []string) error {
- n, pct, err := ParseNextPrevMessage(args)
- if err != nil {
- return err
- }
+func (np NextPrevMsg) Execute(args []string) error {
acct := app.SelectedAccount()
if acct == nil {
return errors.New("No account selected")
}
- return ExecuteNextPrevMessage(args, acct, pct, n)
-}
-
-func ParseNextPrevMessage(args []string) (int, bool, error) {
- if len(args) > 2 {
- return 0, false, nextPrevMessageUsage(args[0])
- }
- var (
- n int = 1
- err error
- pct bool
- )
- if len(args) > 1 {
- if strings.HasSuffix(args[1], "%") {
- pct = true
- args[1] = args[1][:len(args[1])-1]
- }
- n, err = strconv.Atoi(args[1])
- if err != nil {
- return 0, false, nextPrevMessageUsage(args[0])
- }
- }
- return n, pct, nil
-}
-func ExecuteNextPrevMessage(args []string, acct *app.AccountView, pct bool, n int) error {
- if pct {
+ n := np.Amount
+ if np.Percent {
n = int(float64(acct.Messages().Height()) * (float64(n) / 100.0))
}
if args[0] == "prev-message" || args[0] == "prev" {
@@ -77,7 +64,3 @@ func ExecuteNextPrevMessage(args []string, acct *app.AccountView, pct bool, n in
}
return nil
}
-
-func nextPrevMessageUsage(cmd string) error {
- return fmt.Errorf("Usage: %s [<n>[%%]]", cmd)
-}
diff --git a/commands/account/recover.go b/commands/account/recover.go
index e0d1c6eb..dae3d807 100644
--- a/commands/account/recover.go
+++ b/commands/account/recover.go
@@ -10,10 +10,14 @@ import (
"git.sr.ht/~rjarry/aerc/app"
"git.sr.ht/~rjarry/aerc/commands"
"git.sr.ht/~rjarry/aerc/config"
- "git.sr.ht/~sircmpwn/getopt"
)
-type Recover struct{}
+type Recover struct {
+ Force bool `opt:"-f"`
+ Edit bool `opt:"-e"`
+ NoEdit bool `opt:"-E"`
+ File string `opt:"file"`
+}
func init() {
register(Recover{})
@@ -40,31 +44,14 @@ func (r Recover) Complete(args []string) []string {
}
func (r Recover) Execute(args []string) error {
- // Complete() expects to be passed only the arguments, not including the command name
- if len(Recover{}.Complete(args[1:])) == 0 {
- return errors.New("No messages to recover.")
- }
-
- force := false
- editHeaders := config.Compose.EditHeaders
-
- opts, optind, err := getopt.Getopts(args, r.Options())
+ file, err := os.Open(r.File)
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: recover [-f] [-E|-e] <file>")
+ defer file.Close()
+ data, err := io.ReadAll(file)
+ if err != nil {
+ return err
}
acct := app.SelectedAccount()
@@ -72,22 +59,7 @@ func (r Recover) Execute(args []string) error {
return errors.New("No account selected")
}
- readData := func() ([]byte, error) {
- recoverFile, err := os.Open(args[optind])
- if err != nil {
- return nil, err
- }
- defer recoverFile.Close()
- data, err := io.ReadAll(recoverFile)
- if err != nil {
- return nil, err
- }
- return data, nil
- }
- data, err := readData()
- if err != nil {
- return err
- }
+ editHeaders := (config.Compose.EditHeaders || r.Edit) && !r.NoEdit
composer, err := app.NewComposer(acct,
acct.AccountConfig(), acct.Worker(), editHeaders,
@@ -98,8 +70,8 @@ func (r Recover) Execute(args []string) error {
composer.Tab = app.NewTab(composer, "Recovered")
// remove file if force flag is set
- if force {
- err = os.Remove(args[optind])
+ if r.Force {
+ err = os.Remove(r.File)
if err != nil {
return err
}
diff --git a/commands/account/rmdir.go b/commands/account/rmdir.go
index 3b109a67..36bfc699 100644
--- a/commands/account/rmdir.go
+++ b/commands/account/rmdir.go
@@ -4,13 +4,13 @@ import (
"errors"
"time"
- "git.sr.ht/~sircmpwn/getopt"
-
"git.sr.ht/~rjarry/aerc/app"
"git.sr.ht/~rjarry/aerc/worker/types"
)
-type RemoveDir struct{}
+type RemoveDir struct {
+ Force bool `opt:"-f"`
+}
func init() {
register(RemoveDir{})
@@ -24,30 +24,14 @@ func (RemoveDir) Complete(args []string) []string {
return nil
}
-func (RemoveDir) Execute(args []string) error {
+func (r RemoveDir) Execute(args []string) error {
acct := app.SelectedAccount()
if acct == nil {
return errors.New("No account selected")
}
- force := false
-
- opts, optind, err := getopt.Getopts(args, "f")
- if err != nil {
- return err
- }
- for _, opt := range opts {
- if opt.Option == 'f' {
- force = true
- }
- }
-
- if len(args) != optind {
- return errors.New("Usage: rmdir [-f]")
- }
-
// Check for any messages in the directory.
- if !acct.Messages().Empty() && !force {
+ if !acct.Messages().Empty() && !r.Force {
return errors.New("Refusing to remove non-empty directory; use -f")
}
@@ -80,7 +64,7 @@ func (RemoveDir) Execute(args []string) error {
acct.Worker().PostAction(&types.RemoveDirectory{
Directory: curDir,
- Quiet: force,
+ Quiet: r.Force,
}, func(msg types.WorkerMessage) {
switch msg := msg.(type) {
case *types.Done:
diff --git a/commands/account/search.go b/commands/account/search.go
index 51bd3042..7b98d98b 100644
--- a/commands/account/search.go
+++ b/commands/account/search.go
@@ -12,7 +12,9 @@ import (
"git.sr.ht/~rjarry/aerc/worker/types"
)
-type SearchFilter struct{}
+type SearchFilter struct {
+ Unused struct{} `opt:"-"`
+}
func init() {
register(SearchFilter{})
diff --git a/commands/account/select.go b/commands/account/select.go
index aea17e0e..efd8f53b 100644
--- a/commands/account/select.go
+++ b/commands/account/select.go
@@ -2,12 +2,13 @@ package account
import (
"errors"
- "strconv"
"git.sr.ht/~rjarry/aerc/app"
)
-type SelectMessage struct{}
+type SelectMessage struct {
+ Index int `opt:"n"`
+}
func init() {
register(SelectMessage{})
@@ -21,20 +22,7 @@ func (SelectMessage) Complete(args []string) []string {
return nil
}
-func (SelectMessage) Execute(args []string) error {
- if len(args) != 2 {
- return errors.New("Usage: :select-message <n>")
- }
- var (
- n int = 1
- err error
- )
- if len(args) > 1 {
- n, err = strconv.Atoi(args[1])
- if err != nil {
- return errors.New("Usage: :select-message <n>")
- }
- }
+func (s SelectMessage) Execute(args []string) error {
acct := app.SelectedAccount()
if acct == nil {
return errors.New("No account selected")
@@ -42,6 +30,6 @@ func (SelectMessage) Execute(args []string) error {
if acct.Messages().Empty() {
return nil
}
- acct.Messages().Select(n)
+ acct.Messages().Select(s.Index)
return nil
}
diff --git a/commands/account/sort.go b/commands/account/sort.go
index d9eaf16a..2adfbf19 100644
--- a/commands/account/sort.go
+++ b/commands/account/sort.go
@@ -11,7 +11,9 @@ import (
"git.sr.ht/~rjarry/aerc/worker/types"
)
-type Sort struct{}
+type Sort struct {
+ Unused struct{} `opt:"-"`
+}
func init() {
register(Sort{})
diff --git a/commands/account/split.go b/commands/account/split.go
index 4a17b8b2..4774297f 100644
--- a/commands/account/split.go
+++ b/commands/account/split.go
@@ -8,12 +8,27 @@ import (
"git.sr.ht/~rjarry/aerc/app"
)
-type Split struct{}
+type Split struct {
+ Size int `opt:"n" required:"false" action:"ParseSize"`
+ Delta bool
+}
func init() {
register(Split{})
}
+func (s *Split) ParseSize(arg string) error {
+ i, err := strconv.ParseInt(arg, 10, 64)
+ if err != nil {
+ return err
+ }
+ s.Size = int(i)
+ if strings.HasPrefix(arg, "+") || strings.HasPrefix(arg, "-") {
+ s.Delta = true
+ }
+ return nil
+}
+
func (Split) Aliases() []string {
return []string{"split", "vsplit", "hsplit"}
}
@@ -22,10 +37,7 @@ func (Split) Complete(args []string) []string {
return nil
}
-func (Split) Execute(args []string) error {
- if len(args) > 2 {
- return errors.New("Usage: [v|h]split n")
- }
+func (s Split) Execute(args []string) error {
acct := app.SelectedAccount()
if acct == nil {
return errors.New("No account selected")
@@ -34,45 +46,32 @@ func (Split) Execute(args []string) error {
if store == nil {
return errors.New("Cannot perform action. Messages still loading")
}
- n := 0
- if acct.SplitSize() == 0 {
- if args[0] == "split" {
- n = app.SelectedAccount().Messages().Height() / 4
+
+ if s.Size == 0 && acct.SplitSize() == 0 {
+ if args[0] == "split" || args[0] == "hsplit" {
+ s.Size = app.SelectedAccount().Messages().Height() / 4
} else {
- n = app.SelectedAccount().Messages().Width() / 2
+ s.Size = app.SelectedAccount().Messages().Width() / 2
}
}
-
- var err error
- if len(args) > 1 {
- delta := false
- if strings.HasPrefix(args[1], "+") || strings.HasPrefix(args[1], "-") {
- delta = true
- }
- n, err = strconv.Atoi(args[1])
- if err != nil {
- return errors.New("Usage: [v|h]split n")
- }
- if delta {
- n = acct.SplitSize() + n
- acct.SetSplitSize(n)
- return nil
- }
+ if s.Delta {
+ acct.SetSplitSize(acct.SplitSize() + s.Size)
+ return nil
}
- if n == acct.SplitSize() {
+ if s.Size == acct.SplitSize() {
// Repeated commands of the same size have the effect of
// toggling the split
- n = 0
+ s.Size = 0
}
- if n < 0 {
+ if s.Size < 0 {
// Don't allow split to go negative
- n = 1
+ s.Size = 1
}
switch args[0] {
case "split", "hsplit":
- return acct.Split(n)
+ return acct.Split(s.Size)
case "vsplit":
- return acct.Vsplit(n)
+ return acct.Vsplit(s.Size)
}
return nil
}
diff --git a/commands/account/view.go b/commands/account/view.go
index 8d5bc6ed..701a7738 100644
--- a/commands/account/view.go
+++ b/commands/account/view.go
@@ -5,10 +5,11 @@ import (
"git.sr.ht/~rjarry/aerc/app"
"git.sr.ht/~rjarry/aerc/lib"
- "git.sr.ht/~sircmpwn/getopt"
)
-type ViewMessage struct{}
+type ViewMessage struct {
+ Peek bool `opt:"-p"`
+}
func init() {
register(ViewMessage{})
@@ -22,22 +23,7 @@ func (ViewMessage) Complete(args []string) []string {
return nil
}
-func (ViewMessage) Execute(args []string) error {
- peek := false
- opts, optind, err := getopt.Getopts(args, "p")
- if err != nil {
- return err
- }
-
- for _, opt := range opts {
- if opt.Option == 'p' {
- peek = true
- }
- }
-
- if len(args) != optind {
- return errors.New("Usage: view-message [-p]")
- }
+func (v ViewMessage) Execute(args []string) error {
acct := app.SelectedAccount()
if acct == nil {
return errors.New("No account selected")
@@ -58,7 +44,7 @@ func (ViewMessage) Execute(args []string) error {
app.PushError(msg.Error.Error())
return nil
}
- lib.NewMessageStoreView(msg, !peek && acct.UiConfig().AutoMarkRead,
+ lib.NewMessageStoreView(msg, !v.Peek && acct.UiConfig().AutoMarkRead,
store, app.CryptoProvider(), app.DecryptKeys,
func(view lib.MessageView, err error) {
if err != nil {
diff --git a/commands/cd.go b/commands/cd.go
index ded325d5..6620e732 100644
--- a/commands/cd.go
+++ b/commands/cd.go
@@ -11,7 +11,9 @@ import (
var previousDir string
-type ChangeDirectory struct{}
+type ChangeDirectory struct {
+ Target string `opt:"directory" default:"~"`
+}
func init() {
register(ChangeDirectory{})
@@ -36,25 +38,19 @@ func (ChangeDirectory) Complete(args []string) []string {
return dirs
}
-func (ChangeDirectory) Execute(args []string) error {
- if len(args) < 1 {
- return errors.New("Usage: cd [directory]")
- }
+func (cd ChangeDirectory) Execute(args []string) error {
cwd, err := os.Getwd()
if err != nil {
return err
}
- target := strings.Join(args[1:], " ")
- if target == "" {
- target = "~"
- } else if target == "-" {
+ if cd.Target == "-" {
if previousDir == "" {
return errors.New("No previous folder to return to")
} else {
- target = previousDir
+ cd.Target = previousDir
}
}
- target = xdg.ExpandHome(target)
+ target := xdg.ExpandHome(cd.Target)
if err := os.Chdir(target); err == nil {
previousDir = cwd
app.UpdateStatus()
diff --git a/commands/choose.go b/commands/choose.go
index 6810ed1f..4e2007fd 100644
--- a/commands/choose.go
+++ b/commands/choose.go
@@ -6,7 +6,9 @@ import (
"git.sr.ht/~rjarry/aerc/app"
)
-type Choose struct{}
+type Choose struct {
+ Unused struct{} `opt:"-"`
+}
func init() {
register(Choose{})
diff --git a/commands/commands.go b/commands/commands.go
index 57f2e6dc..c2137a58 100644
--- a/commands/commands.go
+++ b/commands/commands.go
@@ -3,6 +3,7 @@ package commands
import (
"bytes"
"errors"
+ "reflect"
"sort"
"strings"
"unicode"
@@ -117,7 +118,12 @@ func ExecuteCommand(cmd Command, cmdline string) error {
return errors.New("No arguments")
}
log.Tracef("executing command %s", args.String())
- return cmd.Execute(args.Args())
+ // copy zeroed struct
+ tmp := reflect.New(reflect.TypeOf(cmd)).Interface().(Command)
+ if err := opt.ArgsToStruct(args.Clone(), tmp); err != nil {
+ return err
+ }
+ return tmp.Execute(args.Args())
}
// expand template expressions
diff --git a/commands/compose/abort.go b/commands/compose/abort.go
index a11c06c5..4bacb9c3 100644
--- a/commands/compose/abort.go
+++ b/commands/compose/abort.go
@@ -1,8 +1,6 @@
package compose
import (
- "errors"
-
"git.sr.ht/~rjarry/aerc/app"
)
@@ -21,12 +19,7 @@ func (Abort) Complete(args []string) []string {
}
func (Abort) Execute(args []string) error {
- if len(args) != 1 {
- return errors.New("Usage: abort")
- }
composer, _ := app.SelectedTabContent().(*app.Composer)
-
app.RemoveTab(composer, true)
-
return nil
}
diff --git a/commands/compose/attach-key.go b/commands/compose/attach-key.go
index 74e05bb7..e81a6a5a 100644
--- a/commands/compose/attach-key.go
+++ b/commands/compose/attach-key.go
@@ -1,8 +1,6 @@
package compose
import (
- "errors"
-
"git.sr.ht/~rjarry/aerc/app"
)
@@ -21,11 +19,6 @@ func (AttachKey) Complete(args []string) []string {
}
func (AttachKey) Execute(args []string) error {
- if len(args) != 1 {
- return errors.New("Usage: attach-key")
- }
-
composer, _ := app.SelectedTabContent().(*app.Composer)
-
return composer.SetAttachKey(!composer.AttachKey())
}
diff --git a/commands/compose/attach.go b/commands/compose/attach.go
index fb533941..3acf28c2 100644
--- a/commands/compose/attach.go
+++ b/commands/compose/attach.go
@@ -18,11 +18,13 @@ import (
"git.sr.ht/~rjarry/aerc/lib/xdg"
"git.sr.ht/~rjarry/aerc/log"
"github.com/pkg/errors"
-
- "git.sr.ht/~sircmpwn/getopt"
)
-type Attach struct{}
+type Attach struct {
+ Menu bool `opt:"-m"`
+ Name string `opt:"-r"`
+ Path string `opt:"..." metavar:"<path>" required:"false"`
+}
func init() {
register(Attach{})
@@ -38,48 +40,20 @@ func (Attach) Complete(args []string) []string {
}
func (a Attach) Execute(args []string) error {
- var (
- menu bool
- read bool
- )
-
- opts, optind, err := getopt.Getopts(args, "mr")
- if err != nil {
- return err
- }
-
- for _, opt := range opts {
- switch opt.Option {
- case 'm':
- if read {
- return errors.New("-m and -r are mutually exclusive")
- }
- menu = true
- case 'r':
- if menu {
- return errors.New("-m and -r are mutually exclusive")
- }
- read = true
+ if a.Menu && a.Name != "" {
+ return errors.New("-m and -r are mutually exclusive")
+ }
+ switch {
+ case a.Menu:
+ return a.openMenu()
+ case a.Name != "":
+ if a.Path == "" {
+ return errors.New("command is required")
}
+ return a.readCommand()
+ default:
+ return a.addPath(a.Path)
}
-
- args = args[optind:]
-
- if menu {
- return a.openMenu(args)
- }
-
- if read {
- if len(args) < 2 {
- return fmt.Errorf("Usage: :attach -r <name> <cmd> [args...]")
- }
- return a.readCommand(args[0], args[1:])
- }
-
- if len(args) == 0 {
- return fmt.Errorf("Usage: :attach <path>")
- }
- return a.addPath(strings.Join(args, " "))
}
func (a Attach) addPath(path string) error {
@@ -129,18 +103,14 @@ func (a Attach) addPath(path string) error {
return nil
}
-func (a Attach) openMenu(args []string) error {
+func (a Attach) openMenu() error {
filePickerCmd := config.Compose.FilePickerCmd
if filePickerCmd == "" {
return fmt.Errorf("no file-picker-cmd defined")
}
if strings.Contains(filePickerCmd, "%s") {
- verb := ""
- if len(args) > 0 {
- verb = args[0]
- }
- filePickerCmd = strings.ReplaceAll(filePickerCmd, "%s", verb)
+ filePickerCmd = strings.ReplaceAll(filePickerCmd, "%s", a.Path)
}
picks, err := os.CreateTemp("", "aerc-filepicker-*")
@@ -215,9 +185,8 @@ func (a Attach) openMenu(args []string) error {
return nil
}
-func (a Attach) readCommand(name string, args []string) error {
- args = append([]string{"-c"}, args...)
- cmd := exec.Command("sh", args...)
+func (a Attach) readCommand() error {
+ cmd := exec.Command("sh", "-c", a.Path)
data, err := cmd.Output()
if err != nil {
@@ -226,20 +195,20 @@ func (a Attach) readCommand(name string, args []string) error {
reader := bufio.NewReader(bytes.NewReader(data))
- mimeType, mimeParams, err := lib.FindMimeType(name, reader)
+ mimeType, mimeParams, err := lib.FindMimeType(a.Name, reader)
if err != nil {
return errors.Wrap(err, "FindMimeType")
}
- mimeParams["name"] = name
+ mimeParams["name"] = a.Name
composer, _ := app.SelectedTabContent().(*app.Composer)
- err = composer.AddPartAttachment(name, mimeType, mimeParams, reader)
+ err = composer.AddPartAttachment(a.Name, mimeType, mimeParams, reader)
if err != nil {
return errors.Wrap(err, "AddPartAttachment")
}
- app.PushSuccess(fmt.Sprintf("Attached %s", name))
+ app.PushSuccess(fmt.Sprintf("Attached %s", a.Name))
return nil
}
diff --git a/commands/compose/cc-bcc.go b/commands/compose/cc-bcc.go
index e1f94c1e..aeb6af97 100644
--- a/commands/compose/cc-bcc.go
+++ b/commands/compose/cc-bcc.go
@@ -1,12 +1,12 @@
package compose
import (
- "strings"
-
"git.sr.ht/~rjarry/aerc/app"
)
-type CC struct{}
+type CC struct {
+ Recipients string `opt:"recipients"`
+}
func init() {
register(CC{})
@@ -20,18 +20,14 @@ func (CC) Complete(args []string) []string {
return nil
}
-func (CC) Execute(args []string) error {
- var addrs string
- if len(args) > 1 {
- addrs = strings.Join(args[1:], " ")
- }
+func (c CC) Execute(args []string) error {
composer, _ := app.SelectedTabContent().(*app.Composer)
switch args[0] {
case "cc":
- return composer.AddEditor("Cc", addrs, true)
+ return composer.AddEditor("Cc", c.Recipients, true)
case "bcc":
- return composer.AddEditor("Bcc", addrs, true)
+ return composer.AddEditor("Bcc", c.Recipients, true)
}
return nil
diff --git a/commands/compose/detach.go b/commands/compose/detach.go
index 4847713f..a2996516 100644
--- a/commands/compose/detach.go
+++ b/commands/compose/detach.go
@@ -2,12 +2,13 @@ package compose
import (
"fmt"
- "strings"
"git.sr.ht/~rjarry/aerc/app"
)
-type Detach struct{}
+type Detach struct {
+ Path string `opt:"path" required:"false"`
+}
func init() {
register(Detach{})
@@ -22,27 +23,24 @@ func (Detach) Complete(args []string) []string {
return composer.GetAttachments()
}
-func (Detach) Execute(args []string) error {
- var path string
+func (d Detach) Execute(args []string) error {
composer, _ := app.SelectedTabContent().(*app.Composer)
- if len(args) > 1 {
- path = strings.Join(args[1:], " ")
- } else {
+ if d.Path == "" {
// if no attachment is specified, delete the first in the list
atts := composer.GetAttachments()
if len(atts) > 0 {
- path = atts[0]
+ d.Path = atts[0]
} else {
return fmt.Errorf("No attachments to delete")
}
}
- if err := composer.DeleteAttachment(path); err != nil {
+ if err := composer.DeleteAttachment(d.Path); err != nil {
return err
}
- app.PushSuccess(fmt.Sprintf("Detached %s", path))
+ app.PushSuccess(fmt.Sprintf("Detached %s", d.Path))
return nil
}
diff --git a/commands/compose/edit.go b/commands/compose/edit.go
index 2948e964..80f4e6f4 100644
--- a/commands/compose/edit.go
+++ b/commands/compose/edit.go
@@ -5,10 +5,12 @@ import (
"git.sr.ht/~rjarry/aerc/app"
"git.sr.ht/~rjarry/aerc/config"
- "git.sr.ht/~sircmpwn/getopt"
)
-type Edit struct{}
+type Edit struct {
+ Edit bool `opt:"-e"`
+ NoEdit bool `opt:"-E"`
+}
func init() {
register(Edit{})
@@ -22,30 +24,15 @@ func (Edit) Complete(args []string) []string {
return nil
}
-func (Edit) Execute(args []string) error {
+func (e Edit) Execute(args []string) error {
composer, ok := app.SelectedTabContent().(*app.Composer)
if !ok {
return errors.New("only valid while composing")
}
- editHeaders := config.Compose.EditHeaders
- opts, optind, err := getopt.Getopts(args, "eE")
- if err != nil {
- return err
- }
- if len(args) != optind {
- return errors.New("Usage: edit [-e|-E]")
- }
- for _, opt := range opts {
- switch opt.Option {
- case 'e':
- editHeaders = true
- case 'E':
- editHeaders = false
- }
- }
+ editHeaders := (config.Compose.EditHeaders || e.Edit) && !e.NoEdit
- err = composer.ShowTerminal(editHeaders)
+ err := composer.ShowTerminal(editHeaders)
if err != nil {
return err
}
diff --git a/commands/compose/encrypt.go b/commands/compose/encrypt.go
index b9094c5e..3121dff0 100644
--- a/commands/compose/encrypt.go
+++ b/commands/compose/encrypt.go
@@ -1,8 +1,6 @@
package compose
import (
- "errors"
-
"git.sr.ht/~rjarry/aerc/app"
)
@@ -21,12 +19,7 @@ func (Encrypt) Complete(args []string) []string {
}
func (Encrypt) Execute(args []string) error {
- if len(args) != 1 {
- return errors.New("Usage: encrypt")
- }
-
composer, _ := app.SelectedTabContent().(*app.Composer)
-
composer.SetEncrypt(!composer.Encrypt())
return nil
}
diff --git a/commands/compose/header.go b/commands/compose/header.go
index 5c13fde5..aaa14e43 100644
--- a/commands/compose/header.go
+++ b/commands/compose/header.go
@@ -6,10 +6,14 @@ import (
"git.sr.ht/~rjarry/aerc/app"
"git.sr.ht/~rjarry/aerc/commands"
- "git.sr.ht/~sircmpwn/getopt"
)
-type Header struct{}
+type Header struct {
+ Force bool `opt:"-f"`
+ Remove bool `opt:"-d"`
+ Name string `opt:"name"`
+ Value string `opt:"..." required:"false"`
+}
var headers = []string{
"From",
@@ -38,47 +42,25 @@ func (Header) Complete(args []string) []string {
}
func (h Header) Execute(args []string) error {
- opts, optind, err := getopt.Getopts(args, h.Options())
- args = args[optind:]
- if err == nil && len(args) < 1 {
- err = fmt.Errorf("not enough arguments")
- }
- if err != nil {
- return fmt.Errorf("%w. usage: header [-fd] <name> [<value>]", err)
- }
-
- var force bool = false
- var remove bool = false
- for _, opt := range opts {
- switch opt.Option {
- case 'f':
- force = true
- case 'd':
- remove = true
- }
- }
-
composer, _ := app.SelectedTabContent().(*app.Composer)
- name := strings.TrimRight(args[0], ":")
+ name := strings.TrimRight(h.Name, ":")
- if remove {
+ if h.Remove {
return composer.DelEditor(name)
}
- value := strings.Join(args[1:], " ")
-
- if !force {
+ if !h.Force {
headers, err := composer.PrepareHeader()
if err != nil {
return err
}
- if headers.Get(name) != "" && value != "" {
+ if headers.Get(name) != "" && h.Value != "" {
return fmt.Errorf(
"Header %s is already set to %q (use -f to overwrite)",
name, headers.Get(name))
}
}
- return composer.AddEditor(name, value, false)
+ return composer.AddEditor(name, h.Value, false)
}
diff --git a/commands/compose/multipart.go b/commands/compose/multipart.go
index 0ad1dc4d..96941062 100644
--- a/commands/compose/multipart.go
+++ b/commands/compose/multipart.go
@@ -7,10 +7,12 @@ import (
"git.sr.ht/~rjarry/aerc/app"
"git.sr.ht/~rjarry/aerc/commands"
"git.sr.ht/~rjarry/aerc/config"
- "git.sr.ht/~sircmpwn/getopt"
)
-type Multipart struct{}
+type Multipart struct {
+ Remove bool `opt:"-d"`
+ Mime string `opt:"mime" metavar:"<mime/type>"`
+}
func init() {
register(Multipart{})
@@ -29,37 +31,21 @@ func (Multipart) Complete(args []string) []string {
return commands.CompletionFromList(completions, args)
}
-func (a Multipart) Execute(args []string) error {
+func (m Multipart) Execute(args []string) error {
composer, ok := app.SelectedTabContent().(*app.Composer)
if !ok {
return fmt.Errorf(":multipart is only available on the compose::review screen")
}
- opts, optind, err := getopt.Getopts(args, "d")
- if err != nil {
- return fmt.Errorf("Usage: :multipart [-d] <mime/type>")
- }
- var remove bool = false
- for _, opt := range opts {
- if opt.Option == 'd' {
- remove = true
- }
- }
- args = args[optind:]
- if len(args) != 1 {
- return fmt.Errorf("Usage: :multipart [-d] <mime/type>")
- }
- mime := args[0]
-
- if remove {
- return composer.RemovePart(mime)
+ if m.Remove {
+ return composer.RemovePart(m.Mime)
} else {
- _, found := config.Converters[mime]
+ _, found := config.Converters[m.Mime]
if !found {
- return fmt.Errorf("no command defined for MIME type: %s", mime)
+ return fmt.Errorf("no command defined for MIME type: %s", m.Mime)
}
- err = composer.AppendPart(
- mime,
+ err := composer.AppendPart(
+ m.Mime,
map[string]string{"Charset": "UTF-8"},
// the actual content of the part will be rendered
// every time the body of the email is updated
diff --git a/commands/compose/next-field.go b/commands/compose/next-field.go
index be5f8e53..88fbb03d 100644
--- a/commands/compose/next-field.go
+++ b/commands/compose/next-field.go
@@ -1,8 +1,6 @@
package compose
import (
- "fmt"
-
"git.sr.ht/~rjarry/aerc/app"
)
@@ -21,9 +19,6 @@ func (NextPrevField) Complete(args []string) []string {
}
func (NextPrevField) Execute(args []string) error {
- if len(args) > 2 {
- return nextPrevFieldUsage(args[0])
- }
composer, _ := app.SelectedTabContent().(*app.Composer)
if args[0] == "prev-field" {
composer.PrevField()
@@ -32,7 +27,3 @@ func (NextPrevField) Execute(args []string) error {
}
return nil
}
-
-func nextPrevFieldUsage(cmd string) error {
- return fmt.Errorf("Usage: %s", cmd)
-}
diff --git a/commands/compose/postpone.go b/commands/compose/postpone.go
index ac16d904..767e759c 100644
--- a/commands/compose/postpone.go
+++ b/commands/compose/postpone.go
@@ -6,8 +6,6 @@ import (
"github.com/pkg/errors"
- "git.sr.ht/~sircmpwn/getopt"
-
"git.sr.ht/~rjarry/aerc/app"
"git.sr.ht/~rjarry/aerc/commands"
"git.sr.ht/~rjarry/aerc/log"
@@ -15,7 +13,9 @@ import (
"git.sr.ht/~rjarry/aerc/worker/types"
)
-type Postpone struct{}
+type Postpone struct {
+ Folder string `opt:"-t"`
+}
func init() {
register(Postpone{})
@@ -42,11 +42,6 @@ func (Postpone) Complete(args []string) []string {
}
func (p Postpone) Execute(args []string) error {
- opts, optind, err := getopt.Getopts(args, p.Options())
- if err != nil {
- return err
- }
-
acct := app.SelectedAccount()
if acct == nil {
return errors.New("No account selected")
@@ -63,15 +58,8 @@ func (p Postpone) Execute(args []string) error {
if composer.RecalledFrom() != "" {
targetFolder = composer.RecalledFrom()
}
- for _, opt := range opts {
- if opt.Option == 't' {
- targetFolder = opt.Value
- }
- }
- args = args[optind:]
-
- if len(args) != 0 {
- return errors.New("Usage: postpone [-t <folder>]")
+ if p.Folder != "" {
+ targetFolder = p.Folder
}
if targetFolder == "" {
return errors.New("No Postpone location configured")
diff --git a/commands/compose/send.go b/commands/compose/send.go
index cd964d70..e3672471 100644
--- a/commands/compose/send.go
+++ b/commands/compose/send.go
@@ -10,7 +10,6 @@ import (
"strings"
"time"
- "git.sr.ht/~sircmpwn/getopt"
"github.com/emersion/go-sasl"
"github.com/emersion/go-smtp"
"github.com/google/shlex"
@@ -19,6 +18,7 @@ import (
"git.sr.ht/~rjarry/aerc/app"
"git.sr.ht/~rjarry/aerc/commands"
"git.sr.ht/~rjarry/aerc/commands/mode"
+ "git.sr.ht/~rjarry/aerc/commands/msg"
"git.sr.ht/~rjarry/aerc/lib"
"git.sr.ht/~rjarry/aerc/log"
"git.sr.ht/~rjarry/aerc/models"
@@ -27,7 +27,10 @@ import (
"golang.org/x/oauth2"
)
-type Send struct{}
+type Send struct {
+ Archive string `opt:"-a" action:"ParseArchive" metavar:"flat|year|month"`
+ CopyTo string `opt:"-t"`
+}
func init() {
register(Send{})
@@ -52,14 +55,17 @@ func (Send) Complete(args []string) []string {
return nil
}
-func (s Send) Execute(args []string) error {
- opts, optind, err := getopt.Getopts(args, s.Options())
- if err != nil {
- return err
- }
- if optind != len(args) {
- return errors.New("Usage: send [-a <flat|year|month>] [-t <folder>")
+func (s *Send) ParseArchive(arg string) error {
+ for _, a := range msg.ARCHIVE_TYPES {
+ if a == arg {
+ s.Archive = arg
+ return nil
+ }
}
+ return errors.New("unsupported archive type")
+}
+
+func (s Send) Execute(args []string) error {
tab := app.SelectedTab()
if tab == nil {
return errors.New("No selected tab")
@@ -69,15 +75,8 @@ func (s Send) Execute(args []string) error {
config := composer.Config()
- var copyto string = config.CopyTo
- var archive string
- for _, opt := range opts {
- if opt.Option == 'a' {
- archive = opt.Value
- }
- if opt.Option == 't' {
- copyto = opt.Value
- }
+ if s.CopyTo == "" {
+ s.CopyTo = config.CopyTo
}
outgoing, err := config.Outgoing.ConnectionString()
@@ -115,13 +114,14 @@ func (s Send) Execute(args []string) error {
domain = domain_
}
ctx := sendCtx{
- uri: uri,
- scheme: scheme,
- auth: auth,
- from: config.From,
- rcpts: rcpts,
- domain: domain,
- copyto: copyto,
+ uri: uri,
+ scheme: scheme,
+ auth: auth,
+ from: config.From,
+ rcpts: rcpts,
+ domain: domain,
+ archive: s.Archive,
+ copyto: s.CopyTo,
}
log.Debugf("send config uri: %s", ctx.uri)
@@ -148,7 +148,7 @@ func (s Send) Execute(args []string) error {
msg+" Abort send? [Y/n] ",
func(text string) {
if text == "n" || text == "N" {
- send(composer, ctx, header, tabName, archive)
+ send(composer, ctx, header, tabName)
}
}, func(cmd string) ([]string, string) {
if cmd == "" {
@@ -161,14 +161,14 @@ func (s Send) Execute(args []string) error {
app.PushPrompt(prompt)
} else {
- send(composer, ctx, header, tabName, archive)
+ send(composer, ctx, header, tabName)
}
return nil
}
func send(composer *app.Composer, ctx sendCtx,
- header *mail.Header, tabName string, archive string,
+ header *mail.Header, tabName string,
) {
// we don't want to block the UI thread while we are sending
// so we do everything in a goroutine and hide the composer from the user
@@ -243,13 +243,13 @@ func send(composer *app.Composer, ctx sendCtx,
"message sent, but copying to %v failed: %v",
ctx.copyto, err.Error())
app.PushError(errmsg)
- composer.SetSent(archive)
+ composer.SetSent(ctx.archive)
composer.Close()
return
}
}
app.PushStatus("Message sent.", 10*time.Second)
- composer.SetSent(archive)
+ composer.SetSent(ctx.archive)
composer.Close()
}()
}
@@ -267,13 +267,14 @@ func listRecipients(h *mail.Header) ([]*mail.Address, error) {
}
type sendCtx struct {
- uri *url.URL
- scheme string
- auth string
- from *mail.Address
- rcpts []*mail.Address
- domain string
- copyto string
+ uri *url.URL
+ scheme string
+ auth string
+ from *mail.Address
+ rcpts []*mail.Address
+ domain string
+ copyto string
+ archive string
}
func newSendmailSender(ctx sendCtx) (io.WriteCloser, error) {
diff --git a/commands/compose/sign.go b/commands/compose/sign.go
index ae7c3417..e30fca05 100644
--- a/commands/compose/sign.go
+++ b/commands/compose/sign.go
@@ -1,7 +1,6 @@
package compose
import (
- "errors"
"time"
"git.sr.ht/~rjarry/aerc/app"
@@ -22,10 +21,6 @@ func (Sign) Complete(args []string) []string {
}
func (Sign) Execute(args []string) error {
- if len(args) != 1 {
- return errors.New("Usage: sign")
- }
-
composer, _ := app.SelectedTabContent().(*app.Composer)
err := composer.SetSign(!composer.Sign())
diff --git a/commands/compose/switch.go b/commands/compose/switch.go
index 1cc388f1..0c027a41 100644
--- a/commands/compose/switch.go
+++ b/commands/compose/switch.go
@@ -2,17 +2,19 @@ package compose
import (
"errors"
- "fmt"
"git.sr.ht/~rjarry/aerc/app"
- "git.sr.ht/~sircmpwn/getopt"
)
type AccountSwitcher interface {
SwitchAccount(*app.AccountView) error
}
-type SwitchAccount struct{}
+type SwitchAccount struct {
+ Next bool `opt:"-n"`
+ Prev bool `opt:"-p"`
+ Account string `opt:"..." metavar:"<account>" required:"false"`
+}
func init() {
register(SwitchAccount{})
@@ -26,30 +28,9 @@ func (SwitchAccount) Complete(args []string) []string {
return app.AccountNames()
}
-func (SwitchAccount) Execute(args []string) error {
- opts, optind, err := getopt.Getopts(args, "np")
- if err != nil {
- return err
- }
- var next, prev bool
- for _, opt := range opts {
- switch opt.Option {
- case 'n':
- next = true
- prev = false
- case 'p':
- next = false
- prev = true
- }
- }
- posargs := args[optind:]
- // NOT ((prev || next) XOR (len(posargs) == 1))
- if (prev || next) == (len(posargs) == 1) {
- name := ""
- if acct := app.SelectedAccount(); acct != nil {
- name = fmt.Sprintf("Current account: %s. ", acct.Name())
- }
- return errors.New(name + "Usage: switch-account [-np] <account-name>")
+func (s SwitchAccount) Execute(args []string) error {
+ if !s.Prev && !s.Next && s.Account == "" {
+ return errors.New("Usage: switch-account -n | -p | <account-name>")
}
switcher, ok := app.SelectedTabContent().(AccountSwitcher)
@@ -58,14 +39,15 @@ func (SwitchAccount) Execute(args []string) error {
}
var acct *app.AccountView
+ var err error
switch {
- case prev:
+ case s.Prev:
acct, err = app.PrevAccount()
- case next:
+ case s.Next:
acct, err = app.NextAccount()
default:
- acct, err = app.Account(posargs[0])
+ acct, err = app.Account(s.Account)
}
if err != nil {
return err
diff --git a/commands/ct.go b/commands/ct.go
index 1b5659c7..8a6bb063 100644
--- a/commands/ct.go
+++ b/commands/ct.go
@@ -2,14 +2,15 @@ package commands
import (
"errors"
- "fmt"
"strconv"
"strings"
"git.sr.ht/~rjarry/aerc/app"
)
-type ChangeTab struct{}
+type ChangeTab struct {
+ Tab string `opt:"tab"`
+}
func init() {
register(ChangeTab{})
@@ -27,25 +28,21 @@ func (ChangeTab) Complete(args []string) []string {
return FilterList(app.TabNames(), joinedArgs, "", app.SelectedAccountUiConfig().FuzzyComplete)
}
-func (ChangeTab) Execute(args []string) error {
- if len(args) == 1 {
- return fmt.Errorf("Usage: %s <tab>", args[0])
- }
- joinedArgs := strings.Join(args[1:], " ")
- if joinedArgs == "-" {
+func (c ChangeTab) Execute(args []string) error {
+ if c.Tab == "-" {
ok := app.SelectPreviousTab()
if !ok {
return errors.New("No previous tab to return to")
}
} else {
- n, err := strconv.Atoi(joinedArgs)
+ n, err := strconv.Atoi(c.Tab)
if err == nil {
switch {
- case strings.HasPrefix(joinedArgs, "+"):
+ case strings.HasPrefix(c.Tab, "+"):
for ; n > 0; n-- {
app.NextTab()
}
- case strings.HasPrefix(joinedArgs, "-"):
+ case strings.HasPrefix(c.Tab, "-"):
for ; n < 0; n++ {
app.PrevTab()
}
@@ -57,7 +54,7 @@ func (ChangeTab) Execute(args []string) error {
}
}
} else {
- ok := app.SelectTab(joinedArgs)
+ ok := app.SelectTab(c.Tab)
if !ok {
return errors.New("No tab with that name")
}
diff --git a/commands/eml.go b/commands/eml.go
index e03792ef..adacd05b 100644
--- a/commands/eml.go
+++ b/commands/eml.go
@@ -11,7 +11,9 @@ import (
"git.sr.ht/~rjarry/aerc/lib"
)
-type Eml struct{}
+type Eml struct {
+ Path string `opt:"path" required:"false"`
+}
func init() {
register(Eml{})
@@ -25,7 +27,7 @@ func (Eml) Complete(args []string) []string {
return CompletePath(strings.Join(args, " "))
}
-func (Eml) Execute(args []string) error {
+func (e Eml) Execute(args []string) error {
acct := app.SelectedAccount()
if acct == nil {
return fmt.Errorf("no account selected")
@@ -49,7 +51,7 @@ func (Eml) Execute(args []string) error {
})
}
- if len(args) == 1 {
+ if e.Path == "" {
switch tab := app.SelectedTabContent().(type) {
case *app.MessageViewer:
part := tab.SelectedMessagePart()
@@ -68,11 +70,7 @@ func (Eml) Execute(args []string) error {
return fmt.Errorf("unsupported operation")
}
} else {
- path := strings.Join(args[1:], " ")
- if _, err := os.Stat(path); err != nil {
- return err
- }
- f, err := os.Open(path)
+ f, err := os.Open(e.Path)
if err != nil {
return err
}
diff --git a/commands/exec.go b/commands/exec.go
index a34f54e7..4c2f3d1b 100644
--- a/commands/exec.go
+++ b/commands/exec.go
@@ -1,7 +1,6 @@
package commands
import (
- "errors"
"fmt"
"os"
"os/exec"
@@ -11,7 +10,9 @@ import (
"git.sr.ht/~rjarry/aerc/log"
)
-type ExecCmd struct{}
+type ExecCmd struct {
+ Args []string `opt:"..."`
+}
func init() {
register(ExecCmd{})
@@ -25,12 +26,8 @@ func (ExecCmd) Complete(args []string) []string {
return nil
}
-func (ExecCmd) Execute(args []string) error {
- if len(args) < 2 {
- return errors.New("Usage: exec [cmd...]")
- }
-
- cmd := exec.Command(args[1], args[2:]...)
+func (e ExecCmd) Execute(args []string) error {
+ cmd := exec.Command(e.Args[0], e.Args[1:]...)
env := os.Environ()
switch view := app.SelectedTabContent().(type) {
diff --git a/commands/help.go b/commands/help.go
index 5cc0aacc..b2bcdf7c 100644
--- a/commands/help.go
+++ b/commands/help.go
@@ -1,12 +1,14 @@
package commands
import (
- "errors"
+ "fmt"
"git.sr.ht/~rjarry/aerc/app"
)
-type Help struct{}
+type Help struct {
+ Topic string `opt:"topic" action:"ParseTopic" default:"aerc"`
+}
var pages = []string{
"aerc",
@@ -37,15 +39,21 @@ func (Help) Complete(args []string) []string {
return CompletionFromList(pages, args)
}
-func (Help) Execute(args []string) error {
- page := "aerc"
- if len(args) == 2 && args[1] != "aerc" {
- page = "aerc-" + args[1]
- } else if len(args) > 2 {
- return errors.New("Usage: help [topic]")
+func (h *Help) ParseTopic(arg string) error {
+ for _, page := range pages {
+ if arg == page {
+ if arg != "aerc" {
+ arg = "aerc-" + arg
+ }
+ h.Topic = arg
+ return nil
+ }
}
+ return fmt.Errorf("unknown topic %q", arg)
+}
- if page == "aerc-keys" {
+func (h Help) Execute(args []string) error {
+ if h.Topic == "aerc-keys" {
app.AddDialog(app.NewDialog(
app.NewListBox(
"Bindings: Press <Esc> or <Enter> to close. "+
@@ -61,6 +69,6 @@ func (Help) Execute(args []string) error {
))
return nil
}
-
- return TermCore([]string{"term", "man", page})
+ term := Term{Cmd: []string{"man", h.Topic}}
+ return term.Execute(args)
}
diff --git a/commands/move-tab.go b/commands/move-tab.go
index 1aa20880..23580f2d 100644
--- a/commands/move-tab.go
+++ b/commands/move-tab.go
@@ -1,19 +1,33 @@
package commands
import (
- "fmt"
"strconv"
"strings"
"git.sr.ht/~rjarry/aerc/app"
)
-type MoveTab struct{}
+type MoveTab struct {
+ Index int `opt:"index" metavar:"[+|-]<index>" action:"ParseIndex"`
+ Relative bool
+}
func init() {
register(MoveTab{})
}
+func (m *MoveTab) ParseIndex(arg string) error {
+ i, err := strconv.ParseInt(arg, 10, 64)
+ if err != nil {
+ return err
+ }
+ m.Index = int(i)
+ if strings.HasPrefix(arg, "+") || strings.HasPrefix(arg, "-") {
+ m.Relative = true
+ }
+ return nil
+}
+
func (MoveTab) Aliases() []string {
return []string{"move-tab"}
}
@@ -22,23 +36,7 @@ func (MoveTab) Complete(args []string) []string {
return nil
}
-func (MoveTab) Execute(args []string) error {
- if len(args) == 1 {
- return fmt.Errorf("Usage: %s [+|-]<index>", args[0])
- }
-
- joinedArgs := strings.Join(args[1:], "")
-
- n, err := strconv.Atoi(joinedArgs)
- if err != nil {
- return fmt.Errorf("failed to parse index argument: %w", err)
- }
-
- var relative bool
- if strings.HasPrefix(joinedArgs, "+") || strings.HasPrefix(joinedArgs, "-") {
- relative = true
- }
- app.MoveTab(n, relative)
-
+func (m MoveTab) Execute(args []string) error {
+ app.MoveTab(m.Index, m.Relative)
return nil
}
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 {
diff --git a/commands/msgview/close.go b/commands/msgview/close.go
index e0ad6040..32702da9 100644
--- a/commands/msgview/close.go
+++ b/commands/msgview/close.go
@@ -1,8 +1,6 @@
package msgview
import (
- "errors"
-
"git.sr.ht/~rjarry/aerc/app"
)
@@ -21,9 +19,6 @@ func (Close) Complete(args []string) []string {
}
func (Close) Execute(args []string) error {
- if len(args) != 1 {
- return errors.New("Usage: close")
- }
mv, _ := app.SelectedTabContent().(*app.MessageViewer)
app.RemoveTab(mv, true)
return nil
diff --git a/commands/msgview/next-part.go b/commands/msgview/next-part.go
index 951842d7..9b1b1fcc 100644
--- a/commands/msgview/next-part.go
+++ b/commands/msgview/next-part.go
@@ -1,13 +1,12 @@
package msgview
import (
- "fmt"
- "strconv"
-
"git.sr.ht/~rjarry/aerc/app"
)
-type NextPrevPart struct{}
+type NextPrevPart struct {
+ Offset int `opt:"n" default:"1"`
+}
func init() {
register(NextPrevPart{})
@@ -21,22 +20,9 @@ func (NextPrevPart) Complete(args []string) []string {
return nil
}
-func (NextPrevPart) Execute(args []string) error {
- if len(args) > 2 {
- return nextPrevPartUsage(args[0])
- }
- var (
- n int = 1
- err error
- )
- if len(args) > 1 {
- n, err = strconv.Atoi(args[1])
- if err != nil {
- return nextPrevPartUsage(args[0])
- }
- }
+func (np NextPrevPart) Execute(args []string) error {
mv, _ := app.SelectedTabContent().(*app.MessageViewer)
- for ; n > 0; n-- {
+ for n := 0; n < np.Offset; n++ {
if args[0] == "prev-part" {
mv.PreviousPart()
} else {
@@ -45,7 +31,3 @@ func (NextPrevPart) Execute(args []string) error {
}
return nil
}
-
-func nextPrevPartUsage(cmd string) error {
- return fmt.Errorf("Usage: %s [n]", cmd)
-}
diff --git a/commands/msgview/next.go b/commands/msgview/next.go
index a69cb8ee..d8f046f8 100644
--- a/commands/msgview/next.go
+++ b/commands/msgview/next.go
@@ -3,6 +3,8 @@ package msgview
import (
"errors"
"fmt"
+ "strconv"
+ "strings"
"git.sr.ht/~rjarry/aerc/app"
"git.sr.ht/~rjarry/aerc/commands/account"
@@ -11,12 +13,28 @@ import (
"git.sr.ht/~rjarry/aerc/worker/types"
)
-type NextPrevMsg struct{}
+type NextPrevMsg struct {
+ Amount int `opt:"n" default:"1" metavar:"N[%]" action:"ParseAmount"`
+ Percent bool
+}
func init() {
register(NextPrevMsg{})
}
+func (np *NextPrevMsg) ParseAmount(arg string) error {
+ if strings.HasSuffix(arg, "%") {
+ np.Percent = true
+ arg = strings.TrimSuffix(arg, "%")
+ }
+ i, err := strconv.ParseInt(arg, 10, 64)
+ if err != nil {
+ return err
+ }
+ np.Amount = int(i)
+ return nil
+}
+
func (NextPrevMsg) Aliases() []string {
return []string{"next", "next-message", "prev", "prev-message"}
}
@@ -25,11 +43,13 @@ func (NextPrevMsg) Complete(args []string) []string {
return nil
}
-func (NextPrevMsg) Execute(args []string) error {
- n, pct, err := account.ParseNextPrevMessage(args)
+func (np NextPrevMsg) Execute(args []string) error {
+ cmd := account.NextPrevMsg{Amount: np.Amount, Percent: np.Percent}
+ err := cmd.Execute(args)
if err != nil {
return err
}
+
mv, _ := app.SelectedTabContent().(*app.MessageViewer)
acct := mv.SelectedAccount()
if acct == nil {
@@ -39,10 +59,6 @@ func (NextPrevMsg) Execute(args []string) error {
if store == nil {
return fmt.Errorf("Cannot perform action. No message store set.")
}
- err = account.ExecuteNextPrevMessage(args, acct, pct, n)
- if err != nil {
- return err
- }
executeNextPrev := func(nextMsg *models.MessageInfo) {
lib.NewMessageStoreView(nextMsg, mv.MessageView().SeenFlagSet(),
store, app.CryptoProvider(), app.DecryptKeys,
diff --git a/commands/msgview/open-link.go b/commands/msgview/open-link.go
index 7241fb40..ad2a7cc2 100644
--- a/commands/msgview/open-link.go
+++ b/commands/msgview/open-link.go
@@ -1,7 +1,6 @@
package msgview
import (
- "errors"
"fmt"
"net/url"
@@ -11,7 +10,10 @@ import (
"git.sr.ht/~rjarry/aerc/log"
)
-type OpenLink struct{}
+type OpenLink struct {
+ Url *url.URL `opt:"url" action:"ParseUrl"`
+ Cmd []string `opt:"..." required:"false"`
+}
func init() {
register(OpenLink{})
@@ -31,18 +33,20 @@ func (OpenLink) Complete(args []string) []string {
return nil
}
-func (OpenLink) Execute(args []string) error {
- if len(args) < 2 {
- return errors.New("Usage: open-link <url> [program [args...]]")
- }
- u, err := url.Parse(args[1])
+func (o *OpenLink) ParseUrl(arg string) error {
+ u, err := url.Parse(arg)
if err != nil {
return err
}
- mime := fmt.Sprintf("x-scheme-handler/%s", u.Scheme)
+ o.Url = u
+ return nil
+}
+
+func (o OpenLink) Execute(args []string) error {
+ mime := fmt.Sprintf("x-scheme-handler/%s", o.Url.Scheme)
go func() {
defer log.PanicHandler()
- if err := lib.XDGOpenMime(args[1], mime, args[2:]); err != nil {
+ if err := lib.XDGOpenMime(o.Url.String(), mime, o.Cmd); err != nil {
app.PushError("open-link: " + err.Error())
}
}()
diff --git a/commands/msgview/open.go b/commands/msgview/open.go
index 6c806c7c..bab46bd7 100644
--- a/commands/msgview/open.go
+++ b/commands/msgview/open.go
@@ -7,14 +7,15 @@ import (
"os"
"path/filepath"
- "git.sr.ht/~sircmpwn/getopt"
-
"git.sr.ht/~rjarry/aerc/app"
"git.sr.ht/~rjarry/aerc/lib"
"git.sr.ht/~rjarry/aerc/log"
)
-type Open struct{}
+type Open struct {
+ Delete bool `opt:"-d"`
+ Cmd []string `opt:"..." required:"false"`
+}
func init() {
register(Open{})
@@ -33,19 +34,6 @@ func (Open) Complete(args []string) []string {
}
func (o Open) Execute(args []string) error {
- opts, optind, err := getopt.Getopts(args, o.Options())
- if err != nil {
- return err
- }
-
- del := false
-
- for _, opt := range opts {
- if opt.Option == 'd' {
- del = true
- }
- }
-
mv := app.SelectedTabContent().(*app.MessageViewer)
if mv == nil {
return errors.New("open only supported selected message parts")
@@ -84,10 +72,10 @@ func (o Open) Execute(args []string) error {
go func() {
defer log.PanicHandler()
- if del {
+ if o.Delete {
defer os.Remove(tmpFile.Name())
}
- err = lib.XDGOpenMime(tmpFile.Name(), mimeType, args[optind:])
+ err = lib.XDGOpenMime(tmpFile.Name(), mimeType, o.Cmd)
if err != nil {
app.PushError("open: " + err.Error())
}
diff --git a/commands/msgview/save.go b/commands/msgview/save.go
index c8e00e4f..ea7599d3 100644
--- a/commands/msgview/save.go
+++ b/commands/msgview/save.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"
"git.sr.ht/~rjarry/aerc/config"
@@ -19,7 +17,13 @@ import (
"git.sr.ht/~rjarry/aerc/models"
)
-type Save struct{}
+type Save struct {
+ Force bool `opt:"-f"`
+ CreateDirs bool `opt:"-p"`
+ Attachments bool `opt:"-a"`
+ AllAttachments bool `opt:"-A"`
+ Path string `opt:"..." required:"false" metavar:"<path>"`
+}
func init() {
register(Save{})
@@ -43,77 +47,33 @@ func (s Save) Complete(args []string) []string {
return commands.CompletePath(xdg.ExpandHome(path))
}
-type saveParams struct {
- force bool
- createDirs bool
- trailingSlash bool
- attachments bool
- allAttachments bool
-}
-
func (s Save) Execute(args []string) error {
- opts, optind, err := getopt.Getopts(args, s.Options())
- if err != nil {
- return err
- }
-
- var params saveParams
-
- for _, opt := range opts {
- switch opt.Option {
- case 'f':
- params.force = true
- case 'p':
- params.createDirs = true
- case 'a':
- params.attachments = true
- case 'A':
- params.allAttachments = true
- }
- }
-
- defaultPath := config.General.DefaultSavePath
// we either need a path or a defaultPath
- if defaultPath == "" && len(args) == optind {
- return errors.New("Usage: :save [-fpa] <path>")
- }
-
- // as a convenience we join with spaces, so that the user doesn't need to
- // quote filenames containing spaces
- path := strings.Join(args[optind:], " ")
-
- // needs to be determined prior to calling filepath.Clean / filepath.Join
- // it gets stripped by Clean.
- // we auto generate a name if a directory was given
- if len(path) > 0 {
- params.trailingSlash = path[len(path)-1] == '/'
- } else if len(defaultPath) > 0 && len(path) == 0 {
- // empty path, so we might have a default that ends in a trailingSlash
- params.trailingSlash = defaultPath[len(defaultPath)-1] == '/'
+ if s.Path == "" && config.General.DefaultSavePath == "" {
+ return errors.New("No default save path in config")
}
// Absolute paths are taken as is so that the user can override the default
// if they want to
- if !isAbsPath(path) {
- path = filepath.Join(defaultPath, path)
+ if !isAbsPath(s.Path) {
+ s.Path = filepath.Join(config.General.DefaultSavePath, s.Path)
}
- path = xdg.ExpandHome(path)
+ s.Path = xdg.ExpandHome(s.Path)
mv, ok := app.SelectedTabContent().(*app.MessageViewer)
if !ok {
return fmt.Errorf("SelectedTabContent is not a MessageViewer")
}
- if params.attachments || params.allAttachments {
- parts := mv.AttachmentParts(params.allAttachments)
+ if s.Attachments || s.AllAttachments {
+ parts := mv.AttachmentParts(s.AllAttachments)
if len(parts) == 0 {
return fmt.Errorf("This message has no attachments")
}
- params.trailingSlash = true
names := make(map[string]struct{})
for _, pi := range parts {
- if err := savePart(pi, path, mv, &params, names); err != nil {
+ if err := s.savePart(pi, mv, names); err != nil {
return err
}
}
@@ -121,23 +81,22 @@ func (s Save) Execute(args []string) error {
}
pi := mv.SelectedMessagePart()
- return savePart(pi, path, mv, &params, make(map[string]struct{}))
+ return s.savePart(pi, mv, make(map[string]struct{}))
}
-func savePart(
+func (s *Save) savePart(
pi *app.PartInfo,
- path string,
mv *app.MessageViewer,
- params *saveParams,
names map[string]struct{},
) error {
- if params.trailingSlash || isDirExists(path) {
+ path := s.Path
+ if s.Attachments || s.AllAttachments || isDirExists(path) {
filename := generateFilename(pi.Part)
path = filepath.Join(path, filename)
}
dir := filepath.Dir(path)
- if params.createDirs && dir != "" {
+ if s.CreateDirs && dir != "" {
err := os.MkdirAll(dir, 0o755)
if err != nil {
return err
@@ -147,7 +106,7 @@ func savePart(
path = getCollisionlessFilename(path, names)
names[path] = struct{}{}
- if pathExists(path) && !params.force {
+ if pathExists(path) && !s.Force {
return fmt.Errorf("%q already exists and -f not given", path)
}
diff --git a/commands/msgview/toggle-headers.go b/commands/msgview/toggle-headers.go
index 746692fd..c27307d3 100644
--- a/commands/msgview/toggle-headers.go
+++ b/commands/msgview/toggle-headers.go
@@ -1,8 +1,6 @@
package msgview
import (
- "fmt"
-
"git.sr.ht/~rjarry/aerc/app"
)
@@ -21,14 +19,7 @@ func (ToggleHeaders) Complete(args []string) []string {
}
func (ToggleHeaders) Execute(args []string) error {
- if len(args) > 1 {
- return toggleHeadersUsage(args[0])
- }
mv, _ := app.SelectedTabContent().(*app.MessageViewer)
mv.ToggleHeaders()
return nil
}
-
-func toggleHeadersUsage(cmd string) error {
- return fmt.Errorf("Usage: %s", cmd)
-}
diff --git a/commands/msgview/toggle-key-passthrough.go b/commands/msgview/toggle-key-passthrough.go
index 1524b420..32735870 100644
--- a/commands/msgview/toggle-key-passthrough.go
+++ b/commands/msgview/toggle-key-passthrough.go
@@ -1,8 +1,6 @@
package msgview
import (
- "errors"
-
"git.sr.ht/~rjarry/aerc/app"
"git.sr.ht/~rjarry/aerc/lib/state"
)
@@ -22,9 +20,6 @@ func (ToggleKeyPassthrough) Complete(args []string) []string {
}
func (ToggleKeyPassthrough) Execute(args []string) error {
- if len(args) != 1 {
- return errors.New("Usage: toggle-key-passthrough")
- }
mv, _ := app.SelectedTabContent().(*app.MessageViewer)
keyPassthroughEnabled := mv.ToggleKeyPassthrough()
if acct := mv.SelectedAccount(); acct != nil {
diff --git a/commands/new-account.go b/commands/new-account.go
index 0f84141f..b30e0e34 100644
--- a/commands/new-account.go
+++ b/commands/new-account.go
@@ -1,13 +1,12 @@
package commands
import (
- "errors"
-
"git.sr.ht/~rjarry/aerc/app"
- "git.sr.ht/~sircmpwn/getopt"
)
-type NewAccount struct{}
+type NewAccount struct {
+ Temp bool `opt:"-t"`
+}
func init() {
register(NewAccount{})
@@ -21,17 +20,9 @@ func (NewAccount) Complete(args []string) []string {
return nil
}
-func (NewAccount) Execute(args []string) error {
- opts, _, err := getopt.Getopts(args, "t")
- if err != nil {
- return errors.New("Usage: new-account [-t]")
- }
+func (n NewAccount) Execute(args []string) error {
wizard := app.NewAccountWizard()
- for _, opt := range opts {
- if opt.Option == 't' {
- wizard.ConfigureTemporaryAccount(true)
- }
- }
+ wizard.ConfigureTemporaryAccount(n.Temp)
wizard.Focus(true)
app.NewTab(wizard, "New account")
return nil
diff --git a/commands/next-tab.go b/commands/next-tab.go
index eb7b1ed1..d8374191 100644
--- a/commands/next-tab.go
+++ b/commands/next-tab.go
@@ -1,13 +1,12 @@
package commands
import (
- "fmt"
- "strconv"
-
"git.sr.ht/~rjarry/aerc/app"
)
-type NextPrevTab struct{}
+type NextPrevTab struct {
+ Offset int `opt:"n" default:"1"`
+}
func init() {
register(NextPrevTab{})
@@ -21,21 +20,8 @@ func (NextPrevTab) Complete(args []string) []string {
return nil
}
-func (NextPrevTab) Execute(args []string) error {
- if len(args) > 2 {
- return nextPrevTabUsage(args[0])
- }
- var (
- n int = 1
- err error
- )
- if len(args) > 1 {
- n, err = strconv.Atoi(args[1])
- if err != nil {
- return nextPrevTabUsage(args[0])
- }
- }
- for ; n > 0; n-- {
+func (np NextPrevTab) Execute(args []string) error {
+ for n := 0; n < np.Offset; n++ {
if args[0] == "prev-tab" {
app.PrevTab()
} else {
@@ -45,7 +31,3 @@ func (NextPrevTab) Execute(args []string) error {
app.UpdateStatus()
return nil
}
-
-func nextPrevTabUsage(cmd string) error {
- return fmt.Errorf("Usage: %s [n]", cmd)
-}
diff --git a/commands/pin-tab.go b/commands/pin-tab.go
index 7a3258d4..276442ce 100644
--- a/commands/pin-tab.go
+++ b/commands/pin-tab.go
@@ -1,8 +1,6 @@
package commands
import (
- "fmt"
-
"git.sr.ht/~rjarry/aerc/app"
)
@@ -21,10 +19,6 @@ func (PinTab) Complete(args []string) []string {
}
func (PinTab) Execute(args []string) error {
- if len(args) != 1 {
- return fmt.Errorf("Usage: %s", args[0])
- }
-
switch args[0] {
case "pin-tab":
app.PinTab()
diff --git a/commands/prompt.go b/commands/prompt.go
index 0d10ffa0..d42a6597 100644
--- a/commands/prompt.go
+++ b/commands/prompt.go
@@ -1,7 +1,6 @@
package commands
import (
- "fmt"
"strings"
"git.sr.ht/~rjarry/go-opt"
@@ -9,7 +8,10 @@ import (
"git.sr.ht/~rjarry/aerc/app"
)
-type Prompt struct{}
+type Prompt struct {
+ Text string `opt:"text"`
+ Cmd []string `opt:"..."`
+}
func init() {
register(Prompt{})
@@ -71,13 +73,8 @@ func (Prompt) Complete(args []string) []string {
return rs
}
-func (Prompt) Execute(args []string) error {
- if len(args) < 3 {
- return fmt.Errorf("Usage: %s <prompt> <cmd>", args[0])
- }
-
- prompt := args[1]
- cmd := opt.QuoteArgs(args[2:]...)
- app.RegisterPrompt(prompt, cmd.String())
+func (p Prompt) Execute(args []string) error {
+ cmd := opt.QuoteArgs(p.Cmd...)
+ app.RegisterPrompt(p.Text, cmd.String())
return nil
}
diff --git a/commands/pwd.go b/commands/pwd.go
index 9b562313..426be78b 100644
--- a/commands/pwd.go
+++ b/commands/pwd.go
@@ -1,7 +1,6 @@
package commands
import (
- "errors"
"os"
"time"
@@ -23,9 +22,6 @@ func (PrintWorkDir) Complete(args []string) []string {
}
func (PrintWorkDir) Execute(args []string) error {
- if len(args) != 1 {
- return errors.New("Usage: pwd")
- }
pwd, err := os.Getwd()
if err != nil {
return err
diff --git a/commands/quit.go b/commands/quit.go
index 42e8fc95..3e0f2968 100644
--- a/commands/quit.go
+++ b/commands/quit.go
@@ -1,14 +1,14 @@
package commands
import (
- "errors"
"fmt"
"git.sr.ht/~rjarry/aerc/commands/mode"
- "git.sr.ht/~sircmpwn/getopt"
)
-type Quit struct{}
+type Quit struct {
+ Force bool `opt:"-f"`
+}
func init() {
register(Quit{})
@@ -28,21 +28,8 @@ func (err ErrorExit) Error() string {
return "exit"
}
-func (Quit) Execute(args []string) error {
- force := false
- opts, optind, err := getopt.Getopts(args, "f")
- if err != nil {
- return err
- }
- for _, opt := range opts {
- if opt.Option == 'f' {
- force = true
- }
- }
- if len(args) != optind {
- return errors.New("Usage: quit [-f]")
- }
- if force || mode.QuitAllowed() {
+func (q Quit) Execute(args []string) error {
+ if q.Force || mode.QuitAllowed() {
return ErrorExit(1)
}
return fmt.Errorf("A task is not done yet. Use -f to force an exit.")
diff --git a/commands/send-keys.go b/commands/send-keys.go
index f9cae392..9541b88d 100644
--- a/commands/send-keys.go
+++ b/commands/send-keys.go
@@ -1,15 +1,15 @@
package commands
import (
- "strings"
-
"git.sr.ht/~rjarry/aerc/app"
"git.sr.ht/~rjarry/aerc/config"
"github.com/gdamore/tcell/v2"
"github.com/pkg/errors"
)
-type SendKeys struct{}
+type SendKeys struct {
+ Keys string `opt:"..."`
+}
func init() {
register(SendKeys{})
@@ -23,7 +23,7 @@ func (SendKeys) Complete(args []string) []string {
return nil
}
-func (SendKeys) Execute(args []string) error {
+func (s SendKeys) Execute(args []string) error {
tab, ok := app.SelectedTabContent().(app.HasTerminal)
if !ok {
return errors.New("There is no terminal here")
@@ -34,10 +34,9 @@ func (SendKeys) Execute(args []string) error {
return errors.New("The terminal is not active")
}
- text2send := strings.Join(args[1:], "")
- keys2send, err := config.ParseKeyStrokes(text2send)
+ keys2send, err := config.ParseKeyStrokes(s.Keys)
if err != nil {
- return errors.Wrapf(err, "Unable to parse keystroke: '%s'", text2send)
+ return errors.Wrapf(err, "Unable to parse keystroke: %q", s.Keys)
}
for _, key := range keys2send {
diff --git a/commands/term.go b/commands/term.go
index 26e277db..225fee57 100644
--- a/commands/term.go
+++ b/commands/term.go
@@ -9,7 +9,9 @@ import (
"git.sr.ht/~rjarry/aerc/lib/ui"
)
-type Term struct{}
+type Term struct {
+ Cmd []string `opt:"..." required:"false"`
+}
func init() {
register(Term{})
@@ -23,23 +25,22 @@ func (Term) Complete(args []string) []string {
return nil
}
-// The help command is an alias for `term man` thus Term requires a simple func
-func TermCore(args []string) error {
- if len(args) == 1 {
+func (t Term) Execute(args []string) error {
+ if len(t.Cmd) == 0 {
shell, err := loginshell.Shell()
if err != nil {
return err
}
- args = append(args, shell)
+ t.Cmd = []string{shell}
}
- term, err := app.NewTerminal(exec.Command(args[1], args[2:]...))
+ term, err := app.NewTerminal(exec.Command(t.Cmd[0], t.Cmd[1:]...))
if err != nil {
return err
}
- tab := app.NewTab(term, args[1])
+ tab := app.NewTab(term, t.Cmd[0])
term.OnTitle = func(title string) {
if title == "" {
- title = args[1]
+ title = t.Cmd[0]
}
if tab.Name != title {
tab.Name = title
@@ -54,7 +55,3 @@ func TermCore(args []string) error {
}
return nil
}
-
-func (Term) Execute(args []string) error {
- return TermCore(args)
-}
diff --git a/commands/terminal/close.go b/commands/terminal/close.go
index 5d52bbc5..913a3387 100644
--- a/commands/terminal/close.go
+++ b/commands/terminal/close.go
@@ -1,8 +1,6 @@
package terminal
import (
- "errors"
-
"git.sr.ht/~rjarry/aerc/app"
)
@@ -21,9 +19,6 @@ func (Close) Complete(args []string) []string {
}
func (Close) Execute(args []string) error {
- if len(args) != 1 {
- return errors.New("Usage: close")
- }
term, _ := app.SelectedTabContent().(*app.Terminal)
term.Close()
return nil
diff --git a/commands/z.go b/commands/z.go
index 966e2ce9..5aee2a2c 100644
--- a/commands/z.go
+++ b/commands/z.go
@@ -7,7 +7,9 @@ import (
"strings"
)
-type Zoxide struct{}
+type Zoxide struct {
+ Target string `opt:"..." default:"~" metavar:"<folder> | <query>..."`
+}
func ZoxideAdd(arg string) error {
zargs := []string{"add", arg}
@@ -40,15 +42,9 @@ func (Zoxide) Complete(args []string) []string {
// Execute calls zoxide add and query and delegates actually changing the
// directory to ChangeDirectory
-func (Zoxide) Execute(args []string) error {
- if len(args) < 1 {
- return errors.New("Usage: z [directory or zoxide query]")
- }
- target := strings.Join(args[1:], " ")
- switch target {
- case "":
- return ChangeDirectory{}.Execute(args)
- case "-":
+func (z Zoxide) Execute(args []string) error {
+ switch z.Target {
+ case "-", "~":
if previousDir != "" {
err := ZoxideAdd(previousDir)
if err != nil {
@@ -57,7 +53,7 @@ func (Zoxide) Execute(args []string) error {
}
return ChangeDirectory{}.Execute(args)
default:
- _, err := os.Stat(target)
+ _, err := os.Stat(z.Target)
if err != nil {
// not a file, assume zoxide query
res, err := ZoxideQuery(args)
@@ -68,11 +64,12 @@ func (Zoxide) Execute(args []string) error {
if err != nil {
return err
}
- return ChangeDirectory{}.Execute([]string{"z", res})
+ cd := ChangeDirectory{Target: res}
+ return cd.Execute([]string{"z", res})
}
} else {
- err := ZoxideAdd(target)
+ err := ZoxideAdd(z.Target)
if err != nil {
return err
}