diff options
| -rw-r--r-- | cmd/lulu/cli.go | 2 | ||||
| -rw-r--r-- | cmd/lulu/cost.go | 33 | ||||
| -rw-r--r-- | cmd/lulu/indent_tab_writer.go | 58 | ||||
| -rw-r--r-- | cmd/lulu/job.go | 125 | ||||
| -rwxr-xr-x | cmd/lulu/test | 8 | ||||
| -rw-r--r-- | cmd/lulu/testchecks/cost | 5 | ||||
| -rw-r--r-- | cmd/lulu/testchecks/job | 27 | ||||
| -rw-r--r-- | cmd/lulu/testchecks/vi | 6 | ||||
| -rw-r--r-- | cmd/lulu/testchecks/vi_basic | 6 | ||||
| -rw-r--r-- | cmd/lulu/tests/job | 3 | ||||
| -rw-r--r-- | go.mod | 1 | ||||
| -rw-r--r-- | go.sum | 2 | ||||
| -rw-r--r-- | lulu.go | 10 |
13 files changed, 256 insertions, 30 deletions
diff --git a/cmd/lulu/cli.go b/cmd/lulu/cli.go index deedb92..0ff077a 100644 --- a/cmd/lulu/cli.go +++ b/cmd/lulu/cli.go @@ -27,7 +27,7 @@ type CLI struct { CoverDimensions CoverDimensionsCmd `cmd name:"cd" help:"Calculate cover dimensions"` Cost CostCmd `cmd help:"Calculate the cost of a print job"` Jobs JobsCmd `cmd help:"Retrieve past print jobs"` - //TODO Info JobCmd `cmd help:"Retrieve information about a particular print job"` + Job JobCmd `cmd help:"Retrieve information about a particular print job"` List ListCmd `cmd help:"Print a list of valid argument values"` } diff --git a/cmd/lulu/cost.go b/cmd/lulu/cost.go index 93c3ab4..66e1cb5 100644 --- a/cmd/lulu/cost.go +++ b/cmd/lulu/cost.go @@ -3,6 +3,7 @@ package main import ( "encoding/json" "fmt" + "io" "os" "regexp" "strconv" @@ -41,20 +42,6 @@ func (cmd *CostCmd) Run(cli *kong.Kong, clnt *lulu.Client) error { return err } - w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0) - for _, fee := range cost.Fees { - fmt.Fprintf(w, "fee %s %s:\t%s %s\t\n", fee.Type, fee.Sku, fee.TotalCostExclTax, fee.Currency) - } - for i, item := range cost.LineItemCosts { - fmt.Fprintf(w, "item %d:\t%s %s\t\n", i, item.TotalCostExclTax, cost.Currency) - } - fmt.Fprintf(w, "shipping:\t%s %s\t\n", cost.ShipCost.TotalCostExclTax, cost.Currency) - fmt.Fprintf(w, "fulfillment:\t%s %s\t\n", cost.FulfillmentCost.TotalCostExclTax, cost.Currency) - fmt.Fprintf(w, "discount:\t%s %s\t\n", cost.TotalDiscount, cost.Currency) - fmt.Fprintf(w, "tax:\t%s %s\t\n", cost.TotalTax, cost.Currency) - fmt.Fprintf(w, "total:\t%s %s\t\n", cost.TotalCostInclTax, cost.Currency) - w.Flush() - if len(addrVal.Warnings) > 0 { j, err := json.Marshal(addrVal) if err != nil { @@ -63,7 +50,9 @@ func (cmd *CostCmd) Run(cli *kong.Kong, clnt *lulu.Client) error { cli.Errorf("%s\n", j) } - return nil + w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0) + printCost(w, cost) + return w.Flush() } type PrintJobCostLineItem struct { @@ -95,3 +84,17 @@ func (item *PrintJobCostLineItem) UnmarshalText(text []byte) error { return nil } + +func printCost(w io.Writer, cost lulu.PrintJobCost) { + for _, fee := range cost.Fees { + fmt.Fprintf(w, "fee %s %s:\t%s %s\t\n", fee.Type, fee.Sku, fee.TotalCostExclTax, fee.Currency) + } + for i, item := range cost.LineItemCosts { + fmt.Fprintf(w, "item-%d:\t%s %s\t\n", i, item.TotalCostExclTax, cost.Currency) + } + fmt.Fprintf(w, "shipping:\t%s %s\t\n", cost.ShipCost.TotalCostExclTax, cost.Currency) + fmt.Fprintf(w, "fulfillment:\t%s %s\t\n", cost.FulfillmentCost.TotalCostExclTax, cost.Currency) + fmt.Fprintf(w, "discount:\t%s %s\t\n", cost.TotalDiscount, cost.Currency) + fmt.Fprintf(w, "tax:\t%s %s\t\n", cost.TotalTax, cost.Currency) + fmt.Fprintf(w, "total:\t%s %s\t\n", cost.TotalCostInclTax, cost.Currency) +} diff --git a/cmd/lulu/indent_tab_writer.go b/cmd/lulu/indent_tab_writer.go new file mode 100644 index 0000000..102cb33 --- /dev/null +++ b/cmd/lulu/indent_tab_writer.go @@ -0,0 +1,58 @@ +package main + +import ( + "fmt" + "io" + "text/tabwriter" + + "github.com/shurcooL/go/indentwriter" +) + +type indentTabWriter struct { + w io.Writer + tabwFlags uint + tabw *tabwriter.Writer + indent int +} + +func newIndentTabWriter(w io.Writer, tabwriterFlags uint) *indentTabWriter { + return &indentTabWriter{ + w, + tabwriterFlags, + tabwriter.NewWriter(w, 0, 0, 1, ' ', tabwriterFlags), + 0, + } +} + +func (w *indentTabWriter) Write(p []byte) (n int, err error) { return w.tabw.Write(p) } + +func (w *indentTabWriter) Flush() error { + return w.tabw.Flush() +} + +func (w *indentTabWriter) Indent() error { + if err := w.tabw.Flush(); err != nil { + return err + } + w.indent++ + w.reset() + return nil +} + +func (w *indentTabWriter) Unindent() error { + if err := w.tabw.Flush(); err != nil { + return err + } + if w.indent <= 0 { + return fmt.Errorf("cannot unindent: already at depth %d\n", w.indent) + } + w.indent-- + w.reset() + return nil +} + +func (w *indentTabWriter) reset() { + w.tabw = tabwriter.NewWriter( + indentwriter.New(w.w, w.indent), + 0, 0, 1, ' ', w.tabwFlags) +} diff --git a/cmd/lulu/job.go b/cmd/lulu/job.go new file mode 100644 index 0000000..49948f0 --- /dev/null +++ b/cmd/lulu/job.go @@ -0,0 +1,125 @@ +package main + +import ( + "fmt" + "io" + "os" + "time" + + "git.samanthony.xyz/lulu" +) + +type JobCmd struct { + Id uint64 `arg help:"ID of the job to retrieve"` +} + +func (cmd JobCmd) Run(clnt *lulu.Client) error { + job, err := clnt.GetPrintJob(cmd.Id) + if err != nil { + return err + } + + w := newIndentTabWriter(os.Stdout, 0) + fmtTime := func(t time.Time) string { return t.UTC().Format(time.RFC3339) } + fmt.Fprintf(w, "extid:\t%s\t\n", job.ExternalId) + fmt.Fprintf(w, "id:\t%d\t\n", job.Id) + fmt.Fprintf(w, "orderid:\t%s\t\n", job.OrderId) + fmt.Fprintf(w, "contact:\t%s\t\n", job.Contact) + fmt.Fprintf(w, "created:\t%s\t\n", fmtTime(job.Created)) + fmt.Fprintf(w, "modified:\t%s\t\n", fmtTime(job.Modified)) + + fmt.Fprintf(w, "items:\t\n") + if err := w.Indent(); err != nil { + return err + } + for _, item := range job.LineItems { + fmt.Fprintf(w, "extid:\t%s\t\n", item.ExternalId) + fmt.Fprintf(w, "id:\t%d\t\n", item.Id) + fmt.Fprintf(w, "printable-id:\t%s\t\n", item.PrintableId) + fmt.Fprintf(w, "mfg:\t%s\t\n", item.Mfg) + fmt.Fprintf(w, "quantity:\t%d\t\n", item.Quantity) + fmt.Fprintf(w, "npages:\t%d\t\n", item.NPages) + fmt.Fprintf(w, "title:\t%s\t\n", item.Title) + if !isEmptyNormalization(item.PrintableNormalization.Interior) { + fmt.Fprintf(w, "interior-normalization:\t\n") + if err := w.Indent(); err != nil { + return err + } + printNormalization(w, item.PrintableNormalization.Interior) + if err := w.Unindent(); err != nil { + return err + } + } + if !isEmptyNormalization(item.PrintableNormalization.Cover) { + fmt.Fprintf(w, "cover-normalization:\t\n") + if err := w.Indent(); err != nil { + return err + } + printNormalization(w, item.PrintableNormalization.Cover) + if err := w.Unindent(); err != nil { + return err + } + } + if item.TrackingId != "" { + fmt.Fprintf(w, "tracking-id:\t%s\t\n", item.TrackingId) + } + if len(item.TrackingUrls) == 1 { + fmt.Fprintf(w, "tracking-url:\t%s\t\n", item.TrackingUrls[0]) + } else if len(item.TrackingUrls) > 1 { + for i, url := range item.TrackingUrls { + fmt.Fprintf(w, "tracking-url-%d:\t%s\t\n", i, url) + } + } + if item.Carrier != "" { + fmt.Fprintf(w, "carrier:\t%s\t\n", item.Carrier) + } + } + if err := w.Unindent(); err != nil { + return err + } + + fmt.Fprintf(w, "cost:\t\n") + if err := w.Indent(); err != nil { + return err + } + printCost(w, job.Cost) + if err := w.Unindent(); err != nil { + return err + } + + fmt.Fprintf(w, "production-delay:\t%s\t\n", job.ProductionDelay) + if !job.ProductionDue.IsZero() { + fmt.Fprintf(w, "production-due:\t%s\t\n", fmtTime(job.ProductionDue)) + } + fmt.Fprintf(w, "shipping-level:\t%s\t\n", job.ShipOpt) + if job.TaxCountry != "" { + fmt.Fprintf(w, "tax-country:\t%s\t\n", job.TaxCountry) + } + fmt.Fprintf(w, "status:\t%s\t\n", job.Status.Status) + fmt.Fprintf(w, "status-msg:\t%s\t\n", job.Status.Msg) + fmt.Fprintf(w, "status-changed:\t%s\t\n", fmtTime(job.Status.Changed)) + + return w.Flush() +} + +func printNormalization(w io.Writer, n lulu.NormalizationJob) { + if n.JobId != 0 { + fmt.Fprintf(w, "jobid:\t%d\t\n", n.JobId) + } + if n.NormalizedFile.Id != 0 { + fmt.Fprintf(w, "normalized-file-id:\t%d\t\n", n.NormalizedFile.Id) + } + if n.NormalizedFile.Name != "" { + fmt.Fprintf(w, "normalized-file-name:\t%s\t\n", n.NormalizedFile.Name) + } + if n.SrcMd5Sum != "" { + fmt.Fprintf(w, "src-md5:\t%s\t\n", n.SrcMd5Sum) + } + if n.SrcUrl != "" { + fmt.Fprintf(w, "src-url:\t%s\t\n", n.SrcUrl) + } +} + +func isEmptyNormalization(n lulu.NormalizationJob) bool { + return n.JobId == 0 && n.NormalizedFile.Id == 0 && n.NormalizedFile.Name == "" && n.SrcMd5Sum == "" && n.SrcUrl == "" +} diff --git a/cmd/lulu/test b/cmd/lulu/test index e446356..ee6b18d 100755 --- a/cmd/lulu/test +++ b/cmd/lulu/test @@ -2,7 +2,13 @@ set -u -flags="-e -u" +flags='-e -u' + +export duration='([0-9]+h)?([0-9]+m)?[0-9]+s' +export email='.+@.+\..+' +export money='[0-9]+(\.[0-9]+)? [A-Z]{3}' +export pkgid='[0-9]{4}X[0-9]{4}\.[A-Z]{2}\.[A-Z]{3}\.[A-Z]{2}\.[A-Z0-9]{5,8}\.[A-Z]{3}' +export time='[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z' [[ ! -d testout ]] && mkdir testout [[ ! -d testerr ]] && mkdir testerr diff --git a/cmd/lulu/testchecks/cost b/cmd/lulu/testchecks/cost index 479bdf4..488d6f4 100644 --- a/cmd/lulu/testchecks/cost +++ b/cmd/lulu/testchecks/cost @@ -1,11 +1,10 @@ -money='[0-9]+(\.[0-9]+)? [A-Z]{3}' -for field in "item 0" "item 1" "shipping" "fulfillment" "discount" "tax" "total" +for field in "item-0" "item-1" "shipping" "fulfillment" "discount" "tax" "total" do re="$(printf '^%s: +%s *$' "${field}" "${money}")" echo "$re" grep -E "$re" $1 done -grep -E -v '^item [^01]' $1 +grep -E -v '^item-[^01]' $1 awk ' # Ensure sum of fields equals total. !/^total/ { wantTotal += $(NF-1) } diff --git a/cmd/lulu/testchecks/job b/cmd/lulu/testchecks/job new file mode 100644 index 0000000..80d6cf9 --- /dev/null +++ b/cmd/lulu/testchecks/job @@ -0,0 +1,27 @@ +grep -E '^extid: +[0-9a-zA-Z_-]+' $1 +grep -E '^id: +[0-9]+' $1 +grep -E '^orderid: +[0-9]+' $1 +grep -E "$(printf '^contact: +%s' "$email")" $1 +grep -E "$(printf '^created: +%s' "$time")" $1 +grep -E "$(printf '^modified: +%s' "$time")" $1 + +grep -E '^items: *$' $1 +grep -E '^ extid: +[0-9a-zA-Z_-]+' $1 +grep -E '^ id: +[0-9]+' $1 +grep -E '^ printable-id: +[0-9a-z-]+' $1 +grep -E "$(printf '^ mfg: +%s' "$pkgid")" $1 +grep -E '^ quantity: +[0-9]+' $1 +grep -E '^ npages: +[0-9]+' $1 +grep -E '^ title: +.+' $1 + +grep -E '^cost: *$' $1 +for field in "item-0" "shipping" "fulfillment" "discount" "tax" "total" +do + grep -E "$(printf '^ %s: +%s *$' "${field}" "${money}")" $1 +done + +grep -E "$(printf '^production-delay: +%s' "$duration")" $1 +grep -E '^shipping-level: +[A-Z_]+' $1 +grep -E '^status: +[A-Z_]+' $1 +grep -E '^status-msg: *.*' $1 +grep -E "$(printf '^status-changed: +%s' "$time")" $1 diff --git a/cmd/lulu/testchecks/vi b/cmd/lulu/testchecks/vi index 19de67c..d3b3ebc 100644 --- a/cmd/lulu/testchecks/vi +++ b/cmd/lulu/testchecks/vi @@ -1,3 +1,3 @@ -grep '^status: NORMALIZED$' $1 -grep '^id: [1-9][0-9]*$' $1 -grep '^valid PkgIds: \[[A-Z0-9. ]\+\]$' $1 +grep -E '^status: NORMALIZED$' $1 +grep -E '^id: [1-9][0-9]*$' $1 +grep -E "$(printf '^valid PkgIds: \[(%s *)+\]$' "$pkgid")" $1 diff --git a/cmd/lulu/testchecks/vi_basic b/cmd/lulu/testchecks/vi_basic index 50acc33..48aca9b 100644 --- a/cmd/lulu/testchecks/vi_basic +++ b/cmd/lulu/testchecks/vi_basic @@ -1,3 +1,3 @@ -grep '^status: VALIDATED$' $1 -grep '^id: [1-9][0-9]*$' $1 -grep '^valid PkgIds: \[[A-Z0-9. ]\+\]$' $1 +grep -E '^status: VALIDATED$' $1 +grep -E '^id: [1-9][0-9]*$' $1 +grep -E "$(printf '^valid PkgIds: \[(%s *)+\]$' "$pkgid")" $1 diff --git a/cmd/lulu/tests/job b/cmd/lulu/tests/job new file mode 100644 index 0000000..ac39591 --- /dev/null +++ b/cmd/lulu/tests/job @@ -0,0 +1,3 @@ +# Get info on the most recent job. +lulu -s jobs | tail -n1 | awk '{id=$3; print id}' \ + | xargs lulu -s job @@ -15,6 +15,7 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/sam-rba/string-enumer v0.0.0-20260516150728-84dfedf65d7e // indirect + github.com/shurcooL/go v0.0.0-20230706063926-5fe729b41b3a // indirect github.com/spf13/pflag v1.0.6 // indirect golang.org/x/mod v0.35.0 // indirect golang.org/x/sync v0.20.0 // indirect @@ -16,6 +16,8 @@ github.com/sam-rba/string-enumer v0.0.0-20260516150728-84dfedf65d7e h1:4iVoDEBWL github.com/sam-rba/string-enumer v0.0.0-20260516150728-84dfedf65d7e/go.mod h1:IqjpIrSTuoqcSL5noZPLd8b6nnliL0nmIdAoREz3Di4= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= +github.com/shurcooL/go v0.0.0-20230706063926-5fe729b41b3a h1:ZHfoO7ZJhws9NU1kzZhStUnnVQiPtDe1PzpUnc6HirU= +github.com/shurcooL/go v0.0.0-20230706063926-5fe729b41b3a/go.mod h1:DNrlr0AR9NsHD/aoc2pPeu4uSBZ/71yCHkR42yrzW3M= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= @@ -86,7 +86,7 @@ func (c *Client) ValidateInterior(ctx context.Context, srcUrl string, mfg PkgId) if err != nil { return InteriorValidation{}, err } - return c.pollInteriorValidation(ctx, id) + return c.pollInteriorValidation(ctx, id, InteriorStatusNormalized) } // ValidateInteriorBasic is like ValidateInterior but without the @@ -96,13 +96,15 @@ func (c *Client) ValidateInteriorBasic(ctx context.Context, srcUrl string) (Inte if err != nil { return InteriorValidation{}, err } - return c.pollInteriorValidation(ctx, id) + return c.pollInteriorValidation(ctx, id, InteriorStatusValidated) } -func (c *Client) pollInteriorValidation(ctx context.Context, id uint) (InteriorValidation, error) { +func (c *Client) pollInteriorValidation(ctx context.Context, id uint, wantStatus InteriorValidationStatus) (InteriorValidation, error) { return poll(ctx, func() (InteriorValidation, bool, error) { val, err := c.GetInteriorValidation(id) - return val, val.Status.IsFinal(), err + done := val.Status == wantStatus || + val.Status == InteriorStatusError + return val, done, err }) } |