diff --git a/mailq/parser.go b/mailq/parser.go index 4de87f5..e030acd 100644 --- a/mailq/parser.go +++ b/mailq/parser.go @@ -2,192 +2,14 @@ package mailq import ( "bufio" - "encoding/json" "errors" - "fmt" "io" "regexp" - "sort" "strconv" "strings" - "text/tabwriter" "time" ) -type QEntry struct { - Id string - Status string - Date time.Time - Size int - Sender string - Recipients []string - Reason string -} - -type MailQ struct { - NumTotal int - NumActive int - NumHold int - NumDeferred int - Entries []QEntry -} - -const SortableDateFormat = "2006-01-02 15:04:05" - -func (m QEntry) String() string { - var recipientSuffix string - if len(m.Recipients) > 1 { - recipientSuffix = ",..." - } - return fmt.Sprintf("[%s] %s <%s> -> {%d}<%s>%s (%s, %d bytes)", m.Date.Format(SortableDateFormat), m.Id, m.Sender, len(m.Recipients), m.Recipients[0], recipientSuffix, m.Status, m.Size) -} - -func (m QEntry) MachineReadableString() string { - return fmt.Sprintf("%s_%s_%s_%d_%s_%s_%s", m.Id, m.Status, m.Date.Format(SortableDateFormat), m.Size, m.Sender, m.Reason, strings.Join(m.Recipients, ",")) -} - -func (m QEntry) DetailedString() string { - var reasonStr string - if m.Reason == "" { - reasonStr = "-/-" - } - return fmt.Sprintf("Id: %s\nDate: %s\nStatus: %s\nReason: %s\nSize: %d\nSender: %s\nRecipients: %s", m.Id, m.Date.Format(SortableDateFormat), m.Status, reasonStr, m.Size, m.Sender, strings.Join(m.Recipients, ", ")) -} - -type qEntrySortConfig struct { - attributes []qEntryAttributeSortConfig -} - -type qEntryAttributeSortConfig struct { - attribute string - order string -} - -func NewSortConfig() qEntrySortConfig { - return qEntrySortConfig{} -} - -func isQEntryAttribute(attributeName string) bool { - var isValidAttribute bool - switch attributeName { - case "Id", "Status", "Date", "Size", "Sender", "Recipients", "Reason": - isValidAttribute = true - default: - isValidAttribute = false - } - return isValidAttribute -} - -func (c qEntrySortConfig) By(attributeName string, order string) qEntrySortConfig { - if !isQEntryAttribute(attributeName) { - panic(fmt.Sprintf("Invalid sort attribute: '%s' given!", attributeName)) - } - if order != "ASC" && order != "DESC" { - panic(fmt.Sprintf("Invalid sort order '%s' given!", order)) - } - newAttributeConfig := qEntryAttributeSortConfig{attribute: attributeName, order: order} - c.attributes = append(c.attributes, newAttributeConfig) - return c -} - -func (queue MailQ) Sort(config qEntrySortConfig) { - if len(config.attributes) == 0 { - return - } - fmt.Printf("SortConfig: %q\n", config) - sort.Slice(queue.Entries, func(a int, b int) bool { - for _, sortBy := range config.attributes { - var cmp bool - var skip bool - // Not sure if these are necessary yet - cmp = false - skip = false - switch sortBy.attribute { - case "Id": - if queue.Entries[a].Id == queue.Entries[b].Id { - skip = true - } - cmp = queue.Entries[a].Id < queue.Entries[b].Id - case "Status": - if queue.Entries[a].Status == queue.Entries[b].Status { - skip = true - } - cmp = queue.Entries[a].Status < queue.Entries[b].Status - case "Date": - if queue.Entries[a].Date.Equal(queue.Entries[b].Date) { - skip = true - } - cmp = queue.Entries[a].Date.Before(queue.Entries[b].Date) - case "Size": - if queue.Entries[a].Size == queue.Entries[b].Size { - skip = true - } - cmp = queue.Entries[a].Size < queue.Entries[b].Size - case "Sender": - if queue.Entries[a].Sender == queue.Entries[b].Sender { - skip = true - } - cmp = queue.Entries[a].Sender < queue.Entries[b].Sender - case "Recipients": - if len(queue.Entries[a].Recipients) == len(queue.Entries[b].Recipients) { - skip = true - } - cmp = len(queue.Entries[a].Recipients) < len(queue.Entries[b].Recipients) - case "Reason": - if queue.Entries[a].Reason == queue.Entries[b].Reason { - skip = true - } - cmp = queue.Entries[a].Reason < queue.Entries[b].Reason - default: - // TODO: Handle this error case? - } - if skip == true { - continue - } else { - if sortBy.order == "ASC" { - return cmp - } else if sortBy.order == "DESC" { - return !cmp - } - } - } - return false - }) -} - -func (queue MailQ) PrintMachineReadable(writer io.Writer) { - for _, entry := range queue.Entries { - fmt.Fprintf(writer, "%s\n", entry.MachineReadableString()) - } -} - -func (queue MailQ) PrintHumanReadable(writer io.Writer) { - if len(queue.Entries) == 0 { - fmt.Fprintf(writer, "Mail queue is empty\n") - } else { - fmt.Fprintf(writer, "%d entries total (%d active, %d deferred, %d on hold)\n\n", len(queue.Entries), queue.NumActive, queue.NumDeferred, queue.NumHold) - tabWriter := tabwriter.NewWriter(writer, 2, 2, 1, ' ', tabwriter.TabIndent) - fmt.Fprintf(tabWriter, "| %s\t| %s\t| %s\t| %s\t| %s\t| %s\t| %s\t| %s\t| \n", "Date", "Id", "Status", "Size", "Sender", "#", "First Recipient", "Reason") - for _, entry := range queue.Entries { - _, writeError := fmt.Fprintf(tabWriter, "| %s\t| %s\t| %s\t| %d\t| %s\t| {%d}\t| %s\t| %s\t| \n", entry.Date.Format(SortableDateFormat), entry.Id, entry.Status, entry.Size, entry.Sender, len(entry.Recipients), entry.Recipients[0], entry.Reason) - if writeError != nil { - // A writeError is expected once the reader was closed - break - } - } - tabWriter.Flush() - } -} - -func (queue MailQ) PrintJSON(writer io.Writer) { - bytes, err := json.Marshal(queue) - if err != nil { - fmt.Fprintf(writer, "Error encoding queue to JSON: %s\n", err.Error()) - } else { - fmt.Fprintf(writer, "%s", bytes) - } -} - func ParseMailQ(dataSource io.Reader) (MailQ, error) { const dateFormat = "2006 Mon Jan _2 15:04:05" var messageIdStart = regexp.MustCompile("^[0-9A-Za-z]+[*!]? ") diff --git a/mailq/sorting.go b/mailq/sorting.go new file mode 100644 index 0000000..931d518 --- /dev/null +++ b/mailq/sorting.go @@ -0,0 +1,107 @@ +package mailq + +import ( + "fmt" + "sort" +) + +type qEntrySortConfig struct { + attributes []qEntryAttributeSortConfig +} + +type qEntryAttributeSortConfig struct { + attribute string + order string +} + +func NewSortConfig() qEntrySortConfig { + return qEntrySortConfig{} +} + +func isQEntryAttribute(attributeName string) bool { + var isValidAttribute bool + switch attributeName { + case "Id", "Status", "Date", "Size", "Sender", "Recipients", "Reason": + isValidAttribute = true + default: + isValidAttribute = false + } + return isValidAttribute +} + +func (c qEntrySortConfig) By(attributeName string, order string) qEntrySortConfig { + if !isQEntryAttribute(attributeName) { + panic(fmt.Sprintf("Invalid sort attribute: '%s' given!", attributeName)) + } + if order != "ASC" && order != "DESC" { + panic(fmt.Sprintf("Invalid sort order '%s' given!", order)) + } + newAttributeConfig := qEntryAttributeSortConfig{attribute: attributeName, order: order} + c.attributes = append(c.attributes, newAttributeConfig) + return c +} + +func (queue MailQ) Sort(config qEntrySortConfig) { + if len(config.attributes) == 0 { + return + } + fmt.Printf("SortConfig: %q\n", config) + sort.Slice(queue.Entries, func(a int, b int) bool { + for _, sortBy := range config.attributes { + var cmp bool + var skip bool + // Not sure if these are necessary yet + cmp = false + skip = false + switch sortBy.attribute { + case "Id": + if queue.Entries[a].Id == queue.Entries[b].Id { + skip = true + } + cmp = queue.Entries[a].Id < queue.Entries[b].Id + case "Status": + if queue.Entries[a].Status == queue.Entries[b].Status { + skip = true + } + cmp = queue.Entries[a].Status < queue.Entries[b].Status + case "Date": + if queue.Entries[a].Date.Equal(queue.Entries[b].Date) { + skip = true + } + cmp = queue.Entries[a].Date.Before(queue.Entries[b].Date) + case "Size": + if queue.Entries[a].Size == queue.Entries[b].Size { + skip = true + } + cmp = queue.Entries[a].Size < queue.Entries[b].Size + case "Sender": + if queue.Entries[a].Sender == queue.Entries[b].Sender { + skip = true + } + cmp = queue.Entries[a].Sender < queue.Entries[b].Sender + case "Recipients": + if len(queue.Entries[a].Recipients) == len(queue.Entries[b].Recipients) { + skip = true + } + cmp = len(queue.Entries[a].Recipients) < len(queue.Entries[b].Recipients) + case "Reason": + if queue.Entries[a].Reason == queue.Entries[b].Reason { + skip = true + } + cmp = queue.Entries[a].Reason < queue.Entries[b].Reason + default: + // TODO: Handle this error case? + } + if skip == true { + continue + } else { + if sortBy.order == "ASC" { + return cmp + } else if sortBy.order == "DESC" { + return !cmp + } + } + } + return false + }) +} diff --git a/mailq/string.go b/mailq/string.go new file mode 100644 index 0000000..67c390a --- /dev/null +++ b/mailq/string.go @@ -0,0 +1,64 @@ +package mailq + +import ( + "encoding/json" + "fmt" + "io" + "strings" + "text/tabwriter" +) + +const SortableDateFormat = "2006-01-02 15:04:05" + +func (m QEntry) String() string { + var recipientSuffix string + if len(m.Recipients) > 1 { + recipientSuffix = ",..." + } + return fmt.Sprintf("[%s] %s <%s> -> {%d}<%s>%s (%s, %d bytes)", m.Date.Format(SortableDateFormat), m.Id, m.Sender, len(m.Recipients), m.Recipients[0], recipientSuffix, m.Status, m.Size) +} + +func (m QEntry) MachineReadableString() string { + return fmt.Sprintf("%s_%s_%s_%d_%s_%s_%s", m.Id, m.Status, m.Date.Format(SortableDateFormat), m.Size, m.Sender, m.Reason, strings.Join(m.Recipients, ",")) +} + +func (m QEntry) DetailedString() string { + var reasonStr string + if m.Reason == "" { + reasonStr = "-/-" + } + return fmt.Sprintf("Id: %s\nDate: %s\nStatus: %s\nReason: %s\nSize: %d\nSender: %s\nRecipients: %s", m.Id, m.Date.Format(SortableDateFormat), m.Status, reasonStr, m.Size, m.Sender, strings.Join(m.Recipients, ", ")) +} + +func (queue MailQ) PrintMachineReadable(writer io.Writer) { + for _, entry := range queue.Entries { + fmt.Fprintf(writer, "%s\n", entry.MachineReadableString()) + } +} + +func (queue MailQ) PrintHumanReadable(writer io.Writer) { + if len(queue.Entries) == 0 { + fmt.Fprintf(writer, "Mail queue is empty\n") + } else { + fmt.Fprintf(writer, "%d entries total (%d active, %d deferred, %d on hold)\n\n", len(queue.Entries), queue.NumActive, queue.NumDeferred, queue.NumHold) + tabWriter := tabwriter.NewWriter(writer, 2, 2, 1, ' ', tabwriter.TabIndent) + fmt.Fprintf(tabWriter, "| %s\t| %s\t| %s\t| %s\t| %s\t| %s\t| %s\t| %s\t| \n", "Date", "Id", "Status", "Size", "Sender", "#", "First Recipient", "Reason") + for _, entry := range queue.Entries { + _, writeError := fmt.Fprintf(tabWriter, "| %s\t| %s\t| %s\t| %d\t| %s\t| {%d}\t| %s\t| %s\t| \n", entry.Date.Format(SortableDateFormat), entry.Id, entry.Status, entry.Size, entry.Sender, len(entry.Recipients), entry.Recipients[0], entry.Reason) + if writeError != nil { + // A writeError is expected once the reader was closed + break + } + } + tabWriter.Flush() + } +} + +func (queue MailQ) PrintJSON(writer io.Writer) { + bytes, err := json.Marshal(queue) + if err != nil { + fmt.Fprintf(writer, "Error encoding queue to JSON: %s\n", err.Error()) + } else { + fmt.Fprintf(writer, "%s", bytes) + } +} diff --git a/mailq/structure.go b/mailq/structure.go new file mode 100644 index 0000000..d3e6fc6 --- /dev/null +++ b/mailq/structure.go @@ -0,0 +1,23 @@ +package mailq + +import ( + "time" +) + +type QEntry struct { + Id string + Status string + Date time.Time + Size int + Sender string + Recipients []string + Reason string +} + +type MailQ struct { + NumTotal int + NumActive int + NumHold int + NumDeferred int + Entries []QEntry +}