mirror of
https://github.com/jhillyerd/inbucket.git
synced 2025-12-17 09:37:02 +00:00
Add powerful match subcommand to cmdline client
- Multiple output formats - Signals matches via exit status for shell scripts - Match against To, From, Subject via regular expressions - Can optionally delete matched messages
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
|
||||
"github.com/google/subcommands"
|
||||
)
|
||||
@@ -13,6 +14,38 @@ import (
|
||||
var host = flag.String("host", "localhost", "host/IP of Inbucket server")
|
||||
var port = flag.Uint("port", 9000, "HTTP port of Inbucket server")
|
||||
|
||||
// Allow subcommands to accept regular expressions as flags
|
||||
type regexFlag struct {
|
||||
*regexp.Regexp
|
||||
}
|
||||
|
||||
func (r *regexFlag) Defined() bool {
|
||||
return r.Regexp != nil
|
||||
}
|
||||
|
||||
func (r *regexFlag) Set(pattern string) error {
|
||||
if pattern == "" {
|
||||
r.Regexp = nil
|
||||
return nil
|
||||
}
|
||||
re, err := regexp.Compile(pattern)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.Regexp = re
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *regexFlag) String() string {
|
||||
if r.Regexp == nil {
|
||||
return ""
|
||||
}
|
||||
return r.Regexp.String()
|
||||
}
|
||||
|
||||
// regexFlag must implement flag.Value
|
||||
var _ flag.Value = ®exFlag{}
|
||||
|
||||
func main() {
|
||||
// Important top-level flags
|
||||
subcommands.ImportantFlag("host")
|
||||
@@ -23,6 +56,7 @@ func main() {
|
||||
subcommands.Register(subcommands.CommandsCommand(), "")
|
||||
// Setup my commands
|
||||
subcommands.Register(&listCmd{}, "")
|
||||
subcommands.Register(&matchCmd{}, "")
|
||||
subcommands.Register(&mboxCmd{}, "")
|
||||
// Parse and execute
|
||||
flag.Parse()
|
||||
|
||||
141
cmd/client/match.go
Normal file
141
cmd/client/match.go
Normal file
@@ -0,0 +1,141 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/google/subcommands"
|
||||
"github.com/jhillyerd/inbucket/rest/client"
|
||||
)
|
||||
|
||||
type matchCmd struct {
|
||||
mailbox string
|
||||
output string
|
||||
outFunc func(headers []*client.MessageHeader) error
|
||||
delete bool
|
||||
from regexFlag
|
||||
subject regexFlag
|
||||
to regexFlag
|
||||
}
|
||||
|
||||
func (*matchCmd) Name() string {
|
||||
return "match"
|
||||
}
|
||||
|
||||
func (*matchCmd) Synopsis() string {
|
||||
return "output messages matching criteria"
|
||||
}
|
||||
|
||||
func (*matchCmd) Usage() string {
|
||||
return `match [options] <mailbox>:
|
||||
output messages matching all specified criteria
|
||||
exit status will be 1 if no matches were found, otherwise 0
|
||||
`
|
||||
}
|
||||
|
||||
func (m *matchCmd) SetFlags(f *flag.FlagSet) {
|
||||
f.StringVar(&m.output, "output", "id", "output format: id, json, or mbox")
|
||||
f.BoolVar(&m.delete, "delete", false, "delete matched messages after output")
|
||||
f.Var(&m.from, "from", "From header matching regexp")
|
||||
f.Var(&m.subject, "subject", "Subject header matching regexp")
|
||||
f.Var(&m.to, "to", "To header matching regexp (must match one)")
|
||||
}
|
||||
|
||||
func (m *matchCmd) Execute(
|
||||
_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
||||
mailbox := f.Arg(0)
|
||||
if mailbox == "" {
|
||||
return usage("mailbox required")
|
||||
}
|
||||
// Select output function
|
||||
switch m.output {
|
||||
case "id":
|
||||
m.outFunc = outputID
|
||||
case "json":
|
||||
m.outFunc = outputJSON
|
||||
case "mbox":
|
||||
m.outFunc = outputMbox
|
||||
default:
|
||||
return usage("unknown output type: " + m.output)
|
||||
}
|
||||
// Setup REST client
|
||||
c, err := client.New(baseURL())
|
||||
if err != nil {
|
||||
return fatal("Couldn't build client", err)
|
||||
}
|
||||
// Get list
|
||||
headers, err := c.ListMailbox(mailbox)
|
||||
if err != nil {
|
||||
return fatal("List REST call failed", err)
|
||||
}
|
||||
// Find matches
|
||||
matches := make([]*client.MessageHeader, 0, len(headers))
|
||||
for _, h := range headers {
|
||||
if m.match(h) {
|
||||
matches = append(matches, h)
|
||||
}
|
||||
}
|
||||
// Return error status if no matches
|
||||
if len(matches) == 0 {
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
// Output matches
|
||||
err = m.outFunc(matches)
|
||||
if err != nil {
|
||||
return fatal("Error", err)
|
||||
}
|
||||
if m.delete {
|
||||
// Delete matches
|
||||
for _, h := range matches {
|
||||
err = h.Delete()
|
||||
if err != nil {
|
||||
return fatal("Delete REST call failed", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return subcommands.ExitSuccess
|
||||
}
|
||||
|
||||
// match returns true if header matches all defined criteria
|
||||
func (m *matchCmd) match(header *client.MessageHeader) bool {
|
||||
if m.subject.Defined() {
|
||||
if !m.subject.MatchString(header.Subject) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if m.from.Defined() {
|
||||
if !m.from.MatchString(header.From) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if m.to.Defined() {
|
||||
match := false
|
||||
for _, to := range header.To {
|
||||
if m.to.MatchString(to) {
|
||||
match = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !match {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func outputID(headers []*client.MessageHeader) error {
|
||||
for _, h := range headers {
|
||||
fmt.Println(h.ID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func outputJSON(headers []*client.MessageHeader) error {
|
||||
jsonEncoder := json.NewEncoder(os.Stdout)
|
||||
jsonEncoder.SetEscapeHTML(false)
|
||||
jsonEncoder.SetIndent("", " ")
|
||||
return jsonEncoder.Encode(headers)
|
||||
}
|
||||
@@ -39,7 +39,7 @@ func (m *mboxCmd) Execute(
|
||||
if mailbox == "" {
|
||||
return usage("mailbox required")
|
||||
}
|
||||
// Setup rest client
|
||||
// Setup REST client
|
||||
c, err := client.New(baseURL())
|
||||
if err != nil {
|
||||
return fatal("Couldn't build client", err)
|
||||
@@ -49,22 +49,34 @@ func (m *mboxCmd) Execute(
|
||||
if err != nil {
|
||||
return fatal("List REST call failed", err)
|
||||
}
|
||||
for _, h := range headers {
|
||||
source, err := h.GetSource()
|
||||
if err != nil {
|
||||
return fatal("Source REST call failed", err)
|
||||
}
|
||||
fmt.Printf("From %s\n", h.From)
|
||||
// TODO Escape "From " in message bodies with >
|
||||
source.WriteTo(os.Stdout)
|
||||
fmt.Println()
|
||||
if m.delete {
|
||||
err = outputMbox(headers)
|
||||
if err != nil {
|
||||
return fatal("Error", err)
|
||||
}
|
||||
if m.delete {
|
||||
// Delete matches
|
||||
for _, h := range headers {
|
||||
err = h.Delete()
|
||||
if err != nil {
|
||||
return fatal("Delete REST call failed", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return subcommands.ExitSuccess
|
||||
}
|
||||
|
||||
// outputMbox renders messages in mbox format
|
||||
// also used by match subcommand
|
||||
func outputMbox(headers []*client.MessageHeader) error {
|
||||
for _, h := range headers {
|
||||
source, err := h.GetSource()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Get source REST failed: %v", err)
|
||||
}
|
||||
fmt.Printf("From %s\n", h.From)
|
||||
// TODO Escape "From " in message bodies with >
|
||||
source.WriteTo(os.Stdout)
|
||||
fmt.Println()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user