diff options
| author | Sam Anthony <sam@samanthony.xyz> | 2026-05-14 12:22:24 -0400 |
|---|---|---|
| committer | Sam Anthony <sam@samanthony.xyz> | 2026-05-14 12:22:24 -0400 |
| commit | b2fc5a8c62b71d52d167e992c42fa6630cfca9b3 (patch) | |
| tree | 068fae0697657f961727866ee94e8f7d4333115c | |
| parent | 71fc4ebe34a18de12d15cbdbaa09fb857b0e0229 (diff) | |
| download | lulu-b2fc5a8c62b71d52d167e992c42fa6630cfca9b3.zip | |
cli: cover dimensions
| -rw-r--r-- | cmd/lulu/flag.go | 86 | ||||
| -rw-r--r-- | cmd/lulu/main.go | 117 | ||||
| -rw-r--r-- | cmd/lulu/validate_cover.go | 44 | ||||
| -rw-r--r-- | cmd/lulu/validate_interior.go | 63 | ||||
| -rw-r--r-- | cover.go | 4 |
5 files changed, 178 insertions, 136 deletions
diff --git a/cmd/lulu/flag.go b/cmd/lulu/flag.go index a981886..0103dfc 100644 --- a/cmd/lulu/flag.go +++ b/cmd/lulu/flag.go @@ -11,12 +11,21 @@ import ( "unicode" ) +func nPagesFlag(nPages *uint) Flag { + return Flag{&uintValue{p: nPages}, "n", "Number of interior pages"} +} + type Flag struct { - val flag.Value + val FlagValue name string usage string } +type FlagValue interface { + flag.Value + IsSet() bool +} + func (f Flag) Synopsis() string { s := fmt.Sprintf("-%s", f.name) if bf, ok := f.val.(boolFlag); !ok || !bf.IsBoolFlag() { @@ -30,7 +39,7 @@ func (f Flag) AddTo(fs *flag.FlagSet) { } func (f Flag) IsSet() bool { - return f.val.String() != "" + return f.val.IsSet() } func (f Flag) MustBeSet(fs *flag.FlagSet) { @@ -46,94 +55,113 @@ type boolFlag interface { // boolValue implements flag.Value. type boolValue struct { - p *bool + p *bool + isSet bool } -func (bv boolValue) String() string { - if bv.p != nil { - return fmt.Sprint(*bv.p) +func (v boolValue) String() string { + if v.p != nil { + return fmt.Sprint(*v.p) } return "" } -func (bv boolValue) Set(s string) error { +func (v *boolValue) Set(s string) error { b, err := strconv.ParseBool(s) if err != nil { return err } - *bv.p = b + *v.p = b + v.isSet = true return nil } -func (bv boolValue) IsBoolFlag() bool { return true } +func (v boolValue) IsSet() bool { return v.isSet } + +func (v boolValue) IsBoolFlag() bool { return true } type uintValue struct { - p *uint + p *uint + isSet bool } -func (uv uintValue) String() string { - if uv.p != nil { - return fmt.Sprint(*uv.p) +func (v uintValue) String() string { + if v.p != nil { + return fmt.Sprint(*v.p) } return "" } -func (uv uintValue) Set(s string) error { - v, err := strconv.ParseUint(s, 10, 32) +func (v *uintValue) Set(s string) error { + u, err := strconv.ParseUint(s, 10, 32) if err != nil { return err } - *uv.p = uint(v) + *v.p = uint(u) + v.isSet = true return nil } +func (v uintValue) IsSet() bool { return v.isSet } + // textValue implements flag.Value for string types that implement // TextUnmarshaler. type textValue[T ~string, PT interface { *T encoding.TextUnmarshaler }] struct { - p PT + p PT + isSet bool } func newTextValue[T ~string, PT interface { *T encoding.TextUnmarshaler -}](p PT) textValue[T, PT] { - return textValue[T, PT]{p} +}](p PT) *textValue[T, PT] { + return &textValue[T, PT]{p: p} } -func (tv textValue[T, PT]) String() string { - if tv.p != nil { - return string(*(*T)(tv.p)) +func (v textValue[T, PT]) String() string { + if v.p != nil { + return string(*(*T)(v.p)) } return "" } -func (tv textValue[T, PT]) Set(s string) error { - return tv.p.UnmarshalText([]byte(s)) +func (v *textValue[T, PT]) Set(s string) error { + if err := v.p.UnmarshalText([]byte(s)); err != nil { + return err + } + v.isSet = true + return nil } +func (v textValue[T, PT]) IsSet() bool { return v.isSet } + type urlValue struct { - *url.URL + p *url.URL + isSet bool } func (v urlValue) String() string { - if v.URL != nil { - return v.URL.String() + if v.p != nil { + return v.p.String() } return "" } -func (v urlValue) Set(s string) error { +func (v *urlValue) Set(s string) error { u, err := url.Parse(s) if err != nil { return err } - *v.URL = *u + *v.p = *u + v.isSet = true return nil } +func (v urlValue) IsSet() bool { return v.isSet } + func mutexFlags(fs *flag.FlagSet, a, b Flag) { if a.IsSet() && b.IsSet() { lg.Printf("lulu: %s: -%s is incompatible with -%s\n", fs.Name(), a.name, b.name) diff --git a/cmd/lulu/main.go b/cmd/lulu/main.go index d86d418..06d5ad4 100644 --- a/cmd/lulu/main.go +++ b/cmd/lulu/main.go @@ -5,6 +5,7 @@ import ( "flag" "fmt" "log" + "net/url" "os" "time" @@ -30,6 +31,10 @@ var commands = []command{ "validate-cover", "vc", validateCover, "Validate a cover file", + }, { + "cover-dimensions", "cd", + coverDimensions, + "Calculate the required dimensions of the cover", }, } @@ -107,3 +112,115 @@ func flagBoolFunc(name, usage string, f func()) { return nil }) } + +func validateInterior(clnt *lulu.Client, args []string) { + var ( + basic bool + url url.URL + mfg = newMfgFlagSet() + timeout time.Duration + ) + + basicFlag := Flag{&boolValue{p: &basic}, "basic", "Basic validation without pod_package_id manufacturing options"} + urlFlag := Flag{&urlValue{p: &url}, "url", "URL of interior file for Lulu to download"} + + fs := flag.NewFlagSet("validate-interior", flag.ExitOnError) + basicFlag.AddTo(fs) + urlFlag.AddTo(fs) + mfg.AddTo(fs) + fs.DurationVar(&timeout, "t", defaultTimeout, "Timeout") + + fs.Usage = func() { + out := fs.Output() + name := "validate-interior" + fmt.Fprintf(out, "Usage of %s:\n", name) + fmt.Fprintf(out, " %s %s %s [flags]\n", name, urlFlag.Synopsis(), mfg.Synopsis()) + fmt.Fprintf(out, " %s %s %s [flags]\n", name, basicFlag.Synopsis(), urlFlag.Synopsis()) + fmt.Fprintf(out, "Flags:\n") + fs.PrintDefaults() + } + + if err := fs.Parse(args); err != nil { + lg.Fatalf("lulu: %v\n", err) + } + + var val lulu.InteriorValidation + var err error + ctx, cancel := context.WithTimeout(clnt.Context(), timeout) + defer cancel() + if basic { + urlFlag.MustBeSet(fs) + for _, f := range mfg.flags { + mutexFlags(fs, f, basicFlag) + } + val, err = clnt.ValidateInteriorBasic(ctx, url.String()) + } else { + urlFlag.MustBeSet(fs) + mfg.MustBeSet(fs) + val, err = clnt.ValidateInterior(ctx, url.String(), mfg.PkgId()) + } + if err != nil { + lg.Fatal(err) + } + fmt.Println(val) // TODO: output format? +} + +func validateCover(clnt *lulu.Client, args []string) { + var ( + url url.URL + mfg = newMfgFlagSet() + nPages uint + timeout time.Duration + ) + + urlFlag := Flag{&urlValue{p: &url}, "url", "URL of cover file for Lulu to download"} + nPagesFlag := nPagesFlag(&nPages) + + fs := flag.NewFlagSet("validate-cover", flag.ExitOnError) + urlFlag.AddTo(fs) + mfg.AddTo(fs) + nPagesFlag.AddTo(fs) + fs.DurationVar(&timeout, "t", defaultTimeout, "Timeout") + + if err := fs.Parse(args); err != nil { + lg.Fatalf("lulu: %v\n", err) + } + urlFlag.MustBeSet(fs) + mfg.MustBeSet(fs) + nPagesFlag.MustBeSet(fs) + + ctx, cancel := context.WithTimeout(clnt.Context(), timeout) + defer cancel() + val, err := clnt.ValidateCover(ctx, url.String(), mfg.PkgId(), nPages) + if err != nil { + lg.Fatal(err) + } + fmt.Println(val) // TODO: output format? +} + +func coverDimensions(clnt *lulu.Client, args []string) { + var ( + mfg = newMfgFlagSet() + nPages uint + unit = lulu.Points + ) + nPagesFlag := nPagesFlag(&nPages) + unitFlag := Flag{newTextValue(&unit), "u", fmt.Sprintf("Unit of measurement %v", lulu.UnitValues())} + + fs := flag.NewFlagSet("cover-dimensions", flag.ExitOnError) + mfg.AddTo(fs) + nPagesFlag.AddTo(fs) + unitFlag.AddTo(fs) + + if err := fs.Parse(args); err != nil { + lg.Fatalf("lulu: %v\n", err) + } + mfg.MustBeSet(fs) + nPagesFlag.MustBeSet(fs) + + dims, err := clnt.CoverDimensions(mfg.PkgId(), nPages, unit) + if err != nil { + lg.Fatal(err) + } + fmt.Println(dims) +} diff --git a/cmd/lulu/validate_cover.go b/cmd/lulu/validate_cover.go deleted file mode 100644 index dd16db7..0000000 --- a/cmd/lulu/validate_cover.go +++ /dev/null @@ -1,44 +0,0 @@ -package main - -import ( - "context" - "flag" - "fmt" - "net/url" - "time" - - "git.samanthony.xyz/lulu" -) - -func validateCover(clnt *lulu.Client, args []string) { - var ( - url url.URL - mfg = newMfgFlagSet() - nPages uint - timeout time.Duration - ) - - urlFlag := Flag{urlValue{&url}, "url", "URL of cover file for Lulu to download"} - nPagesFlag := Flag{uintValue{&nPages}, "n", "Number of interior pages"} - - fs := flag.NewFlagSet("validate-cover", flag.ExitOnError) - urlFlag.AddTo(fs) - mfg.AddTo(fs) - nPagesFlag.AddTo(fs) - fs.DurationVar(&timeout, "t", defaultTimeout, "Timeout") - - if err := fs.Parse(args); err != nil { - lg.Fatalf("lulu: %v\n", err) - } - urlFlag.MustBeSet(fs) - mfg.MustBeSet(fs) - nPagesFlag.MustBeSet(fs) - - ctx, cancel := context.WithTimeout(clnt.Context(), timeout) - defer cancel() - val, err := clnt.ValidateCover(ctx, url.String(), mfg.PkgId(), nPages) - if err != nil { - lg.Fatal(err) - } - fmt.Println(val) // TODO: output format? -} diff --git a/cmd/lulu/validate_interior.go b/cmd/lulu/validate_interior.go deleted file mode 100644 index d2b34bf..0000000 --- a/cmd/lulu/validate_interior.go +++ /dev/null @@ -1,63 +0,0 @@ -package main - -import ( - "context" - "flag" - "fmt" - "net/url" - "time" - - "git.samanthony.xyz/lulu" -) - -func validateInterior(clnt *lulu.Client, args []string) { - var ( - basic bool - url url.URL - mfg = newMfgFlagSet() - timeout time.Duration - ) - - basicFlag := Flag{boolValue{&basic}, "basic", "Basic validation without pod_package_id manufacturing options"} - urlFlag := Flag{urlValue{&url}, "url", "URL of interior file for Lulu to download"} - - fs := flag.NewFlagSet("validate-interior", flag.ExitOnError) - basicFlag.AddTo(fs) - urlFlag.AddTo(fs) - mfg.AddTo(fs) - fs.DurationVar(&timeout, "t", defaultTimeout, "Timeout") - - fs.Usage = func() { - out := fs.Output() - name := "validate-interior" - fmt.Fprintf(out, "Usage of %s:\n", name) - fmt.Fprintf(out, " %s %s %s [flags]\n", name, urlFlag.Synopsis(), mfg.Synopsis()) - fmt.Fprintf(out, " %s %s %s [flags]\n", name, basicFlag.Synopsis(), urlFlag.Synopsis()) - fmt.Fprintf(out, "Flags:\n") - fs.PrintDefaults() - } - - if err := fs.Parse(args); err != nil { - lg.Fatalf("lulu: %v\n", err) - } - - var val lulu.InteriorValidation - var err error - ctx, cancel := context.WithTimeout(clnt.Context(), timeout) - defer cancel() - if basic { - urlFlag.MustBeSet(fs) - for _, f := range mfg.flags { - mutexFlags(fs, f, basicFlag) - } - val, err = clnt.ValidateInteriorBasic(ctx, url.String()) - } else { - urlFlag.MustBeSet(fs) - mfg.MustBeSet(fs) - val, err = clnt.ValidateInterior(ctx, url.String(), mfg.PkgId()) - } - if err != nil { - lg.Fatal(err) - } - fmt.Println(val) // TODO: output format? -} @@ -80,6 +80,10 @@ func (cd *CoverDimensions) UnmarshalJSON(data []byte) error { return nil } +func (cd CoverDimensions) String() string { + return fmt.Sprintf("%0.3f x %0.3f %s", cd.Width, cd.Height, cd.Unit) +} + // CoverValidation contains the validation status of a cover file. type CoverValidation struct { Id uint |