f: trying to find where the pieces go

This commit is contained in:
AJ ONeal 2026-01-25 01:40:02 -07:00
parent fd918575bf
commit 592bbffd6d
No known key found for this signature in database
4 changed files with 81 additions and 51 deletions

View File

@ -14,6 +14,7 @@ import (
"github.com/joho/godotenv" "github.com/joho/godotenv"
"github.com/therootcompany/golib/net/smsgw" "github.com/therootcompany/golib/net/smsgw"
"github.com/therootcompany/golib/net/smsgw/androidsmsgateway" "github.com/therootcompany/golib/net/smsgw/androidsmsgateway"
"github.com/therootcompany/golib/net/smsgw/smscsv"
) )
type MainConfig struct { type MainConfig struct {
@ -122,7 +123,7 @@ func main() {
csvr := csv.NewReader(file) csvr := csv.NewReader(file)
csvr.FieldsPerRecord = -1 csvr.FieldsPerRecord = -1
messages, warns, err := cfg.LaxParseCSV(csvr) messages, warns, err := smscsv.ReadOrIgnoreAll(csvr)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "\n%sError%s: %v\n", textErr, textReset, err) fmt.Fprintf(os.Stderr, "\n%sError%s: %v\n", textErr, textReset, err)
os.Exit(1) os.Exit(1)

View File

@ -1,22 +1,15 @@
package main package smscsv
import ( import (
"encoding/csv"
"fmt" "fmt"
"io" "io"
"slices" "slices"
"strings" "strings"
"github.com/therootcompany/golib/net/smsgw"
) )
func GetFieldIndex(header []string, name string) int { type Reader interface {
for i, h := range header { Read() ([]string, error)
if strings.EqualFold(strings.TrimSpace(h), name) { // ReadAll() ([][]string, error)
return i
}
}
return -1
} }
type CSVWarn struct { type CSVWarn struct {
@ -30,7 +23,36 @@ func (w CSVWarn) Error() string {
return w.Message return w.Message
} }
func (cfg *MainConfig) LaxParseCSV(csvr *csv.Reader) (messages []smsgw.Message, warns []CSVWarn, err error) { type Message struct {
header []string
indices map[string]int
fields []string
Name string
Number string
Template string
Vars map[string]string
Text string
}
func (m Message) Size() int {
return len(m.fields)
}
func (m Message) Get(key string) string {
index, ok := m.indices[key]
if !ok {
return ""
}
if len(m.fields) >= 1+index {
return m.fields[index]
}
return ""
}
// TODO XXX AJ pass in column name mapping
func ReadOrIgnoreAll(csvr Reader) (messages []Message, warns []CSVWarn, err error) {
header, err := csvr.Read() header, err := csvr.Read()
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("header could not be parsed: %w", err) return nil, nil, fmt.Errorf("header could not be parsed: %w", err)
@ -55,16 +77,8 @@ func (cfg *MainConfig) LaxParseCSV(csvr *csv.Reader) (messages []smsgw.Message,
return nil, nil, fmt.Errorf("failed to parse row %d (and all following rows): %w", rowIndex, err) return nil, nil, fmt.Errorf("failed to parse row %d (and all following rows): %w", rowIndex, err)
} }
if len(rec) < FIELD_MIN { // TODO XXX AJ create an abstraction around the header []string and the record []string
warns = append(warns, CSVWarn{ // the idea is to return the same thing for valid and invalid rows
Index: rowIndex,
Code: "TooFewFields",
Message: fmt.Sprintf("ignoring row %d: too few fields (want %d, have %d)", rowIndex, FIELD_MIN, len(rec)),
Record: rec,
})
continue
}
vars := make(map[string]string) vars := make(map[string]string)
n := min(len(header), len(rec)) n := min(len(header), len(rec))
for i := range n { for i := range n {
@ -78,7 +92,17 @@ func (cfg *MainConfig) LaxParseCSV(csvr *csv.Reader) (messages []smsgw.Message,
} }
} }
message := smsgw.Message{ if len(rec) < FIELD_MIN {
warns = append(warns, CSVWarn{
Index: rowIndex,
Code: "TooFewFields",
Message: fmt.Sprintf("ignoring row %d: too few fields (want %d, have %d)", rowIndex, FIELD_MIN, len(rec)),
Record: rec,
})
continue
}
message := Message{
// Index: rowIndex, // Index: rowIndex,
Name: strings.TrimSpace(rec[FIELD_NAME]), Name: strings.TrimSpace(rec[FIELD_NAME]),
Number: strings.TrimSpace(rec[FIELD_PHONE]), Number: strings.TrimSpace(rec[FIELD_PHONE]),
@ -86,20 +110,17 @@ func (cfg *MainConfig) LaxParseCSV(csvr *csv.Reader) (messages []smsgw.Message,
Vars: vars, Vars: vars,
} }
message.Number = smsgw.StripFormatting(message.Number)
message.Number, err = smsgw.PrefixUS10Digit(message.Number)
if err != nil {
warns = append(warns, CSVWarn{
Index: rowIndex,
Code: "PhoneInvalid",
Message: fmt.Sprintf("ignoring row %d (%s): %s", rowIndex, message.Name, err.Error()),
Record: rec,
})
continue
}
messages = append(messages, message) messages = append(messages, message)
} }
return messages, warns, nil return messages, warns, nil
} }
func GetFieldIndex(header []string, name string) int {
for i, h := range header {
if strings.EqualFold(strings.TrimSpace(h), name) {
return i
}
}
return -1
}

View File

@ -5,19 +5,6 @@ import (
"strings" "strings"
) )
type Gateway interface {
CurlString(to, text string) string
Send(to, text string) error
}
type Message struct {
Name string
Number string
Template string
Vars map[string]string
Text string
}
var ErrInvalidClockFormat = fmt.Errorf("invalid clock time, ex: '06:00 PM', '6pm', or '18:00' (space and case insensitive)") var ErrInvalidClockFormat = fmt.Errorf("invalid clock time, ex: '06:00 PM', '6pm', or '18:00' (space and case insensitive)")
var ErrInvalidClockTime = fmt.Errorf("invalid hour or minute, for example '27:63 p' would not be valid") var ErrInvalidClockTime = fmt.Errorf("invalid hour or minute, for example '27:63 p' would not be valid")
var ErrPhoneEmpty = fmt.Errorf("no phone number") var ErrPhoneEmpty = fmt.Errorf("no phone number")
@ -25,6 +12,11 @@ var ErrPhoneInvalid11 = fmt.Errorf("invalid 11-digit number (does not start with
var ErrPhoneInvalid12 = fmt.Errorf("invalid 12-digit number (does not start with +1)") var ErrPhoneInvalid12 = fmt.Errorf("invalid 12-digit number (does not start with +1)")
var ErrPhoneInvalidLength = fmt.Errorf("invalid number length (should be 10 digits or 12 with +1 prefix)") var ErrPhoneInvalidLength = fmt.Errorf("invalid number length (should be 10 digits or 12 with +1 prefix)")
type Gateway interface {
CurlString(to, text string) string
Send(to, text string) error
}
// Strips away symbols, non-printing characters copied from HTML, etc, // Strips away symbols, non-printing characters copied from HTML, etc,
// leaving only a possible leading '+' and digits. // leaving only a possible leading '+' and digits.
// Does not leave *, # or comma. // Does not leave *, # or comma.

View File

@ -8,11 +8,15 @@ import (
"strings" "strings"
"github.com/therootcompany/golib/net/smsgw" "github.com/therootcompany/golib/net/smsgw"
"github.com/therootcompany/golib/net/smsgw/smscsv"
) )
var reUnmatchedVars = regexp.MustCompile(`(\{[^}]+\})`) var reUnmatchedVars = regexp.MustCompile(`(\{[^}]+\})`)
func RenderMessages(messages []smsgw.Message) ([]smsgw.Message, error) { func RenderAll(messages []smscsv.Message) ([]smscsv.Message, error) {
var err error
var warns []smscsv.CSVWarn
for i, message := range messages { for i, message := range messages {
rowIndex := i + 1 rowIndex := i + 1
@ -24,8 +28,20 @@ func RenderMessages(messages []smsgw.Message) ([]smsgw.Message, error) {
message.Text = ReplaceVar(message.Text, key, val) message.Text = ReplaceVar(message.Text, key, val)
} }
message.Number = smsgw.StripFormatting(message.Number)
message.Number, err = smsgw.PrefixUS10Digit(message.Number)
if err != nil {
warns = append(warns, smscsv.CSVWarn{
Index: rowIndex,
Code: "PhoneInvalid",
Message: fmt.Sprintf("ignoring row %d (%s): %s", rowIndex, message.Name, err.Error()),
// Record: rec,
})
continue
}
if tmpls := reUnmatchedVars.FindAllString(message.Text, -1); len(tmpls) != 0 { if tmpls := reUnmatchedVars.FindAllString(message.Text, -1); len(tmpls) != 0 {
return nil, &CSVWarn{ return nil, &smscsv.CSVWarn{
Index: rowIndex, Index: rowIndex,
Code: "UnmatchedVars", Code: "UnmatchedVars",
Message: fmt.Sprintf( Message: fmt.Sprintf(