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-03 22:34:39 +0200
commit4549ac84a15f6ebada51ad7a29078713c6e149ba (patch)
treeb41e7ab1707fe007c7f3e82c8a492911e9eb8849
parent96ab33a5841fcb69fdd7f30661eb17b03a4d5b18 (diff)
downloadaerc-4549ac84a15f6ebada51ad7a29078713c6e149ba.tar.gz
aerc-4549ac84a15f6ebada51ad7a29078713c6e149ba.zip
commands: parse arguments with opt.ArgsToStruct
Signed-off-by: Robin Jarry <robin@jarry.cc>
-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.go61
-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.go5
-rw-r--r--commands/compose/attach-key.go6
-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.go6
-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.go31
-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.go66
-rw-r--r--commands/msg/modify-labels.go14
-rw-r--r--commands/msg/move.go29
-rw-r--r--commands/msg/pipe.go78
-rw-r--r--commands/msg/read.go131
-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.go20
-rw-r--r--commands/pwd.go4
-rw-r--r--commands/quit.go23
-rw-r--r--commands/term.go21
-rw-r--r--commands/terminal/close.go5
-rw-r--r--commands/z.go23
74 files changed, 640 insertions, 1314 deletions
diff --git a/commands/account/cf.go b/commands/account/cf.go
index f0fa3b67..09d2e523 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/commands"
"git.sr.ht/~rjarry/aerc/lib/state"
@@ -11,7 +10,9 @@ import (
var history map[string]string
-type ChangeFolder struct{}
+type ChangeFolder struct {
+ Folder string `opt:"..."`
+}
func init() {
history = make(map[string]string)
@@ -26,24 +27,21 @@ func (ChangeFolder) Complete(aerc *widgets.Aerc, args []string) []string {
return commands.GetFolders(aerc, args)
}
-func (ChangeFolder) Execute(aerc *widgets.Aerc, args []string) error {
- if len(args) == 1 {
- return errors.New("Usage: cf <folder>")
- }
+func (c ChangeFolder) Execute(aerc *widgets.Aerc, args []string) error {
acct := aerc.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 a383b621..0990cd39 100644
--- a/commands/account/clear.go
+++ b/commands/account/clear.go
@@ -5,10 +5,11 @@ import (
"git.sr.ht/~rjarry/aerc/lib/state"
"git.sr.ht/~rjarry/aerc/widgets"
- "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(aerc *widgets.Aerc, args []string) []string {
return nil
}
-func (Clear) Execute(aerc *widgets.Aerc, args []string) error {
+func (c Clear) Execute(aerc *widgets.Aerc, args []string) error {
acct := aerc.SelectedAccount()
if acct == nil {
return errors.New("No account selected")
@@ -32,23 +33,7 @@ func (Clear) Execute(aerc *widgets.Aerc, 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 5da0f163..491f929f 100644
--- a/commands/account/compose.go
+++ b/commands/account/compose.go
@@ -12,15 +12,31 @@ import (
"git.sr.ht/~rjarry/aerc/config"
"git.sr.ht/~rjarry/aerc/widgets"
- "git.sr.ht/~sircmpwn/getopt"
)
-type Compose struct{}
+type Compose struct {
+ Headers string `opt:"-H" parse:"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(aerc *widgets.Aerc, args []string) []string {
return nil
}
-func (Compose) Execute(aerc *widgets.Aerc, args []string) error {
- body, template, editHeaders, err := buildBody(args)
- if err != nil {
- return err
+func (c Compose) Execute(aerc *widgets.Aerc, 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 := aerc.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(aerc *widgets.Aerc, args []string) error {
composer, err := widgets.NewComposer(aerc, acct,
acct.AccountConfig(), acct.Worker(), editHeaders,
- template, &headers, nil, msg.Body)
+ c.Template, &headers, nil, msg.Body)
if err != nil {
return err
}
composer.Tab = aerc.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 0a67b2fe..4e4cc0e8 100644
--- a/commands/account/connection.go
+++ b/commands/account/connection.go
@@ -22,7 +22,7 @@ func (Connection) Complete(aerc *widgets.Aerc, args []string) []string {
return nil
}
-func (Connection) Execute(aerc *widgets.Aerc, args []string) error {
+func (c Connection) Execute(aerc *widgets.Aerc, args []string) error {
acct := aerc.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 3eafaa09..58ac7f05 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/widgets"
)
@@ -22,9 +21,6 @@ func (ExpandCollapseFolder) Complete(aerc *widgets.Aerc, args []string) []string
}
func (ExpandCollapseFolder) Execute(aerc *widgets.Aerc, args []string) error {
- if len(args) > 1 {
- return expandCollapseFolderUsage(args[0])
- }
acct := aerc.SelectedAccount()
if acct == nil {
return errors.New("No account selected")
@@ -36,7 +32,3 @@ func (ExpandCollapseFolder) Execute(aerc *widgets.Aerc, 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 8981261b..53df98f3 100644
--- a/commands/account/export-mbox.go
+++ b/commands/account/export-mbox.go
@@ -15,7 +15,9 @@ import (
"git.sr.ht/~rjarry/aerc/worker/types"
)
-type ExportMbox struct{}
+type ExportMbox struct {
+ Filename string `opt:"FILENAME"`
+}
func init() {
register(ExportMbox{})
@@ -29,12 +31,7 @@ func (ExportMbox) Complete(aerc *widgets.Aerc, args []string) []string {
return commands.CompletePath(filepath.Join(args...))
}
-func (ExportMbox) Execute(aerc *widgets.Aerc, args []string) error {
- if len(args) != 2 {
- return exportFolderUsage(args[0])
- }
- filename := args[1]
-
+func (e ExportMbox) Execute(aerc *widgets.Aerc, args []string) error {
acct := aerc.SelectedAccount()
if acct == nil {
return errors.New("No account selected")
@@ -44,16 +41,16 @@ func (ExportMbox) Execute(aerc *widgets.Aerc, 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")
}
}
}
- aerc.PushStatus("Exporting to "+filename, 10*time.Second)
+ aerc.PushStatus("Exporting to "+e.Filename, 10*time.Second)
// uids of messages to export
var uids []uint32
@@ -72,7 +69,7 @@ func (ExportMbox) Execute(aerc *widgets.Aerc, 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)
aerc.PushError(err.Error())
@@ -139,14 +136,10 @@ func (ExportMbox) Execute(aerc *widgets.Aerc, 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)
aerc.PushStatus(statusInfo, 10*time.Second)
log.Debugf(statusInfo)
}()
return nil
}
-
-func exportFolderUsage(cmd string) error {
- return fmt.Errorf("Usage: %s <filename>", cmd)
-}
diff --git a/commands/account/import-mbox.go b/commands/account/import-mbox.go
index 85e9a341..96f13888 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(aerc *widgets.Aerc, args []string) []string {
return commands.CompletePath(filepath.Join(args...))
}
-func (ImportMbox) Execute(aerc *widgets.Aerc, args []string) error {
- if len(args) != 2 {
- return importFolderUsage(args[0])
- }
- filename := args[1]
-
+func (i ImportMbox) Execute(aerc *widgets.Aerc, args []string) error {
acct := aerc.SelectedAccount()
if acct == nil {
return errors.New("No account selected")
@@ -54,10 +51,10 @@ func (ImportMbox) Execute(aerc *widgets.Aerc, 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)
aerc.PushStatus(statusInfo, 10*time.Second)
log.Debugf(statusInfo)
- f, err := os.Open(filename)
+ f, err := os.Open(i.Filename)
if err != nil {
aerc.PushError(err.Error())
return
@@ -148,7 +145,3 @@ func (ImportMbox) Execute(aerc *widgets.Aerc, 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 02d997e4..886c9664 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 {
+ Name string `opt:"..." metavar:"FOLDER"`
+}
func init() {
register(MakeDir{})
@@ -41,22 +43,18 @@ func (MakeDir) Complete(aerc *widgets.Aerc, args []string) []string {
return inboxes
}
-func (MakeDir) Execute(aerc *widgets.Aerc, args []string) error {
- if len(args) == 0 {
- return errors.New("Usage: :mkdir <name>")
- }
+func (m MakeDir) Execute(aerc *widgets.Aerc, args []string) error {
acct := aerc.SelectedAccount()
if acct == nil {
return errors.New("No account selected")
}
- name := strings.Join(args[1:], " ")
acct.Worker().PostAction(&types.CreateDirectory{
- Directory: name,
+ Directory: m.Name,
}, func(msg types.WorkerMessage) {
switch msg := msg.(type) {
case *types.Done:
aerc.PushStatus("Directory created.", 10*time.Second)
- acct.Directories().Select(name)
+ acct.Directories().Select(m.Name)
case *types.Error:
aerc.PushError(msg.Error.Error())
}
diff --git a/commands/account/next-folder.go b/commands/account/next-folder.go
index e3541e52..ba02e1f5 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/widgets"
)
-type NextPrevFolder struct{}
+type NextPrevFolder struct {
+ Offset int `opt:"N" default:"1"`
+}
func init() {
register(NextPrevFolder{})
@@ -22,32 +22,15 @@ func (NextPrevFolder) Complete(aerc *widgets.Aerc, args []string) []string {
return nil
}
-func (NextPrevFolder) Execute(aerc *widgets.Aerc, 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(aerc *widgets.Aerc, args []string) error {
acct := aerc.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 922f95a1..39efeeef 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/lib/ui"
"git.sr.ht/~rjarry/aerc/widgets"
@@ -23,9 +22,6 @@ func (NextPrevResult) Complete(aerc *widgets.Aerc, args []string) []string {
}
func (NextPrevResult) Execute(aerc *widgets.Aerc, args []string) error {
- if len(args) > 1 {
- return nextPrevResultUsage(args[0])
- }
acct := aerc.SelectedAccount()
if acct == nil {
return errors.New("No account selected")
@@ -45,7 +41,3 @@ func (NextPrevResult) Execute(aerc *widgets.Aerc, 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 15dc5363..870487ff 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/widgets"
)
-type NextPrevMsg struct{}
+type NextPrevMsg struct {
+ Amount int `opt:"N" default:"1" metavar:"N[%]" parse:"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(aerc *widgets.Aerc, args []string) []string {
return nil
}
-func (NextPrevMsg) Execute(aerc *widgets.Aerc, args []string) error {
- n, pct, err := ParseNextPrevMessage(args)
- if err != nil {
- return err
- }
+func (np NextPrevMsg) Execute(aerc *widgets.Aerc, args []string) error {
acct := aerc.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 *widgets.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 *widgets.AccountView, pct bool,
}
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 9fdaa3e9..03eb857a 100644
--- a/commands/account/recover.go
+++ b/commands/account/recover.go
@@ -10,10 +10,14 @@ import (
"git.sr.ht/~rjarry/aerc/commands"
"git.sr.ht/~rjarry/aerc/config"
"git.sr.ht/~rjarry/aerc/widgets"
- "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(aerc *widgets.Aerc, args []string) []string {
}
func (r Recover) Execute(aerc *widgets.Aerc, args []string) error {
- // Complete() expects to be passed only the arguments, not including the command name
- if len(Recover{}.Complete(aerc, 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 := aerc.SelectedAccount()
@@ -72,22 +59,7 @@ func (r Recover) Execute(aerc *widgets.Aerc, 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 := widgets.NewComposer(aerc, acct,
acct.AccountConfig(), acct.Worker(), editHeaders,
@@ -98,8 +70,8 @@ func (r Recover) Execute(aerc *widgets.Aerc, args []string) error {
composer.Tab = aerc.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 9f6fedeb..8f26327e 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/widgets"
"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(aerc *widgets.Aerc, args []string) []string {
return nil
}
-func (RemoveDir) Execute(aerc *widgets.Aerc, args []string) error {
+func (r RemoveDir) Execute(aerc *widgets.Aerc, args []string) error {
acct := aerc.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(aerc *widgets.Aerc, 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 d7884f15..1c8066fd 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 {
+ Filter string `opt:"..." required:"false"`
+}
func init() {
register(SearchFilter{})
diff --git a/commands/account/select.go b/commands/account/select.go
index 28aedfa5..26559fc5 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/widgets"
)
-type SelectMessage struct{}
+type SelectMessage struct {
+ Index int `opt:"N"`
+}
func init() {
register(SelectMessage{})
@@ -21,20 +22,7 @@ func (SelectMessage) Complete(aerc *widgets.Aerc, args []string) []string {
return nil
}
-func (SelectMessage) Execute(aerc *widgets.Aerc, 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(aerc *widgets.Aerc, args []string) error {
acct := aerc.SelectedAccount()
if acct == nil {
return errors.New("No account selected")
@@ -42,6 +30,6 @@ func (SelectMessage) Execute(aerc *widgets.Aerc, 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 cabe10ec..413c26fc 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 {
+ Criteria string `opt:"..." required:"false"`
+}
func init() {
register(Sort{})
diff --git a/commands/account/split.go b/commands/account/split.go
index 7a5acc47..ef6d2647 100644
--- a/commands/account/split.go
+++ b/commands/account/split.go
@@ -8,12 +8,27 @@ import (
"git.sr.ht/~rjarry/aerc/widgets"
)
-type Split struct{}
+type Split struct {
+ Size int `opt:"N" required:"false" parse:"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"}
}
@@ -22,10 +37,7 @@ func (Split) Complete(aerc *widgets.Aerc, args []string) []string {
return nil
}
-func (Split) Execute(aerc *widgets.Aerc, args []string) error {
- if len(args) > 2 {
- return errors.New("Usage: [v]split n")
- }
+func (s Split) Execute(aerc *widgets.Aerc, args []string) error {
acct := aerc.SelectedAccount()
if acct == nil {
return errors.New("No account selected")
@@ -34,45 +46,32 @@ func (Split) Execute(aerc *widgets.Aerc, args []string) error {
if store == nil {
return errors.New("Cannot perform action. Messages still loading")
}
- n := 0
- if acct.SplitSize() == 0 {
+
+ if s.Size == 0 && acct.SplitSize() == 0 {
if args[0] == "split" {
- n = aerc.SelectedAccount().Messages().Height() / 4
+ s.Size = aerc.SelectedAccount().Messages().Height() / 4
} else {
- n = aerc.SelectedAccount().Messages().Width() / 2
+ s.Size = aerc.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]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":
- 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 f48d3bc3..e9e39ab8 100644
--- a/commands/account/view.go
+++ b/commands/account/view.go
@@ -5,10 +5,11 @@ import (
"git.sr.ht/~rjarry/aerc/lib"
"git.sr.ht/~rjarry/aerc/widgets"
- "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(aerc *widgets.Aerc, args []string) []string {
return nil
}
-func (ViewMessage) Execute(aerc *widgets.Aerc, 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(aerc *widgets.Aerc, args []string) error {
acct := aerc.SelectedAccount()
if acct == nil {
return errors.New("No account selected")
@@ -58,7 +44,7 @@ func (ViewMessage) Execute(aerc *widgets.Aerc, args []string) error {
aerc.PushError(msg.Error.Error())
return nil
}
- lib.NewMessageStoreView(msg, !peek && acct.UiConfig().AutoMarkRead,
+ lib.NewMessageStoreView(msg, !v.Peek && acct.UiConfig().AutoMarkRead,
store, aerc.Crypto, aerc.DecryptKeys,
func(view lib.MessageView, err error) {
if err != nil {
diff --git a/commands/cd.go b/commands/cd.go
index 8c0191c2..c1bb7fe2 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(aerc *widgets.Aerc, args []string) []string {
return dirs
}
-func (ChangeDirectory) Execute(aerc *widgets.Aerc, args []string) error {
- if len(args) < 1 {
- return errors.New("Usage: cd [directory]")
- }
+func (cd ChangeDirectory) Execute(aerc *widgets.Aerc, 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
aerc.UpdateStatus()
diff --git a/commands/choose.go b/commands/choose.go
index c18f0292..db9c44b3 100644
--- a/commands/choose.go
+++ b/commands/choose.go
@@ -6,7 +6,9 @@ import (
"git.sr.ht/~rjarry/aerc/widgets"
)
-type Choose struct{}
+type Choose struct {
+ Args string `opt:"..." required:"false"`
+}
func init() {
register(Choose{})
diff --git a/commands/commands.go b/commands/commands.go
index 4942f4aa..76605e0d 100644
--- a/commands/commands.go
+++ b/commands/commands.go
@@ -3,6 +3,7 @@ package commands
import (
"bytes"
"errors"
+ "reflect"
"sort"
"strings"
"unicode"
@@ -133,7 +134,12 @@ func (cmds *Commands) ExecuteCommand(
}
if cmd, ok := cmds.dict()[name]; ok {
log.Tracef("executing command %s", args.String())
- return cmd.Execute(aerc, args.Args())
+ // copy zeroed struct
+ tmp := reflect.New(reflect.TypeOf(cmd)).Interface().(Command)
+ if err := opt.ArgsToStruct(args, tmp); err != nil {
+ return err
+ }
+ return tmp.Execute(aerc, args.Args())
}
return NoSuchCommand(name)
}
diff --git a/commands/compose/abort.go b/commands/compose/abort.go
index d6a81b57..137c5794 100644
--- a/commands/compose/abort.go
+++ b/commands/compose/abort.go
@@ -1,8 +1,6 @@
package compose
import (
- "errors"
-
"git.sr.ht/~rjarry/aerc/widgets"
)
@@ -21,9 +19,6 @@ func (Abort) Complete(aerc *widgets.Aerc, args []string) []string {
}
func (Abort) Execute(aerc *widgets.Aerc, args []string) error {
- if len(args) != 1 {
- return errors.New("Usage: abort")
- }
composer, _ := aerc.SelectedTabContent().(*widgets.Composer)
aerc.RemoveTab(composer, true)
diff --git a/commands/compose/attach-key.go b/commands/compose/attach-key.go
index 208e9fd8..249decef 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/widgets"
)
@@ -21,10 +19,6 @@ func (AttachKey) Complete(aerc *widgets.Aerc, args []string) []string {
}
func (AttachKey) Execute(aerc *widgets.Aerc, args []string) error {
- if len(args) != 1 {
- return errors.New("Usage: attach-key")
- }
-
composer, _ := aerc.SelectedTabContent().(*widgets.Composer)
return composer.SetAttachKey(!composer.AttachKey())
diff --git a/commands/compose/attach.go b/commands/compose/attach.go
index f9ef027f..6e73fff4 100644
--- a/commands/compose/attach.go
+++ b/commands/compose/attach.go
@@ -18,11 +18,13 @@ import (
"git.sr.ht/~rjarry/aerc/log"
"git.sr.ht/~rjarry/aerc/widgets"
"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"`
+}
func init() {
register(Attach{})
@@ -38,48 +40,20 @@ func (Attach) Complete(aerc *widgets.Aerc, args []string) []string {
}
func (a Attach) Execute(aerc *widgets.Aerc, 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(aerc)
+ case a.Name != "":
+ if a.Path == "" {
+ return errors.New("command is required")
}
+ return a.readCommand(aerc)
+ default:
+ return a.addPath(aerc, a.Path)
}
-
- args = args[optind:]
-
- if menu {
- return a.openMenu(aerc, args)
- }
-
- if read {
- if len(args) < 2 {
- return fmt.Errorf("Usage: :attach -r <name> <cmd> [args...]")
- }
- return a.readCommand(aerc, args[0], args[1:])
- }
-
- if len(args) == 0 {
- return fmt.Errorf("Usage: :attach <path>")
- }
- return a.addPath(aerc, strings.Join(args, " "))
}
func (a Attach) addPath(aerc *widgets.Aerc, path string) error {
@@ -129,18 +103,14 @@ func (a Attach) addPath(aerc *widgets.Aerc, path string) error {
return nil
}
-func (a Attach) openMenu(aerc *widgets.Aerc, args []string) error {
+func (a Attach) openMenu(aerc *widgets.Aerc) 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(aerc *widgets.Aerc, args []string) error {
return nil
}
-func (a Attach) readCommand(aerc *widgets.Aerc, name string, args []string) error {
- args = append([]string{"-c"}, args...)
- cmd := exec.Command("sh", args...)
+func (a Attach) readCommand(aerc *widgets.Aerc) error {
+ cmd := exec.Command("sh", "-c", a.Path)
data, err := cmd.Output()
if err != nil {
@@ -226,20 +195,20 @@ func (a Attach) readCommand(aerc *widgets.Aerc, name string, args []string) erro
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, _ := aerc.SelectedTabContent().(*widgets.Composer)
- err = composer.AddPartAttachment(name, mimeType, mimeParams, reader)
+ err = composer.AddPartAttachment(a.Name, mimeType, mimeParams, reader)
if err != nil {
return errors.Wrap(err, "AddPartAttachment")
}
- aerc.PushSuccess(fmt.Sprintf("Attached %s", name))
+ aerc.PushSuccess(fmt.Sprintf("Attached %s", a.Name))
return nil
}
diff --git a/commands/compose/cc-bcc.go b/commands/compose/cc-bcc.go
index 045f9092..9f203436 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/widgets"
)
-type CC struct{}
+type CC struct {
+ Recipients string `opt:"RECIPIENTS"`
+}
func init() {
register(CC{})
@@ -20,18 +20,14 @@ func (CC) Complete(aerc *widgets.Aerc, args []string) []string {
return nil
}
-func (CC) Execute(aerc *widgets.Aerc, args []string) error {
- var addrs string
- if len(args) > 1 {
- addrs = strings.Join(args[1:], " ")
- }
+func (c CC) Execute(aerc *widgets.Aerc, args []string) error {
composer, _ := aerc.SelectedTabContent().(*widgets.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 487bf225..3b5a6833 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/widgets"
)
-type Detach struct{}
+type Detach struct {
+ Path string `opt:"PATH" required:"false"`
+}
func init() {
register(Detach{})
@@ -22,27 +23,24 @@ func (Detach) Complete(aerc *widgets.Aerc, args []string) []string {
return composer.GetAttachments()
}
-func (Detach) Execute(aerc *widgets.Aerc, args []string) error {
- var path string
+func (d Detach) Execute(aerc *widgets.Aerc, args []string) error {
composer, _ := aerc.SelectedTabContent().(*widgets.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
}
- aerc.PushSuccess(fmt.Sprintf("Detached %s", path))
+ aerc.PushSuccess(fmt.Sprintf("Detached %s", d.Path))
return nil
}
diff --git a/commands/compose/edit.go b/commands/compose/edit.go
index 1e8e0672..198259e2 100644
--- a/commands/compose/edit.go
+++ b/commands/compose/edit.go
@@ -5,10 +5,12 @@ import (
"git.sr.ht/~rjarry/aerc/config"
"git.sr.ht/~rjarry/aerc/widgets"
- "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(aerc *widgets.Aerc, args []string) []string {
return nil
}
-func (Edit) Execute(aerc *widgets.Aerc, args []string) error {
+func (e Edit) Execute(aerc *widgets.Aerc, args []string) error {
composer, ok := aerc.SelectedTabContent().(*widgets.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 905bdc4b..115c5f41 100644
--- a/commands/compose/encrypt.go
+++ b/commands/compose/encrypt.go
@@ -1,8 +1,6 @@
package compose
import (
- "errors"
-
"git.sr.ht/~rjarry/aerc/widgets"
)
@@ -21,10 +19,6 @@ func (Encrypt) Complete(aerc *widgets.Aerc, args []string) []string {
}
func (Encrypt) Execute(aerc *widgets.Aerc, args []string) error {
- if len(args) != 1 {
- return errors.New("Usage: encrypt")
- }
-
composer, _ := aerc.SelectedTabContent().(*widgets.Composer)
composer.SetEncrypt(!composer.Encrypt())
diff --git a/commands/compose/header.go b/commands/compose/header.go
index 59d01952..59c60930 100644
--- a/commands/compose/header.go
+++ b/commands/compose/header.go
@@ -6,10 +6,14 @@ import (
"git.sr.ht/~rjarry/aerc/commands"
"git.sr.ht/~rjarry/aerc/widgets"
- "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(aerc *widgets.Aerc, args []string) []string {
}
func (h Header) Execute(aerc *widgets.Aerc, 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, _ := aerc.SelectedTabContent().(*widgets.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 32801965..277b9a24 100644
--- a/commands/compose/multipart.go
+++ b/commands/compose/multipart.go
@@ -7,10 +7,12 @@ import (
"git.sr.ht/~rjarry/aerc/commands"
"git.sr.ht/~rjarry/aerc/config"
"git.sr.ht/~rjarry/aerc/widgets"
- "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(aerc *widgets.Aerc, args []string) []string {
return commands.CompletionFromList(aerc, completions, args)
}
-func (a Multipart) Execute(aerc *widgets.Aerc, args []string) error {
+func (m Multipart) Execute(aerc *widgets.Aerc, args []string) error {
composer, ok := aerc.SelectedTabContent().(*widgets.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 ec4db582..b6342dec 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/widgets"
)
@@ -21,9 +19,6 @@ func (NextPrevField) Complete(aerc *widgets.Aerc, args []string) []string {
}
func (NextPrevField) Execute(aerc *widgets.Aerc, args []string) error {
- if len(args) > 2 {
- return nextPrevFieldUsage(args[0])
- }
composer, _ := aerc.SelectedTabContent().(*widgets.Composer)
if args[0] == "prev-field" {
composer.PrevField()
@@ -32,7 +27,3 @@ func (NextPrevField) Execute(aerc *widgets.Aerc, 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 fd59cc11..94138d05 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/commands"
"git.sr.ht/~rjarry/aerc/log"
"git.sr.ht/~rjarry/aerc/models"
@@ -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(aerc *widgets.Aerc, args []string) []string {
}
func (p Postpone) Execute(aerc *widgets.Aerc, args []string) error {
- opts, optind, err := getopt.Getopts(args, p.Options())
- if err != nil {
- return err
- }
-
acct := aerc.SelectedAccount()
if acct == nil {
return errors.New("No account selected")
@@ -63,15 +58,8 @@ func (p Postpone) Execute(aerc *widgets.Aerc, 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 e53b01a3..4c925f2a 100644
--- a/commands/compose/send.go
+++ b/commands/compose/send.go
@@ -10,13 +10,13 @@ import (
"strings"
"time"
- "git.sr.ht/~sircmpwn/getopt"
"github.com/emersion/go-sasl"
"github.com/emersion/go-smtp"
"github.com/google/shlex"
"github.com/pkg/errors"
"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"
@@ -26,7 +26,9 @@ import (
"golang.org/x/oauth2"
)
-type Send struct{}
+type Send struct {
+ Archive string `opt:"-a" parse:"ParseArchive" metavar:"flat|year|month"`
+}
func init() {
register(Send{})
@@ -40,20 +42,17 @@ func (Send) Complete(aerc *widgets.Aerc, args []string) []string {
return nil
}
-func (Send) Execute(aerc *widgets.Aerc, args []string) error {
- opts, optind, err := getopt.Getopts(args, "a:")
- if err != nil {
- return err
- }
- if optind != len(args) {
- return errors.New("Usage: send [-a <flat|year|month>]")
- }
- var archive string
- for _, opt := range opts {
- if opt.Option == 'a' {
- archive = opt.Value
+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(aerc *widgets.Aerc, args []string) error {
tab := aerc.SelectedTab()
if tab == nil {
return errors.New("No selected tab")
@@ -129,7 +128,7 @@ func (Send) Execute(aerc *widgets.Aerc, args []string) error {
msg+" Abort send? [Y/n] ",
func(text string) {
if text == "n" || text == "N" {
- send(aerc, composer, ctx, header, tabName, archive)
+ send(aerc, composer, ctx, header, tabName, s.Archive)
}
}, func(cmd string) ([]string, string) {
if cmd == "" {
@@ -142,7 +141,7 @@ func (Send) Execute(aerc *widgets.Aerc, args []string) error {
aerc.PushPrompt(prompt)
} else {
- send(aerc, composer, ctx, header, tabName, archive)
+ send(aerc, composer, ctx, header, tabName, s.Archive)
}
return nil
diff --git a/commands/compose/sign.go b/commands/compose/sign.go
index e6afd98e..ded816a6 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/widgets"
@@ -22,10 +21,6 @@ func (Sign) Complete(aerc *widgets.Aerc, args []string) []string {
}
func (Sign) Execute(aerc *widgets.Aerc, args []string) error {
- if len(args) != 1 {
- return errors.New("Usage: sign")
- }
-
composer, _ := aerc.SelectedTabContent().(*widgets.Composer)
err := composer.SetSign(!composer.Sign())
diff --git a/commands/compose/switch.go b/commands/compose/switch.go
index 2b6aadb5..807ef8f2 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/widgets"
- "git.sr.ht/~sircmpwn/getopt"
)
type AccountSwitcher interface {
SwitchAccount(*widgets.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(aerc *widgets.Aerc, args []string) []string {
return aerc.AccountNames()
}
-func (SwitchAccount) Execute(aerc *widgets.Aerc, 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 := aerc.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(aerc *widgets.Aerc, args []string) error {
+ if !s.Prev && !s.Next && s.Account == "" {
+ return errors.New("Usage: switch-account -n | -p | <account-name>")
}
switcher, ok := aerc.SelectedTabContent().(AccountSwitcher)
@@ -58,14 +39,15 @@ func (SwitchAccount) Execute(aerc *widgets.Aerc, args []string) error {
}
var acct *widgets.AccountView
+ var err error
switch {
- case prev:
+ case s.Prev:
acct, err = aerc.PrevAccount()
- case next:
+ case s.Next:
acct, err = aerc.NextAccount()
default:
- acct, err = aerc.Account(posargs[0])
+ acct, err = aerc.Account(s.Account)
}
if err != nil {
return err
diff --git a/commands/ct.go b/commands/ct.go
index 3bd3428e..01eec808 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/widgets"
)
-type ChangeTab struct{}
+type ChangeTab struct {
+ Tab string `opt:"TAB"`
+}
func init() {
register(ChangeTab{})
@@ -27,25 +28,21 @@ func (ChangeTab) Complete(aerc *widgets.Aerc, args []string) []string {
return FilterList(aerc.TabNames(), joinedArgs, "", aerc.SelectedAccountUiConfig().FuzzyComplete)
}
-func (ChangeTab) Execute(aerc *widgets.Aerc, 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(aerc *widgets.Aerc, args []string) error {
+ if c.Tab == "-" {
ok := aerc.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-- {
aerc.NextTab()
}
- case strings.HasPrefix(joinedArgs, "-"):
+ case strings.HasPrefix(c.Tab, "-"):
for ; n < 0; n++ {
aerc.PrevTab()
}
@@ -57,7 +54,7 @@ func (ChangeTab) Execute(aerc *widgets.Aerc, args []string) error {
}
}
} else {
- ok := aerc.SelectTab(joinedArgs)
+ ok := aerc.SelectTab(c.Tab)
if !ok {
return errors.New("No tab with that name")
}
diff --git a/commands/eml.go b/commands/eml.go
index 00380763..b00e988f 100644
--- a/commands/eml.go
+++ b/commands/eml.go
@@ -11,7 +11,9 @@ import (
"git.sr.ht/~rjarry/aerc/widgets"
)
-type Eml struct{}
+type Eml struct {
+ Path string `opt:"PATH" required:"false"`
+}
func init() {
register(Eml{})
@@ -25,7 +27,7 @@ func (Eml) Complete(aerc *widgets.Aerc, args []string) []string {
return CompletePath(strings.Join(args, " "))
}
-func (Eml) Execute(aerc *widgets.Aerc, args []string) error {
+func (e Eml) Execute(aerc *widgets.Aerc, args []string) error {
acct := aerc.SelectedAccount()
if acct == nil {
return fmt.Errorf("no account selected")
@@ -49,7 +51,7 @@ func (Eml) Execute(aerc *widgets.Aerc, args []string) error {
})
}
- if len(args) == 1 {
+ if e.Path == "" {
switch tab := aerc.SelectedTabContent().(type) {
case *widgets.MessageViewer:
part := tab.SelectedMessagePart()
@@ -68,11 +70,7 @@ func (Eml) Execute(aerc *widgets.Aerc, 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 37274116..ff94c658 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/widgets"
)
-type ExecCmd struct{}
+type ExecCmd struct {
+ Args []string `opt:"..."`
+}
func init() {
register(ExecCmd{})
@@ -25,12 +26,8 @@ func (ExecCmd) Complete(aerc *widgets.Aerc, args []string) []string {
return nil
}
-func (ExecCmd) Execute(aerc *widgets.Aerc, args []string) error {
- if len(args) < 2 {
- return errors.New("Usage: exec [cmd...]")
- }
-
- cmd := exec.Command(args[1], args[2:]...)
+func (e ExecCmd) Execute(aerc *widgets.Aerc, args []string) error {
+ cmd := exec.Command(e.Args[0], e.Args[1:]...)
env := os.Environ()
switch view := aerc.SelectedTabContent().(type) {
diff --git a/commands/help.go b/commands/help.go
index b2654ab5..8856247e 100644
--- a/commands/help.go
+++ b/commands/help.go
@@ -1,12 +1,14 @@
package commands
import (
- "errors"
+ "fmt"
"git.sr.ht/~rjarry/aerc/widgets"
)
-type Help struct{}
+type Help struct {
+ Topic string `opt:"TOPIC" parse:"ParseTopic" default:"aerc"`
+}
var pages = []string{
"aerc",
@@ -37,15 +39,21 @@ func (Help) Complete(aerc *widgets.Aerc, args []string) []string {
return CompletionFromList(aerc, pages, args)
}
-func (Help) Execute(aerc *widgets.Aerc, 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(aerc *widgets.Aerc, args []string) error {
+ if h.Topic == "aerc-keys" {
aerc.AddDialog(widgets.NewDialog(
widgets.NewListBox(
"Bindings: Press <Esc> or <Enter> to close. "+
@@ -61,6 +69,6 @@ func (Help) Execute(aerc *widgets.Aerc, args []string) error {
))
return nil
}
-
- return TermCore(aerc, []string{"term", "man", page})
+ term := Term{Cmd: []string{"man", h.Topic}}
+ return term.Execute(aerc, args)
}
diff --git a/commands/move-tab.go b/commands/move-tab.go
index 76e5348d..0ebb1eb8 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/widgets"
)
-type MoveTab struct{}
+type MoveTab struct {
+ Index int `opt:"INDEX" metavar:"[+|-]INDEX" parse:"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(aerc *widgets.Aerc, args []string) []string {
return nil
}
-func (MoveTab) Execute(aerc *widgets.Aerc, 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
- }
- aerc.MoveTab(n, relative)
-
+func (m MoveTab) Execute(aerc *widgets.Aerc, args []string) error {
+ aerc.MoveTab(m.Index, m.Relative)
return nil
}
diff --git a/commands/msg/archive.go b/commands/msg/archive.go
index 1c9d7929..c9358f8f 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" parse:"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(aerc *widgets.Aerc, args []string) []string {
return commands.CompletionFromList(aerc, valid, args)
}
-func (Archive) Execute(aerc *widgets.Aerc, args []string) error {
- if len(args) != 2 {
- return errors.New("Usage: archive <flat|year|month>")
- }
+func (a Archive) Execute(aerc *widgets.Aerc, args []string) error {
h := newHelper(aerc)
msgs, err := h.messages()
if err != nil {
return err
}
- err = archive(aerc, msgs, args[1])
+ err = archive(aerc, msgs, a.Type)
return err
}
diff --git a/commands/msg/copy.go b/commands/msg/copy.go
index 7118e4f8..0fe3ae00 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/commands"
"git.sr.ht/~rjarry/aerc/widgets"
"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(aerc *widgets.Aerc, args []string) []string {
return commands.GetFolders(aerc, args)
}
-func (Copy) Execute(aerc *widgets.Aerc, 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(aerc *widgets.Aerc, args []string) error {
h := newHelper(aerc)
uids, err := h.markedOrSelectedUids()
if err != nil {
@@ -49,8 +35,8 @@ func (Copy) Execute(aerc *widgets.Aerc, 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 6d3fb4a3..f601380d 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/config"
@@ -27,10 +26,6 @@ func (Delete) Complete(aerc *widgets.Aerc, args []string) []string {
}
func (Delete) Execute(aerc *widgets.Aerc, args []string) error {
- if len(args) != 1 {
- return errors.New("Usage: :delete")
- }
-
h := newHelper(aerc)
store, err := h.store()
if err != nil {
diff --git a/commands/msg/envelope.go b/commands/msg/envelope.go
index 1b16d16f..9e77a98c 100644
--- a/commands/msg/envelope.go
+++ b/commands/msg/envelope.go
@@ -9,11 +9,13 @@ import (
"git.sr.ht/~rjarry/aerc/log"
"git.sr.ht/~rjarry/aerc/models"
"git.sr.ht/~rjarry/aerc/widgets"
- "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(aerc *widgets.Aerc, args []string) []string {
return nil
}
-func (Envelope) Execute(aerc *widgets.Aerc, 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(aerc *widgets.Aerc, args []string) error {
acct := aerc.SelectedAccount()
if acct == nil {
return errors.New("No account selected")
@@ -53,10 +40,10 @@ func (Envelope) Execute(aerc *widgets.Aerc, 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 14d00f17..128b1c80 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"
@@ -24,9 +23,6 @@ func (Fold) Complete(aerc *widgets.Aerc, args []string) []string {
}
func (Fold) Execute(aerc *widgets.Aerc, args []string) error {
- if len(args) != 1 {
- return fmt.Errorf("Usage: %s", args[0])
- }
h := newHelper(aerc)
store, err := h.store()
if err != nil {
diff --git a/commands/msg/forward.go b/commands/msg/forward.go
index 86c52059..5e1383f2 100644
--- a/commands/msg/forward.go
+++ b/commands/msg/forward.go
@@ -20,11 +20,16 @@ import (
"git.sr.ht/~rjarry/aerc/widgets"
"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(aerc *widgets.Aerc, args []string) []string {
return nil
}
-func (forward) Execute(aerc *widgets.Aerc, 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(aerc *widgets.Aerc, 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 := aerc.SelectedTabContent().(widgets.ProvidesMessage)
acct := widget.SelectedAccount()
@@ -89,7 +69,7 @@ func (forward) Execute(aerc *widgets.Aerc, 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(aerc *widgets.Aerc, args []string) error {
addTab := func() (*widgets.Composer, error) {
composer, err := widgets.NewComposer(aerc, acct,
acct.AccountConfig(), acct.Worker(), editHeaders,
- template, h, &original, nil)
+ f.Template, h, &original, nil)
if err != nil {
aerc.PushError("Error: " + err.Error())
return nil, err
@@ -124,7 +104,7 @@ func (forward) Execute(aerc *widgets.Aerc, 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(aerc *widgets.Aerc, 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(aerc *widgets.Aerc, 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 309fe643..5b3f7fbe 100644
--- a/commands/msg/invite.go
+++ b/commands/msg/invite.go
@@ -12,11 +12,13 @@ import (
"git.sr.ht/~rjarry/aerc/log"
"git.sr.ht/~rjarry/aerc/models"
"git.sr.ht/~rjarry/aerc/widgets"
- "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(aerc *widgets.Aerc, args []string) []string {
return nil
}
-func (invite) Execute(aerc *widgets.Aerc, args []string) error {
+func (i invite) Execute(aerc *widgets.Aerc, args []string) error {
acct := aerc.SelectedAccount()
if acct == nil {
return errors.New("no account selected")
@@ -49,22 +51,7 @@ func (invite) Execute(aerc *widgets.Aerc, 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 51aa1eb4..68d12df6 100644
--- a/commands/msg/mark.go
+++ b/commands/msg/mark.go
@@ -4,10 +4,15 @@ import (
"fmt"
"git.sr.ht/~rjarry/aerc/widgets"
- "git.sr.ht/~sircmpwn/getopt"
)
-type Mark struct{}
+type Mark struct {
+ All bool `opt:"-a"`
+ Toggle bool `opt:"-t"`
+ Visual bool `opt:"-v"`
+ VisualClear bool `opt:"-V"`
+ Thread bool `opt:"-T"`
+}
func init() {
register(Mark{})
@@ -21,7 +26,7 @@ func (Mark) Complete(aerc *widgets.Aerc, args []string) []string {
return nil
}
-func (Mark) Execute(aerc *widgets.Aerc, args []string) error {
+func (m Mark) Execute(aerc *widgets.Aerc, args []string) error {
h := newHelper(aerc)
OnSelectedMessage := func(fn func(uint32)) error {
if fn == nil {
@@ -39,63 +44,38 @@ func (Mark) Execute(aerc *widgets.Aerc, 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
@@ -110,22 +90,22 @@ func (Mark) Execute(aerc *widgets.Aerc, 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
@@ -139,7 +119,7 @@ func (Mark) Execute(aerc *widgets.Aerc, args []string) error {
return nil
}
case "remark":
- if all || visual || toggle || thread {
+ if m.All || m.Thread || m.Visual || m.Toggle || m.VisualClear {
return fmt.Errorf("Usage: :remark")
}
marker.Remark()
diff --git a/commands/msg/modify-labels.go b/commands/msg/modify-labels.go
index 02eed520..72915e65 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/commands"
@@ -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(aerc *widgets.Aerc, args []string) []string {
return commands.GetLabels(aerc, args)
}
-func (ModifyLabels) Execute(aerc *widgets.Aerc, args []string) error {
- changes := args[1:]
- if len(changes) == 0 {
- return errors.New("Usage: modify-labels <[+-]label> ...")
- }
-
+func (m ModifyLabels) Execute(aerc *widgets.Aerc, args []string) error {
h := newHelper(aerc)
store, err := h.store()
if err != nil {
@@ -40,7 +36,7 @@ func (ModifyLabels) Execute(aerc *widgets.Aerc, 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 e8661a61..c5f58b40 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/commands"
@@ -12,10 +10,12 @@ import (
"git.sr.ht/~rjarry/aerc/models"
"git.sr.ht/~rjarry/aerc/widgets"
"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(aerc *widgets.Aerc, args []string) []string {
return commands.GetFolders(aerc, args)
}
-func (Move) Execute(aerc *widgets.Aerc, 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(aerc *widgets.Aerc, args []string) error {
h := newHelper(aerc)
acct, err := h.account()
if err != nil {
@@ -64,14 +50,13 @@ func (Move) Execute(aerc *widgets.Aerc, 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(aerc, acct, next, "Messages moved to "+joinedArgs, store)
+ handleDone(aerc, acct, next, "Messages moved to "+m.Folder, store)
case *types.Error:
aerc.PushError(msg.Error.Error())
marker.Remark()
diff --git a/commands/msg/pipe.go b/commands/msg/pipe.go
index fc1ac8f8..d37ab0ff 100644
--- a/commands/msg/pipe.go
+++ b/commands/msg/pipe.go
@@ -14,11 +14,14 @@ import (
"git.sr.ht/~rjarry/aerc/widgets"
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"`
+ Cmd []string `opt:"..."`
+}
func init() {
register(Pipe{})
@@ -32,44 +35,17 @@ func (Pipe) Complete(aerc *widgets.Aerc, args []string) []string {
return nil
}
-func (Pipe) Execute(aerc *widgets.Aerc, 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(aerc *widgets.Aerc, args []string) error {
+ if p.Full && p.Part {
+ return errors.New("-m and -p are mutually exclusive")
}
provider := aerc.SelectedTabContent().(widgets.ProvidesMessage)
- if !pipeFull && !pipePart {
+ if !p.Full && !p.Part {
if _, ok := provider.(*widgets.MessageViewer); ok {
- pipePart = true
+ p.Part = true
} else if _, ok := provider.(*widgets.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(aerc *widgets.Aerc, args []string) error {
}
doTerm := func(reader io.Reader, name string) {
- term, err := commands.QuickTerm(aerc, cmd, reader)
+ term, err := commands.QuickTerm(aerc, p.Cmd, reader)
if err != nil {
aerc.PushError(err.Error())
return
@@ -86,7 +62,7 @@ func (Pipe) Execute(aerc *widgets.Aerc, args []string) error {
}
doExec := func(reader io.Reader) {
- ecmd := exec.Command(cmd[0], cmd[1:]...)
+ ecmd := exec.Command(p.Cmd[0], p.Cmd[1:]...)
pipe, err := ecmd.StdinPipe()
if err != nil {
return
@@ -106,17 +82,17 @@ func (Pipe) Execute(aerc *widgets.Aerc, args []string) error {
} else {
if ecmd.ProcessState.ExitCode() != 0 {
aerc.PushError(fmt.Sprintf(
- "%s: completed with status %d", cmd[0],
+ "%s: completed with status %d", p.Cmd[0],
ecmd.ProcessState.ExitCode()))
} else {
aerc.PushStatus(fmt.Sprintf(
- "%s: completed with status %d", cmd[0],
+ "%s: completed with status %d", p.Cmd[0],
ecmd.ProcessState.ExitCode()), 10*time.Second)
}
}
}
- if pipeFull {
+ if p.Full {
var uids []uint32
var title string
@@ -125,12 +101,12 @@ func (Pipe) Execute(aerc *widgets.Aerc, args []string) error {
if err != nil {
if mv, ok := provider.(*widgets.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.Cmd[0], title))
}
})
return nil
@@ -202,27 +178,27 @@ func (Pipe) Execute(aerc *widgets.Aerc, 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.Cmd[0], title))
}
}()
- } else if pipePart {
+ } else if p.Part {
mv, ok := provider.(*widgets.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.Cmd[0], part.Msg.Envelope.Subject, part.Index)
doTerm(reader, name)
}
})
diff --git a/commands/msg/read.go b/commands/msg/read.go
index cffd2218..55bb90c4 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/models"
"git.sr.ht/~rjarry/aerc/widgets"
"git.sr.ht/~rjarry/aerc/worker/types"
)
-type FlagMsg struct{}
+type FlagMsg struct {
+ Toggle bool `opt:"-t"`
+ Answered bool `opt:"-a"`
+ Flag models.Flags `opt:"-x" parse:"ParseFlag"`
+ FlagName string
+}
func init() {
register(FlagMsg{})
@@ -25,6 +29,23 @@ func (FlagMsg) Complete(aerc *widgets.Aerc, 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,84 +53,18 @@ func (FlagMsg) Complete(aerc *widgets.Aerc, 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(aerc *widgets.Aerc, 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(aerc *widgets.Aerc, 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":
+ if f.Flag != 0 || f.Answered {
+ // these arguments are only supported for :flag :unflag
+ return fmt.Errorf("Usage: %s -t", args[0])
}
- }
- 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)
+ f.Flag = models.SeenFlag
+ f.FlagName = "seen"
}
h := newHelper(aerc)
@@ -122,7 +77,7 @@ func (FlagMsg) Execute(aerc *widgets.Aerc, 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 +85,35 @@ func (FlagMsg) Execute(aerc *widgets.Aerc, 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:
- aerc.PushStatus(actionName+" flag '"+flagName+"' successful", 10*time.Second)
+ aerc.PushStatus(status, 10*time.Second)
store.Marker().ClearVisualMark()
case *types.Error:
aerc.PushError(msg.Error.Error())
@@ -160,10 +121,10 @@ func (FlagMsg) Execute(aerc *widgets.Aerc, 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:
- aerc.PushStatus(actionName+" flag '"+flagName+"' successful", 10*time.Second)
+ aerc.PushStatus(status, 10*time.Second)
store.Marker().ClearVisualMark()
case *types.Error:
aerc.PushError(msg.Error.Error())
diff --git a/commands/msg/recall.go b/commands/msg/recall.go
index e7579ca1..030924d3 100644
--- a/commands/msg/recall.go
+++ b/commands/msg/recall.go
@@ -15,10 +15,13 @@ import (
"git.sr.ht/~rjarry/aerc/log"
"git.sr.ht/~rjarry/aerc/widgets"
"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(aerc *widgets.Aerc, args []string) []string {
return nil
}
-func (Recall) Execute(aerc *widgets.Aerc, 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(aerc *widgets.Aerc, args []string) error {
+ editHeaders := (config.Compose.EditHeaders || r.Edit) && !r.NoEdit
widget := aerc.SelectedTabContent().(widgets.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(aerc *widgets.Aerc, 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 b2a61a80..dc7750f7 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/commands/account"
"git.sr.ht/~rjarry/aerc/config"
"git.sr.ht/~rjarry/aerc/lib"
@@ -23,7 +21,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{})
@@ -37,37 +42,8 @@ func (reply) Complete(aerc *widgets.Aerc, args []string) []string {
return nil
}
-func (reply) Execute(aerc *widgets.Aerc, 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(aerc *widgets.Aerc, args []string) error {
+ editHeaders := (config.Compose.EditHeaders || r.Edit) && !r.NoEdit
widget := aerc.SelectedTabContent().(widgets.ProvidesMessage)
acct := widget.SelectedAccount()
@@ -132,7 +108,7 @@ func (reply) Execute(aerc *widgets.Aerc, 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
@@ -181,12 +157,12 @@ func (reply) Execute(aerc *widgets.Aerc, args []string) error {
addTab := func() error {
composer, err := widgets.NewComposer(aerc, acct,
acct.AccountConfig(), acct.Worker(), editHeaders,
- template, h, &original, nil)
+ r.Template, h, &original, nil)
if err != nil {
aerc.PushError("Error: " + err.Error())
return err
}
- if mv != nil && closeOnReply {
+ if mv != nil && r.Close {
aerc.RemoveTab(mv, true)
}
@@ -206,18 +182,19 @@ func (reply) Execute(aerc *widgets.Aerc, 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(aerc, []string{"-p"})
+ view.Execute(aerc, []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) {
@@ -272,8 +249,8 @@ func (reply) Execute(aerc *widgets.Aerc, 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 09c60b85..e2b5369f 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"
"git.sr.ht/~rjarry/aerc/widgets"
)
@@ -22,9 +20,6 @@ func (ToggleThreadContext) Complete(aerc *widgets.Aerc, args []string) []string
}
func (ToggleThreadContext) Execute(aerc *widgets.Aerc, args []string) error {
- if len(args) != 1 {
- return errors.New("Usage: toggle-entire-thread")
- }
h := newHelper(aerc)
store, err := h.store()
if err != nil {
diff --git a/commands/msg/toggle-threads.go b/commands/msg/toggle-threads.go
index 9508da50..5728a720 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"
"git.sr.ht/~rjarry/aerc/widgets"
@@ -23,9 +21,6 @@ func (ToggleThreads) Complete(aerc *widgets.Aerc, args []string) []string {
}
func (ToggleThreads) Execute(aerc *widgets.Aerc, args []string) error {
- if len(args) != 1 {
- return errors.New("Usage: toggle-threads")
- }
h := newHelper(aerc)
acct, err := h.account()
if err != nil {
diff --git a/commands/msg/unsubscribe.go b/commands/msg/unsubscribe.go
index 505392d4..41159f03 100644
--- a/commands/msg/unsubscribe.go
+++ b/commands/msg/unsubscribe.go
@@ -12,13 +12,15 @@ import (
"git.sr.ht/~rjarry/aerc/lib"
"git.sr.ht/~rjarry/aerc/log"
"git.sr.ht/~rjarry/aerc/widgets"
- "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(aerc *widgets.Aerc, args []string) []string {
}
// Execute runs the Unsubscribe command
-func (Unsubscribe) Execute(aerc *widgets.Aerc, 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(aerc *widgets.Aerc, args []string) error {
+ editHeaders := (config.Compose.EditHeaders || u.Edit) && !u.NoEdit
+
widget := aerc.SelectedTabContent().(widgets.ProvidesMessage)
msg, err := widget.SelectedMessage()
if err != nil {
diff --git a/commands/msgview/close.go b/commands/msgview/close.go
index 5e4f3e92..71b6767a 100644
--- a/commands/msgview/close.go
+++ b/commands/msgview/close.go
@@ -1,8 +1,6 @@
package msgview
import (
- "errors"
-
"git.sr.ht/~rjarry/aerc/widgets"
)
@@ -21,9 +19,6 @@ func (Close) Complete(aerc *widgets.Aerc, args []string) []string {
}
func (Close) Execute(aerc *widgets.Aerc, args []string) error {
- if len(args) != 1 {
- return errors.New("Usage: close")
- }
mv, _ := aerc.SelectedTabContent().(*widgets.MessageViewer)
aerc.RemoveTab(mv, true)
return nil
diff --git a/commands/msgview/next-part.go b/commands/msgview/next-part.go
index 652dccb6..e1225d63 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/widgets"
)
-type NextPrevPart struct{}
+type NextPrevPart struct {
+ Offset int `opt:"N" default:"1"`
+}
func init() {
register(NextPrevPart{})
@@ -21,22 +20,9 @@ func (NextPrevPart) Complete(aerc *widgets.Aerc, args []string) []string {
return nil
}
-func (NextPrevPart) Execute(aerc *widgets.Aerc, 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(aerc *widgets.Aerc, args []string) error {
mv, _ := aerc.SelectedTabContent().(*widgets.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(aerc *widgets.Aerc, 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 cc7a5479..b113af22 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/commands/account"
"git.sr.ht/~rjarry/aerc/lib"
@@ -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[%]" parse:"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(aerc *widgets.Aerc, args []string) []string {
return nil
}
-func (NextPrevMsg) Execute(aerc *widgets.Aerc, args []string) error {
- n, pct, err := account.ParseNextPrevMessage(args)
+func (np NextPrevMsg) Execute(aerc *widgets.Aerc, args []string) error {
+ cmd := account.NextPrevMsg{Amount: np.Amount, Percent: np.Percent}
+ err := cmd.Execute(aerc, args)
if err != nil {
return err
}
+
mv, _ := aerc.SelectedTabContent().(*widgets.MessageViewer)
acct := mv.SelectedAccount()
if acct == nil {
@@ -39,10 +59,6 @@ func (NextPrevMsg) Execute(aerc *widgets.Aerc, 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, aerc.Crypto, aerc.DecryptKeys,
diff --git a/commands/msgview/open-link.go b/commands/msgview/open-link.go
index da58b717..2858f13c 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/widgets"
)
-type OpenLink struct{}
+type OpenLink struct {
+ Url *url.URL `opt:"URL" parse:"ParseUrl"`
+ Cmd []string `opt:"..." required:"false"`
+}
func init() {
register(OpenLink{})
@@ -31,18 +33,20 @@ func (OpenLink) Complete(aerc *widgets.Aerc, args []string) []string {
return nil
}
-func (OpenLink) Execute(aerc *widgets.Aerc, 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(aerc *widgets.Aerc, 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 {
aerc.PushError("open-link: " + err.Error())
}
}()
diff --git a/commands/msgview/open.go b/commands/msgview/open.go
index b66456cc..b9a4745d 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/lib"
"git.sr.ht/~rjarry/aerc/log"
"git.sr.ht/~rjarry/aerc/widgets"
)
-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(aerc *widgets.Aerc, args []string) []string {
}
func (o Open) Execute(aerc *widgets.Aerc, 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 := aerc.SelectedTabContent().(*widgets.MessageViewer)
if mv == nil {
return errors.New("open only supported selected message parts")
@@ -84,10 +72,10 @@ func (o Open) Execute(aerc *widgets.Aerc, 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 {
aerc.PushError("open: " + err.Error())
}
diff --git a/commands/msgview/save.go b/commands/msgview/save.go
index 1ffdaf92..3d923916 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/commands"
"git.sr.ht/~rjarry/aerc/config"
"git.sr.ht/~rjarry/aerc/lib/xdg"
@@ -19,7 +17,13 @@ import (
"git.sr.ht/~rjarry/aerc/widgets"
)
-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(aerc *widgets.Aerc, 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(aerc *widgets.Aerc, 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 := aerc.SelectedTabContent().(*widgets.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, aerc, &params, names); err != nil {
+ if err := s.savePart(pi, mv, aerc, names); err != nil {
return err
}
}
@@ -121,24 +81,23 @@ func (s Save) Execute(aerc *widgets.Aerc, args []string) error {
}
pi := mv.SelectedMessagePart()
- return savePart(pi, path, mv, aerc, &params, make(map[string]struct{}))
+ return s.savePart(pi, mv, aerc, make(map[string]struct{}))
}
-func savePart(
+func (s *Save) savePart(
pi *widgets.PartInfo,
- path string,
mv *widgets.MessageViewer,
aerc *widgets.Aerc,
- 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
@@ -148,7 +107,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 1a593494..00abb0e9 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/widgets"
)
@@ -21,14 +19,7 @@ func (ToggleHeaders) Complete(aerc *widgets.Aerc, args []string) []string {
}
func (ToggleHeaders) Execute(aerc *widgets.Aerc, args []string) error {
- if len(args) > 1 {
- return toggleHeadersUsage(args[0])
- }
mv, _ := aerc.SelectedTabContent().(*widgets.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 00a39559..fd284ec0 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/lib/state"
"git.sr.ht/~rjarry/aerc/widgets"
)
@@ -22,9 +20,6 @@ func (ToggleKeyPassthrough) Complete(aerc *widgets.Aerc, args []string) []string
}
func (ToggleKeyPassthrough) Execute(aerc *widgets.Aerc, args []string) error {
- if len(args) != 1 {
- return errors.New("Usage: toggle-key-passthrough")
- }
mv, _ := aerc.SelectedTabContent().(*widgets.MessageViewer)
keyPassthroughEnabled := mv.ToggleKeyPassthrough()
if acct := mv.SelectedAccount(); acct != nil {
diff --git a/commands/new-account.go b/commands/new-account.go
index d67b5eca..26c96314 100644
--- a/commands/new-account.go
+++ b/commands/new-account.go
@@ -1,13 +1,12 @@
package commands
import (
- "errors"
-
"git.sr.ht/~rjarry/aerc/widgets"
- "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(aerc *widgets.Aerc, args []string) []string {
return nil
}
-func (NewAccount) Execute(aerc *widgets.Aerc, args []string) error {
- opts, _, err := getopt.Getopts(args, "t")
- if err != nil {
- return errors.New("Usage: new-account [-t]")
- }
+func (n NewAccount) Execute(aerc *widgets.Aerc, args []string) error {
wizard := widgets.NewAccountWizard(aerc)
- for _, opt := range opts {
- if opt.Option == 't' {
- wizard.ConfigureTemporaryAccount(true)
- }
- }
+ wizard.ConfigureTemporaryAccount(n.Temp)
wizard.Focus(true)
aerc.NewTab(wizard, "New account")
return nil
diff --git a/commands/next-tab.go b/commands/next-tab.go
index 854353f8..6fef4b12 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/widgets"
)
-type NextPrevTab struct{}
+type NextPrevTab struct {
+ Offset int `opt:"N" default:"1"`
+}
func init() {
register(NextPrevTab{})
@@ -21,21 +20,8 @@ func (NextPrevTab) Complete(aerc *widgets.Aerc, args []string) []string {
return nil
}
-func (NextPrevTab) Execute(aerc *widgets.Aerc, 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(aerc *widgets.Aerc, args []string) error {
+ for n := 0; n < np.Offset; n++ {
if args[0] == "prev-tab" {
aerc.PrevTab()
} else {
@@ -45,7 +31,3 @@ func (NextPrevTab) Execute(aerc *widgets.Aerc, args []string) error {
aerc.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 9a626614..ffbd64bb 100644
--- a/commands/pin-tab.go
+++ b/commands/pin-tab.go
@@ -1,8 +1,6 @@
package commands
import (
- "fmt"
-
"git.sr.ht/~rjarry/aerc/widgets"
)
@@ -21,10 +19,6 @@ func (PinTab) Complete(aerc *widgets.Aerc, args []string) []string {
}
func (PinTab) Execute(aerc *widgets.Aerc, args []string) error {
- if len(args) != 1 {
- return fmt.Errorf("Usage: %s", args[0])
- }
-
switch args[0] {
case "pin-tab":
aerc.PinTab()
diff --git a/commands/prompt.go b/commands/prompt.go
index 3d84703d..0fa76ff7 100644
--- a/commands/prompt.go
+++ b/commands/prompt.go
@@ -1,14 +1,15 @@
package commands
import (
- "fmt"
"strings"
- "git.sr.ht/~rjarry/aerc/lib/opt"
"git.sr.ht/~rjarry/aerc/widgets"
)
-type Prompt struct{}
+type Prompt struct {
+ Text string `opt:"TEXT"`
+ Cmd string `opt:"..."`
+}
func init() {
register(Prompt{})
@@ -70,16 +71,7 @@ func (Prompt) Complete(aerc *widgets.Aerc, args []string) []string {
return rs
}
-func (Prompt) Execute(aerc *widgets.Aerc, args []string) error {
- if len(args) < 3 {
- return fmt.Errorf("Usage: %s <prompt> <cmd>", args[0])
- }
-
- prompt := args[1]
- cmd, err := opt.QuoteArgs(args[2:]...)
- if err != nil {
- return err
- }
- aerc.RegisterPrompt(prompt, cmd.String())
+func (p Prompt) Execute(aerc *widgets.Aerc, args []string) error {
+ aerc.RegisterPrompt(p.Text, p.Cmd)
return nil
}
diff --git a/commands/pwd.go b/commands/pwd.go
index 624258ce..a97c6e4e 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(aerc *widgets.Aerc, args []string) []string {
}
func (PrintWorkDir) Execute(aerc *widgets.Aerc, 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 09791a74..a02b183b 100644
--- a/commands/quit.go
+++ b/commands/quit.go
@@ -1,15 +1,15 @@
package commands
import (
- "errors"
"fmt"
"git.sr.ht/~rjarry/aerc/commands/mode"
"git.sr.ht/~rjarry/aerc/widgets"
- "git.sr.ht/~sircmpwn/getopt"
)
-type Quit struct{}
+type Quit struct {
+ Force bool `opt:"-f"`
+}
func init() {
register(Quit{})
@@ -29,21 +29,8 @@ func (err ErrorExit) Error() string {
return "exit"
}
-func (Quit) Execute(aerc *widgets.Aerc, 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(aerc *widgets.Aerc, 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/term.go b/commands/term.go
index 22957635..1a0bcc69 100644
--- a/commands/term.go
+++ b/commands/term.go
@@ -9,7 +9,9 @@ import (
"git.sr.ht/~rjarry/aerc/widgets"
)
-type Term struct{}
+type Term struct {
+ Cmd []string `opt:"..." required:"false"`
+}
func init() {
register(Term{})
@@ -23,23 +25,22 @@ func (Term) Complete(aerc *widgets.Aerc, args []string) []string {
return nil
}
-// The help command is an alias for `term man` thus Term requires a simple func
-func TermCore(aerc *widgets.Aerc, args []string) error {
- if len(args) == 1 {
+func (t Term) Execute(aerc *widgets.Aerc, 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 := widgets.NewTerminal(exec.Command(args[1], args[2:]...))
+ term, err := widgets.NewTerminal(exec.Command(t.Cmd[0], t.Cmd[1:]...))
if err != nil {
return err
}
- tab := aerc.NewTab(term, args[1])
+ tab := aerc.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(aerc *widgets.Aerc, args []string) error {
}
return nil
}
-
-func (Term) Execute(aerc *widgets.Aerc, args []string) error {
- return TermCore(aerc, args)
-}
diff --git a/commands/terminal/close.go b/commands/terminal/close.go
index 812266c0..ded38b95 100644
--- a/commands/terminal/close.go
+++ b/commands/terminal/close.go
@@ -1,8 +1,6 @@
package terminal
import (
- "errors"
-
"git.sr.ht/~rjarry/aerc/widgets"
)
@@ -21,9 +19,6 @@ func (Close) Complete(aerc *widgets.Aerc, args []string) []string {
}
func (Close) Execute(aerc *widgets.Aerc, args []string) error {
- if len(args) != 1 {
- return errors.New("Usage: close")
- }
term, _ := aerc.SelectedTabContent().(*widgets.Terminal)
term.Close()
return nil
diff --git a/commands/z.go b/commands/z.go
index ca982ba7..a0054faa 100644
--- a/commands/z.go
+++ b/commands/z.go
@@ -9,7 +9,9 @@ import (
"git.sr.ht/~rjarry/aerc/widgets"
)
-type Zoxide struct{}
+type Zoxide struct {
+ Target string `opt:"..." default:"~" metavar:"DIRECTORY|QUERY"`
+}
func ZoxideAdd(arg string) error {
zargs := []string{"add", arg}
@@ -42,15 +44,9 @@ func (Zoxide) Complete(aerc *widgets.Aerc, args []string) []string {
// Execute calls zoxide add and query and delegates actually changing the
// directory to ChangeDirectory
-func (Zoxide) Execute(aerc *widgets.Aerc, 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(aerc, args)
- case "-":
+func (z Zoxide) Execute(aerc *widgets.Aerc, args []string) error {
+ switch z.Target {
+ case "-", "~":
if previousDir != "" {
err := ZoxideAdd(previousDir)
if err != nil {
@@ -59,7 +55,7 @@ func (Zoxide) Execute(aerc *widgets.Aerc, args []string) error {
}
return ChangeDirectory{}.Execute(aerc, args)
default:
- _, err := os.Stat(target)
+ _, err := os.Stat(z.Target)
if err != nil {
// not a file, assume zoxide query
res, err := ZoxideQuery(args)
@@ -70,11 +66,12 @@ func (Zoxide) Execute(aerc *widgets.Aerc, args []string) error {
if err != nil {
return err
}
- return ChangeDirectory{}.Execute(aerc, []string{"z", res})
+ cd := ChangeDirectory{Target: res}
+ return cd.Execute(aerc, []string{"cd", res})
}
} else {
- err := ZoxideAdd(target)
+ err := ZoxideAdd(z.Target)
if err != nil {
return err
}