mirror of
https://github.com/therootcompany/golib.git
synced 2026-01-27 23:18:05 +00:00
ref(sendsms): hard error on leftover template strings, other output improvements
This commit is contained in:
parent
0c3c436c60
commit
a050e5d0c7
@ -74,6 +74,7 @@ const (
|
|||||||
fgRed = "\033[31m"
|
fgRed = "\033[31m"
|
||||||
textErr = textBold + fgRed
|
textErr = textBold + fgRed
|
||||||
textWarn = textBold + fgYellow
|
textWarn = textBold + fgYellow
|
||||||
|
textInfo = fgYellow
|
||||||
textPrompt = fgBlue
|
textPrompt = fgBlue
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -115,17 +116,17 @@ func main() {
|
|||||||
{
|
{
|
||||||
cfg.startTime, err = parseClock(cfg.startClock, now)
|
cfg.startTime, err = parseClock(cfg.startClock, now)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "%sError%s: could not use --start-time %q: %v\n", textErr, textReset, cfg.startClock, err)
|
fmt.Fprintf(os.Stderr, "\n%sError%s: could not use --start-time %q: %v\n", textErr, textReset, cfg.startClock, err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
cfg.endTime, err = parseClock(cfg.endClock, now)
|
cfg.endTime, err = parseClock(cfg.endClock, now)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "%sError%s: could not use --end-time %q: %v\n", textErr, textReset, cfg.endClock, err)
|
fmt.Fprintf(os.Stderr, "\n%sError%s: could not use --end-time %q: %v\n", textErr, textReset, cfg.endClock, err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
if cfg.startTime.After(cfg.endTime) || cfg.startTime.Equal(cfg.endTime) {
|
if cfg.startTime.After(cfg.endTime) || cfg.startTime.Equal(cfg.endTime) {
|
||||||
fmt.Fprintf(os.Stderr,
|
fmt.Fprintf(os.Stderr,
|
||||||
"%sError%s: no time between --start-time %q and --end-time %q\n", textErr, textReset, cfg.startTime, cfg.endTime)
|
"\n%sError%s: no time between --start-time %q and --end-time %q\n", textErr, textReset, cfg.startTime, cfg.endTime)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -140,7 +141,7 @@ func main() {
|
|||||||
fmt.Fprintf(os.Stderr, "Info: opening, reading, and parsing %q\n", cfg.csvPath)
|
fmt.Fprintf(os.Stderr, "Info: opening, reading, and parsing %q\n", cfg.csvPath)
|
||||||
file, err := os.Open(cfg.csvPath)
|
file, err := os.Open(cfg.csvPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "%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)
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
@ -151,7 +152,7 @@ func main() {
|
|||||||
|
|
||||||
messages, warns, err := cfg.LaxParseCSV(csvr)
|
messages, warns, err := cfg.LaxParseCSV(csvr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "%v\n", err)
|
fmt.Fprintf(os.Stderr, "\n%sError%s: %v\n", textErr, textReset, err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
if len(warns) > 0 {
|
if len(warns) > 0 {
|
||||||
@ -161,14 +162,13 @@ func main() {
|
|||||||
fmt.Fprintf(os.Stderr, " (pass --verbose to show warnings)\n")
|
fmt.Fprintf(os.Stderr, " (pass --verbose to show warnings)\n")
|
||||||
}
|
}
|
||||||
if cfg.verbose {
|
if cfg.verbose {
|
||||||
fmt.Fprintf(os.Stderr, "\n")
|
|
||||||
for _, warn := range warns {
|
for _, warn := range warns {
|
||||||
fmt.Fprintf(os.Stderr, "Skip: %s\n", warn.Message)
|
fmt.Fprintf(os.Stderr, " Skip: %s\n", warn.Message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fmt.Fprintf(os.Stderr, "\n")
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
}
|
}
|
||||||
fmt.Fprintf(os.Stderr, "Info: messages to send: %d\n", len(messages))
|
fmt.Fprintf(os.Stderr, "Info: list of %d messages\n", len(messages))
|
||||||
|
|
||||||
if now.After(cfg.endTime) || now.Equal(cfg.endTime) {
|
if now.After(cfg.endTime) || now.Equal(cfg.endTime) {
|
||||||
fmt.Fprintf(os.Stderr, "%sWarning%s: Too late now. %sWaiting until tomorrow%s:\n", textWarn, textReset, textWarn, textReset)
|
fmt.Fprintf(os.Stderr, "%sWarning%s: Too late now. %sWaiting until tomorrow%s:\n", textWarn, textReset, textWarn, textReset)
|
||||||
@ -179,7 +179,7 @@ func main() {
|
|||||||
// check for issues caused by daylight savings
|
// check for issues caused by daylight savings
|
||||||
if cfg.startTime.After(cfg.endTime) || cfg.startTime.Equal(cfg.endTime) {
|
if cfg.startTime.After(cfg.endTime) || cfg.startTime.Equal(cfg.endTime) {
|
||||||
fmt.Fprintf(os.Stderr,
|
fmt.Fprintf(os.Stderr,
|
||||||
"%sError%s: no time between --start-time %q and --end-time %q\n",
|
"\n%sError%s: no time between --start-time %q and --end-time %q\n",
|
||||||
textErr, textReset,
|
textErr, textReset,
|
||||||
cfg.startTime, cfg.endTime,
|
cfg.startTime, cfg.endTime,
|
||||||
)
|
)
|
||||||
@ -207,12 +207,12 @@ func main() {
|
|||||||
}
|
}
|
||||||
var startAgo = now.Sub(cfg.startTime)
|
var startAgo = now.Sub(cfg.startTime)
|
||||||
if startAgo >= 0 {
|
if startAgo >= 0 {
|
||||||
fmt.Fprintf(os.Stderr, "Info: start time was %s (%s ago)\n", cfg.startTime.Format("3:04pm"), startAgo.Round(time.Second))
|
fmt.Fprintf(os.Stderr, "Info: start after %s (%s ago)\n", cfg.startTime.Format("3:04pm"), startAgo.Round(time.Second))
|
||||||
} else {
|
} else {
|
||||||
startAgo *= -1
|
startAgo *= -1
|
||||||
fmt.Fprintf(os.Stderr, "Info: start time is %s (%s from now)\n", cfg.startTime.Format("3:04pm"), startAgo.Round(time.Second))
|
fmt.Fprintf(os.Stderr, "Info: start after %s (%s from now)\n", cfg.startTime.Format("3:04pm"), startAgo.Round(time.Second))
|
||||||
}
|
}
|
||||||
fmt.Fprintf(os.Stderr, "Info: end time is %s (%s from now)\n", cfg.endTime.Format("3:04pm"), cfg.duration.Round(time.Second))
|
fmt.Fprintf(os.Stderr, "Info: end around %s (%s from now)\n", cfg.endTime.Format("3:04pm"), cfg.duration.Round(time.Second))
|
||||||
}
|
}
|
||||||
|
|
||||||
// delay
|
// delay
|
||||||
@ -243,21 +243,25 @@ func main() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintf(os.Stderr, "Info: average delay between messages: %s\n", cfg.delay.Round(time.Second))
|
|
||||||
quarterDelay := cfg.delay / 4
|
quarterDelay := cfg.delay / 4
|
||||||
baseDelay := quarterDelay * 3
|
baseDelay := quarterDelay * 3
|
||||||
jitter := int64(quarterDelay * 2)
|
jitter := int64(quarterDelay * 2)
|
||||||
fmt.Fprintf(os.Stderr,
|
fmt.Fprintf(os.Stderr,
|
||||||
" (%s minimum + %s jitter)\n",
|
"Info: delay %s between messages (%s + %s jitter)\n",
|
||||||
baseDelay.Round(time.Millisecond), time.Duration(jitter).Round(time.Millisecond),
|
cfg.delay.Round(time.Second), baseDelay.Round(time.Millisecond), time.Duration(jitter).Round(time.Millisecond),
|
||||||
)
|
)
|
||||||
|
|
||||||
if len(messages) == 0 {
|
if len(messages) == 0 {
|
||||||
fmt.Fprintf(os.Stderr, "\n")
|
fmt.Fprintf(os.Stderr, "\n%sError%s: no messages to send\n", textErr, textReset)
|
||||||
fmt.Fprintf(os.Stderr, "%sError%s: no messages to send\n", textErr, textReset)
|
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "Info: This is what a %ssample message%s from list look like:\n", textInfo, textReset)
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " To: %s (%s)\n", messages[0].Number, messages[0].Name)
|
||||||
|
fmt.Fprintf(os.Stderr, " %s%s%s\n", textInfo, messages[0].Text, textReset)
|
||||||
|
|
||||||
if !cfg.confirmed && !cfg.dryRun {
|
if !cfg.confirmed && !cfg.dryRun {
|
||||||
fmt.Fprintf(os.Stderr, "\n")
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
if !confirmContinue() {
|
if !confirmContinue() {
|
||||||
@ -283,7 +287,7 @@ func main() {
|
|||||||
last := len(messages)
|
last := len(messages)
|
||||||
left := last - cur
|
left := last - cur
|
||||||
if left > 0 {
|
if left > 0 {
|
||||||
fmt.Fprintf(os.Stderr, "%sError%s: Oh, look at the time. Ending now. (%d messages remaining)\n", textErr, textReset, left)
|
fmt.Fprintf(os.Stderr, "\n%sError%s: Oh, look at the time. Ending now. (%d messages remaining)\n", textErr, textReset, left)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -315,7 +319,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintf(os.Stderr, "finished at %s", time.Now())
|
fmt.Fprintf(os.Stderr, "Info: finished at %s\n", time.Now())
|
||||||
}
|
}
|
||||||
|
|
||||||
func confirmContinue() bool {
|
func confirmContinue() bool {
|
||||||
@ -395,7 +399,11 @@ type CSVWarn struct {
|
|||||||
Record []string
|
Record []string
|
||||||
}
|
}
|
||||||
|
|
||||||
var reUnmatchedVars = regexp.MustCompile(`\{[^}]+\}`)
|
func (w CSVWarn) Error() string {
|
||||||
|
return w.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
var reUnmatchedVars = regexp.MustCompile(`(\{[^}]+\})`)
|
||||||
|
|
||||||
func (cfg *MainConfig) LaxParseCSV(csvr *csv.Reader) (messages []SMSMessage, warns []CSVWarn, err error) {
|
func (cfg *MainConfig) LaxParseCSV(csvr *csv.Reader) (messages []SMSMessage, warns []CSVWarn, err error) {
|
||||||
header, err := csvr.Read()
|
header, err := csvr.Read()
|
||||||
@ -407,7 +415,7 @@ func (cfg *MainConfig) LaxParseCSV(csvr *csv.Reader) (messages []SMSMessage, war
|
|||||||
FIELD_PHONE := GetFieldIndex(header, "Phone")
|
FIELD_PHONE := GetFieldIndex(header, "Phone")
|
||||||
FIELD_MESSAGE := GetFieldIndex(header, "Message")
|
FIELD_MESSAGE := GetFieldIndex(header, "Message")
|
||||||
if FIELD_NAME == -1 || FIELD_PHONE == -1 || FIELD_MESSAGE == -1 {
|
if FIELD_NAME == -1 || FIELD_PHONE == -1 || FIELD_MESSAGE == -1 {
|
||||||
return nil, nil, fmt.Errorf("header is missing one or more of 'Preferred', 'Phone', and/or 'Message'")
|
return nil, nil, fmt.Errorf("header is missing one or more of 'Name', 'Phone', and/or 'Message'")
|
||||||
}
|
}
|
||||||
FIELD_MIN := 1 + slices.Max([]int{FIELD_NAME, FIELD_PHONE, FIELD_MESSAGE})
|
FIELD_MIN := 1 + slices.Max([]int{FIELD_NAME, FIELD_PHONE, FIELD_MESSAGE})
|
||||||
|
|
||||||
@ -452,6 +460,7 @@ func (cfg *MainConfig) LaxParseCSV(csvr *csv.Reader) (messages []SMSMessage, war
|
|||||||
Vars: vars,
|
Vars: vars,
|
||||||
Text: strings.TrimSpace(rec[FIELD_MESSAGE]),
|
Text: strings.TrimSpace(rec[FIELD_MESSAGE]),
|
||||||
}
|
}
|
||||||
|
message.Text = replaceVar(message.Template, "Name", message.Name)
|
||||||
|
|
||||||
keyIter := maps.Keys(message.Vars)
|
keyIter := maps.Keys(message.Vars)
|
||||||
keys := slices.Sorted(keyIter)
|
keys := slices.Sorted(keyIter)
|
||||||
@ -460,14 +469,16 @@ func (cfg *MainConfig) LaxParseCSV(csvr *csv.Reader) (messages []SMSMessage, war
|
|||||||
message.Text = replaceVar(message.Text, key, val)
|
message.Text = replaceVar(message.Text, key, val)
|
||||||
}
|
}
|
||||||
|
|
||||||
if reUnmatchedVars.MatchString(message.Text) {
|
if tmpls := reUnmatchedVars.FindAllString(message.Text, -1); len(tmpls) != 0 {
|
||||||
warns = append(warns, CSVWarn{
|
return nil, nil, &CSVWarn{
|
||||||
Index: rowIndex,
|
Index: rowIndex,
|
||||||
Code: "UnmatchedVars",
|
Code: "UnmatchedVars",
|
||||||
Message: fmt.Sprintf("ignoring row %d: leftover template variables (e.g. {VarName})", rowIndex),
|
Message: fmt.Sprintf(
|
||||||
|
"failing due to row %d (%s): leftover template variable(s): %s",
|
||||||
|
rowIndex, message.Name, strings.Join(tmpls, " "),
|
||||||
|
),
|
||||||
Record: rec,
|
Record: rec,
|
||||||
})
|
}
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message.Number = cleanPhoneNumber(message.Number)
|
message.Number = cleanPhoneNumber(message.Number)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user