xmain: rm -rf
This commit is contained in:
parent
820226c72b
commit
07e316fac5
18 changed files with 36 additions and 761 deletions
|
|
@ -3,8 +3,9 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"oss.terrastruct.com/util-go/xmain"
|
||||
|
||||
"oss.terrastruct.com/d2/d2plugin"
|
||||
"oss.terrastruct.com/d2/lib/xmain"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
|
|
|||
|
|
@ -9,8 +9,9 @@ import (
|
|||
"context"
|
||||
"os/exec"
|
||||
|
||||
"oss.terrastruct.com/d2/d2graph"
|
||||
"oss.terrastruct.com/util-go/xexec"
|
||||
|
||||
"oss.terrastruct.com/d2/d2graph"
|
||||
)
|
||||
|
||||
// plugins contains the bundled d2 plugins.
|
||||
|
|
|
|||
|
|
@ -7,8 +7,9 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
|
||||
"oss.terrastruct.com/util-go/xmain"
|
||||
|
||||
"oss.terrastruct.com/d2/d2graph"
|
||||
"oss.terrastruct.com/d2/lib/xmain"
|
||||
)
|
||||
|
||||
// Serve returns a xmain.RunFunc that will invoke the plugin p as necessary to service the
|
||||
|
|
|
|||
3
fmt.go
3
fmt.go
|
|
@ -6,9 +6,10 @@ import (
|
|||
|
||||
"oss.terrastruct.com/xdefer"
|
||||
|
||||
"oss.terrastruct.com/util-go/xmain"
|
||||
|
||||
"oss.terrastruct.com/d2/d2format"
|
||||
"oss.terrastruct.com/d2/d2parser"
|
||||
"oss.terrastruct.com/d2/lib/xmain"
|
||||
)
|
||||
|
||||
func fmtCmd(ctx context.Context, ms *xmain.State) (err error) {
|
||||
|
|
|
|||
7
go.mod
generated
7
go.mod
generated
|
|
@ -17,17 +17,13 @@ require (
|
|||
go.uber.org/multierr v1.8.0
|
||||
golang.org/x/image v0.1.0
|
||||
golang.org/x/net v0.2.0
|
||||
golang.org/x/text v0.4.0
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2
|
||||
gonum.org/v1/plot v0.12.0
|
||||
nhooyr.io/websocket v1.8.7
|
||||
oss.terrastruct.com/cmdlog v0.0.0-20221201100934-012c01b3431c
|
||||
oss.terrastruct.com/diff v1.0.2-0.20221116222035-8bf4dd3ab541
|
||||
oss.terrastruct.com/util-go v0.0.0-20221201190418-569dcbf6dc3f
|
||||
oss.terrastruct.com/xcontext v0.0.0-20221018000442-50fdafb12f4f
|
||||
oss.terrastruct.com/util-go v0.0.0-20221201191904-5edc89ce397b
|
||||
oss.terrastruct.com/xdefer v0.0.0-20221017222355-6f3b6e4d1557
|
||||
oss.terrastruct.com/xjson v0.0.0-20221018000420-4986731c4c4a
|
||||
oss.terrastruct.com/xos v0.0.0-20221130233107-5fb84d57c9e3
|
||||
oss.terrastruct.com/xrand v0.0.0-20221020211818-4ac08e618333
|
||||
rogchap.com/v8go v0.7.1-0.20221102201510-1f00b5007d95
|
||||
)
|
||||
|
|
@ -59,6 +55,7 @@ require (
|
|||
golang.org/x/exp v0.0.0-20221126150942-6ab00d035af9 // indirect
|
||||
golang.org/x/sys v0.2.0 // indirect
|
||||
golang.org/x/term v0.2.0 // indirect
|
||||
golang.org/x/text v0.4.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
|
|
|
|||
12
go.sum
generated
12
go.sum
generated
|
|
@ -798,22 +798,14 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9
|
|||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g=
|
||||
nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
|
||||
oss.terrastruct.com/cmdlog v0.0.0-20221201100934-012c01b3431c h1:C1DDLzj2NrVi1YJpbZluEYZ2MkpJGppZYfSQ+F87TT0=
|
||||
oss.terrastruct.com/cmdlog v0.0.0-20221201100934-012c01b3431c/go.mod h1:C8u/lYTvQWc1xC7rHpgFfpScfQC4NMeGGMmlKVZZUXM=
|
||||
oss.terrastruct.com/diff v1.0.2-0.20221116222035-8bf4dd3ab541 h1:I9B1O1IJ6spivIQxbFRZmbhAwVeLwrcQRR1JbYUOvrI=
|
||||
oss.terrastruct.com/diff v1.0.2-0.20221116222035-8bf4dd3ab541/go.mod h1:ags2QDy/T6jr69hT6bpmAmhr2H98n9o8Atf3QlUJPiU=
|
||||
oss.terrastruct.com/util-go v0.0.0-20221201185848-8cc30ca56bbe h1:1CTXmBqea1vbVhYsyZ3NiCYUFZgURIQF/ItjrdlhwlE=
|
||||
oss.terrastruct.com/util-go v0.0.0-20221201185848-8cc30ca56bbe/go.mod h1:bL3CBn27CtTm++1iqRh2p6f8AIWeTdtlTN199Kg9JYM=
|
||||
oss.terrastruct.com/util-go v0.0.0-20221201190418-569dcbf6dc3f h1:ADVDj5TeMMxXMEKAxC9RKLCQq4gaW6GKynAeojB9VQQ=
|
||||
oss.terrastruct.com/util-go v0.0.0-20221201190418-569dcbf6dc3f/go.mod h1:bL3CBn27CtTm++1iqRh2p6f8AIWeTdtlTN199Kg9JYM=
|
||||
oss.terrastruct.com/xcontext v0.0.0-20221018000442-50fdafb12f4f h1:7voRCwKM7TZkTo9u7hj+uV/zXoVB8czWrTq6MVIh3dg=
|
||||
oss.terrastruct.com/xcontext v0.0.0-20221018000442-50fdafb12f4f/go.mod h1:Y0coTLsWwX0q3a+/Ndq797t+vWyxm42T49Ik3bzaDKY=
|
||||
oss.terrastruct.com/util-go v0.0.0-20221201191904-5edc89ce397b h1:o8+5KfZpQyaw7uKcPIdc9HOqVjVDEdsPZpdRV1k0rmc=
|
||||
oss.terrastruct.com/util-go v0.0.0-20221201191904-5edc89ce397b/go.mod h1:Fwy72FDIOOM4K8F96ScXkxHHppR1CPfUyo9+x9c1PBU=
|
||||
oss.terrastruct.com/xdefer v0.0.0-20221017222355-6f3b6e4d1557 h1:rPbhJbN1q7B4tnppSPoAMwq0t6Pk5SrQDQ5S6uoNNHg=
|
||||
oss.terrastruct.com/xdefer v0.0.0-20221017222355-6f3b6e4d1557/go.mod h1:plvfydF5METAlsbpeuSz44jckaOwrCWX3M0kTLoCA4I=
|
||||
oss.terrastruct.com/xjson v0.0.0-20221018000420-4986731c4c4a h1:AAcupsjBwpbcyLASX0ppDlxbfHWb5Neq5gWdGpLfaSA=
|
||||
oss.terrastruct.com/xjson v0.0.0-20221018000420-4986731c4c4a/go.mod h1:XJ71qiTzk/dbTWuYbuLJuRpBdKFN06Sk5FdFpq2TNmE=
|
||||
oss.terrastruct.com/xos v0.0.0-20221130233107-5fb84d57c9e3 h1:bchGZ5WryJNqr/yZ00rTcgZh1AComRFwKKBWOIxzJZE=
|
||||
oss.terrastruct.com/xos v0.0.0-20221130233107-5fb84d57c9e3/go.mod h1:lUSNCN0HA3pWHOMXT6gRNJtjg1U5t0TEEwAzPyV6enA=
|
||||
oss.terrastruct.com/xrand v0.0.0-20221020211818-4ac08e618333 h1:7EdxwXM75Id1VIN71QbE8bLzZRMs0qD7olnDw5gbI7w=
|
||||
oss.terrastruct.com/xrand v0.0.0-20221020211818-4ac08e618333/go.mod h1:O7TAoBmlQhoi46RdgVikDcoLRb/vLflhkXCAd+nO4SM=
|
||||
rogchap.com/v8go v0.7.1-0.20221102201510-1f00b5007d95 h1:r89YHVIWeQj/A3Nu6462eqARUECJlJkLRk36pfML1xA=
|
||||
|
|
|
|||
3
help.go
3
help.go
|
|
@ -9,8 +9,9 @@ import (
|
|||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"oss.terrastruct.com/util-go/xmain"
|
||||
|
||||
"oss.terrastruct.com/d2/d2plugin"
|
||||
"oss.terrastruct.com/d2/lib/xmain"
|
||||
)
|
||||
|
||||
func help(ms *xmain.State) {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import (
|
|||
"golang.org/x/xerrors"
|
||||
"oss.terrastruct.com/xdefer"
|
||||
|
||||
"oss.terrastruct.com/d2/lib/xmain"
|
||||
"oss.terrastruct.com/util-go/xmain"
|
||||
)
|
||||
|
||||
const maxImageSize int64 = 1 << 25 // 33_554_432
|
||||
|
|
|
|||
|
|
@ -12,10 +12,10 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
"oss.terrastruct.com/cmdlog"
|
||||
"oss.terrastruct.com/xos"
|
||||
"oss.terrastruct.com/util-go/cmdlog"
|
||||
"oss.terrastruct.com/util-go/xos"
|
||||
|
||||
"oss.terrastruct.com/d2/lib/xmain"
|
||||
"oss.terrastruct.com/util-go/xmain"
|
||||
)
|
||||
|
||||
//go:embed test_png.png
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import (
|
|||
|
||||
"github.com/playwright-community/playwright-go"
|
||||
|
||||
"oss.terrastruct.com/d2/lib/xmain"
|
||||
"oss.terrastruct.com/util-go/xmain"
|
||||
)
|
||||
|
||||
type Playwright struct {
|
||||
|
|
|
|||
165
lib/xhttp/err.go
165
lib/xhttp/err.go
|
|
@ -1,165 +0,0 @@
|
|||
package xhttp
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"oss.terrastruct.com/cmdlog"
|
||||
)
|
||||
|
||||
// Error represents an HTTP error.
|
||||
// It's exported only for comparison in tests.
|
||||
type Error struct {
|
||||
Code int
|
||||
Resp interface{}
|
||||
Err error
|
||||
}
|
||||
|
||||
var _ interface {
|
||||
Is(error) bool
|
||||
Unwrap() error
|
||||
} = Error{}
|
||||
|
||||
// Errorf creates a new error with code, resp, msg and v.
|
||||
//
|
||||
// When returned from an xhttp.HandlerFunc, it will be correctly logged
|
||||
// and written to the connection. See xhttp.WrapHandlerFunc
|
||||
func Errorf(code int, resp interface{}, msg string, v ...interface{}) error {
|
||||
return errorWrap(code, resp, fmt.Errorf(msg, v...))
|
||||
}
|
||||
|
||||
// ErrorWrap wraps err with the code and resp for xhttp.HandlerFunc.
|
||||
//
|
||||
// When returned from an xhttp.HandlerFunc, it will be correctly logged
|
||||
// and written to the connection. See xhttp.WrapHandlerFunc
|
||||
func ErrorWrap(code int, resp interface{}, err error) error {
|
||||
return errorWrap(code, resp, err)
|
||||
}
|
||||
|
||||
func errorWrap(code int, resp interface{}, err error) error {
|
||||
if resp == nil {
|
||||
resp = http.StatusText(code)
|
||||
}
|
||||
return Error{code, resp, err}
|
||||
}
|
||||
|
||||
func (e Error) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
func (e Error) Is(err error) bool {
|
||||
e2, ok := err.(Error)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return e.Code == e2.Code && e.Resp == e2.Resp && errors.Is(e.Err, e2.Err)
|
||||
}
|
||||
|
||||
func (e Error) Error() string {
|
||||
return fmt.Sprintf("http error with code %v and resp %#v: %v", e.Code, e.Resp, e.Err)
|
||||
}
|
||||
|
||||
// HandlerFunc is like http.HandlerFunc but returns an error.
|
||||
// See Errorf and ErrorWrap.
|
||||
type HandlerFunc func(w http.ResponseWriter, r *http.Request) error
|
||||
|
||||
type HandlerFuncAdapter struct {
|
||||
Log *cmdlog.Logger
|
||||
Func HandlerFunc
|
||||
}
|
||||
|
||||
// ServeHTTP adapts xhttp.HandlerFunc into http.Handler for usage with standard
|
||||
// HTTP routers like chi.
|
||||
//
|
||||
// It logs and writes any error from xhttp.HandlerFunc to the connection.
|
||||
//
|
||||
// If err was created with xhttp.Errorf or wrapped with xhttp.WrapError, then the error
|
||||
// will be logged at the correct level for the status code and xhttp.JSON will be called
|
||||
// with the code and resp.
|
||||
//
|
||||
// 400s are logged as warns and 500s as errors.
|
||||
//
|
||||
// If the error was not created with the xhttp helpers then a 500 will be written.
|
||||
//
|
||||
// If resp is nil, then resp is set to http.StatusText(code)
|
||||
//
|
||||
// If the code is not a 400 or a 500, then an error about about the unexpected error code
|
||||
// will be logged and a 500 will be written. The original error will also be logged.
|
||||
func (a HandlerFuncAdapter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
var h http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
err := a.Func(w, r)
|
||||
if err != nil {
|
||||
handleError(a.Log, w, err)
|
||||
}
|
||||
})
|
||||
|
||||
h.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func handleError(clog *cmdlog.Logger, w http.ResponseWriter, err error) {
|
||||
var herr Error
|
||||
ok := errors.As(err, &herr)
|
||||
if !ok {
|
||||
herr = ErrorWrap(http.StatusInternalServerError, nil, err).(Error)
|
||||
}
|
||||
|
||||
var logger *log.Logger
|
||||
switch {
|
||||
case 400 <= herr.Code && herr.Code < 500:
|
||||
logger = clog.Warn
|
||||
case 500 <= herr.Code && herr.Code < 600:
|
||||
logger = clog.Error
|
||||
default:
|
||||
logger = clog.Error
|
||||
|
||||
clog.Error.Printf("unexpected non error http status code %d with resp: %#v", herr.Code, herr.Resp)
|
||||
|
||||
herr.Code = http.StatusInternalServerError
|
||||
herr.Resp = nil
|
||||
}
|
||||
|
||||
if herr.Resp == nil {
|
||||
herr.Resp = http.StatusText(herr.Code)
|
||||
}
|
||||
|
||||
logger.Printf("error handling http request: %v", err)
|
||||
|
||||
ww, ok := w.(writtenResponseWriter)
|
||||
if !ok {
|
||||
clog.Warn.Printf("response writer does not implement Written, double write logs possible: %#v", w)
|
||||
} else if ww.Written() {
|
||||
// Avoid double writes if an error occurred while the response was
|
||||
// being written.
|
||||
return
|
||||
}
|
||||
|
||||
JSON(clog, w, herr.Code, map[string]interface{}{
|
||||
"error": herr.Resp,
|
||||
})
|
||||
}
|
||||
|
||||
type writtenResponseWriter interface {
|
||||
Written() bool
|
||||
}
|
||||
|
||||
func JSON(clog *cmdlog.Logger, w http.ResponseWriter, code int, v interface{}) {
|
||||
if v == nil {
|
||||
v = map[string]interface{}{
|
||||
"status": http.StatusText(code),
|
||||
}
|
||||
}
|
||||
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
clog.Error.Printf("json marshal error: %v", err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
w.WriteHeader(code)
|
||||
_, _ = w.Write(b)
|
||||
}
|
||||
125
lib/xhttp/log.go
125
lib/xhttp/log.go
|
|
@ -1,125 +0,0 @@
|
|||
package xhttp
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"runtime/debug"
|
||||
"time"
|
||||
|
||||
"golang.org/x/text/message"
|
||||
|
||||
"oss.terrastruct.com/cmdlog"
|
||||
)
|
||||
|
||||
type ResponseWriter interface {
|
||||
http.ResponseWriter
|
||||
http.Hijacker
|
||||
http.Flusher
|
||||
writtenResponseWriter
|
||||
}
|
||||
|
||||
var _ ResponseWriter = &responseWriter{}
|
||||
|
||||
type responseWriter struct {
|
||||
rw http.ResponseWriter
|
||||
|
||||
written bool
|
||||
status int
|
||||
length int
|
||||
}
|
||||
|
||||
func (rw *responseWriter) Header() http.Header {
|
||||
return rw.rw.Header()
|
||||
}
|
||||
|
||||
func (rw *responseWriter) WriteHeader(statusCode int) {
|
||||
if !rw.written {
|
||||
rw.written = true
|
||||
rw.status = statusCode
|
||||
}
|
||||
rw.rw.WriteHeader(statusCode)
|
||||
}
|
||||
|
||||
func (rw *responseWriter) Write(p []byte) (int, error) {
|
||||
if !rw.written && len(p) > 0 {
|
||||
rw.written = true
|
||||
if rw.status == 0 {
|
||||
rw.status = http.StatusOK
|
||||
}
|
||||
}
|
||||
rw.length += len(p)
|
||||
return rw.rw.Write(p)
|
||||
}
|
||||
|
||||
func (rw *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
hj, ok := rw.rw.(http.Hijacker)
|
||||
if !ok {
|
||||
return nil, nil, fmt.Errorf("underlying response writer does not implement http.Hijacker: %T", rw.rw)
|
||||
}
|
||||
return hj.Hijack()
|
||||
}
|
||||
|
||||
func (rw *responseWriter) Flush() {
|
||||
f, ok := rw.rw.(http.Flusher)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
f.Flush()
|
||||
}
|
||||
|
||||
func (rw *responseWriter) Written() bool {
|
||||
return rw.written
|
||||
}
|
||||
|
||||
func Log(clog *cmdlog.Logger, next http.Handler) http.Handler {
|
||||
englishPrinter := message.NewPrinter(message.MatchLanguage("en"))
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
defer func() {
|
||||
rec := recover()
|
||||
if rec != nil {
|
||||
clog.Error.Printf("caught panic: %#v\n%s", rec, debug.Stack())
|
||||
JSON(clog, w, http.StatusInternalServerError, map[string]interface{}{
|
||||
"error": http.StatusText(http.StatusInternalServerError),
|
||||
})
|
||||
}
|
||||
}()
|
||||
|
||||
rw := &responseWriter{
|
||||
rw: w,
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
next.ServeHTTP(rw, r)
|
||||
dur := time.Since(start)
|
||||
|
||||
if !rw.Written() {
|
||||
_, err := rw.Write(nil)
|
||||
if errors.Is(err, http.ErrHijacked) {
|
||||
clog.Success.Printf("%s %s %v: hijacked", r.Method, r.URL, dur)
|
||||
return
|
||||
}
|
||||
|
||||
clog.Warn.Printf("%s %s %v: no response written", r.Method, r.URL, dur)
|
||||
return
|
||||
}
|
||||
|
||||
var statusLogger *log.Logger
|
||||
switch {
|
||||
case 100 <= rw.status && rw.status <= 299:
|
||||
statusLogger = clog.Success
|
||||
case 300 <= rw.status && rw.status <= 399:
|
||||
statusLogger = clog.Info
|
||||
case 400 <= rw.status && rw.status <= 499:
|
||||
statusLogger = clog.Warn
|
||||
case 500 <= rw.status && rw.status <= 599:
|
||||
statusLogger = clog.Error
|
||||
}
|
||||
lengthStr := englishPrinter.Sprint(rw.length)
|
||||
// TODO: make work with watch.go on hijack, not after
|
||||
statusLogger.Printf("%s %s %d %sB %v", r.Method, r.URL, rw.status, lengthStr, dur)
|
||||
})
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
// Package xhttp implements http helpers.
|
||||
package xhttp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"oss.terrastruct.com/xcontext"
|
||||
)
|
||||
|
||||
func NewServer(log *log.Logger, h http.Handler) *http.Server {
|
||||
return &http.Server{
|
||||
MaxHeaderBytes: 1 << 18, // 262,144B
|
||||
ReadTimeout: time.Minute,
|
||||
WriteTimeout: time.Minute,
|
||||
IdleTimeout: time.Hour,
|
||||
ErrorLog: log,
|
||||
Handler: http.MaxBytesHandler(h, 1<<20), // 1,048,576B
|
||||
}
|
||||
}
|
||||
|
||||
func Serve(ctx context.Context, shutdownTimeout time.Duration, s *http.Server, l net.Listener) error {
|
||||
s.BaseContext = func(net.Listener) context.Context {
|
||||
return ctx
|
||||
}
|
||||
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
done <- s.Serve(l)
|
||||
}()
|
||||
|
||||
select {
|
||||
case err := <-done:
|
||||
return err
|
||||
case <-ctx.Done():
|
||||
ctx = xcontext.WithoutCancel(ctx)
|
||||
ctx, cancel := context.WithTimeout(ctx, shutdownTimeout)
|
||||
defer cancel()
|
||||
return s.Shutdown(ctx)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
// flag_helpers.go are private functions from pflag/flag.go
|
||||
package xmain
|
||||
|
||||
import "strings"
|
||||
|
||||
func wrap(i, w int, s string) string {
|
||||
if w == 0 {
|
||||
return strings.Replace(s, "\n", "\n"+strings.Repeat(" ", i), -1)
|
||||
}
|
||||
wrap := w - i
|
||||
var r, l string
|
||||
if wrap < 24 {
|
||||
i = 16
|
||||
wrap = w - i
|
||||
r += "\n" + strings.Repeat(" ", i)
|
||||
}
|
||||
if wrap < 24 {
|
||||
return strings.Replace(s, "\n", r, -1)
|
||||
}
|
||||
slop := 5
|
||||
wrap = wrap - slop
|
||||
l, s = wrapN(wrap, slop, s)
|
||||
r = r + strings.Replace(l, "\n", "\n"+strings.Repeat(" ", i), -1)
|
||||
for s != "" {
|
||||
var t string
|
||||
t, s = wrapN(wrap, slop, s)
|
||||
r = r + "\n" + strings.Repeat(" ", i) + strings.Replace(t, "\n", "\n"+strings.Repeat(" ", i), -1)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func wrapN(i, slop int, s string) (string, string) {
|
||||
if i+slop > len(s) {
|
||||
return s, ""
|
||||
}
|
||||
w := strings.LastIndexAny(s[:i], " \t\n")
|
||||
if w <= 0 {
|
||||
return s, ""
|
||||
}
|
||||
nlPos := strings.LastIndex(s[:i], "\n")
|
||||
if nlPos > 0 && nlPos < w {
|
||||
return s[:nlPos], s[nlPos+1:]
|
||||
}
|
||||
return s[:w], s[w+1:]
|
||||
}
|
||||
|
|
@ -1,173 +0,0 @@
|
|||
package xmain
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
"oss.terrastruct.com/cmdlog"
|
||||
"oss.terrastruct.com/xos"
|
||||
)
|
||||
|
||||
type Opts struct {
|
||||
Args []string
|
||||
Flags *pflag.FlagSet
|
||||
env *xos.Env
|
||||
log *cmdlog.Logger
|
||||
|
||||
flagEnv map[string]string
|
||||
}
|
||||
|
||||
func NewOpts(env *xos.Env, log *cmdlog.Logger, args []string) *Opts {
|
||||
flags := pflag.NewFlagSet("", pflag.ContinueOnError)
|
||||
flags.SortFlags = false
|
||||
flags.Usage = func() {}
|
||||
flags.SetOutput(io.Discard)
|
||||
return &Opts{
|
||||
Args: args,
|
||||
Flags: flags,
|
||||
env: env,
|
||||
log: log,
|
||||
flagEnv: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
// Mostly copy pasted pasted from pflag.FlagUsagesWrapped
|
||||
// with modifications for env var
|
||||
func (o *Opts) Defaults() string {
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
var lines []string
|
||||
|
||||
maxlen := 0
|
||||
maxEnvLen := 0
|
||||
o.Flags.VisitAll(func(flag *pflag.Flag) {
|
||||
if flag.Hidden {
|
||||
return
|
||||
}
|
||||
|
||||
line := ""
|
||||
if flag.Shorthand != "" && flag.ShorthandDeprecated == "" {
|
||||
line = fmt.Sprintf(" -%s, --%s", flag.Shorthand, flag.Name)
|
||||
} else {
|
||||
line = fmt.Sprintf(" --%s", flag.Name)
|
||||
}
|
||||
|
||||
varname, usage := pflag.UnquoteUsage(flag)
|
||||
if varname != "" {
|
||||
line += " " + varname
|
||||
}
|
||||
if flag.NoOptDefVal != "" {
|
||||
switch flag.Value.Type() {
|
||||
case "string":
|
||||
line += fmt.Sprintf("[=\"%s\"]", flag.NoOptDefVal)
|
||||
case "bool":
|
||||
if flag.NoOptDefVal != "true" {
|
||||
line += fmt.Sprintf("[=%s]", flag.NoOptDefVal)
|
||||
}
|
||||
case "count":
|
||||
if flag.NoOptDefVal != "+1" {
|
||||
line += fmt.Sprintf("[=%s]", flag.NoOptDefVal)
|
||||
}
|
||||
default:
|
||||
line += fmt.Sprintf("[=%s]", flag.NoOptDefVal)
|
||||
}
|
||||
}
|
||||
|
||||
line += "\x00"
|
||||
|
||||
if len(line) > maxlen {
|
||||
maxlen = len(line)
|
||||
}
|
||||
|
||||
if e, ok := o.flagEnv[flag.Name]; ok {
|
||||
line += fmt.Sprintf("$%s", e)
|
||||
}
|
||||
|
||||
line += "\x01"
|
||||
|
||||
if len(line) > maxEnvLen {
|
||||
maxEnvLen = len(line)
|
||||
}
|
||||
|
||||
line += usage
|
||||
if flag.Value.Type() == "string" {
|
||||
line += fmt.Sprintf(" (default %q)", flag.DefValue)
|
||||
} else {
|
||||
line += fmt.Sprintf(" (default %s)", flag.DefValue)
|
||||
}
|
||||
if len(flag.Deprecated) != 0 {
|
||||
line += fmt.Sprintf(" (DEPRECATED: %s)", flag.Deprecated)
|
||||
}
|
||||
|
||||
lines = append(lines, line)
|
||||
})
|
||||
|
||||
for _, line := range lines {
|
||||
sidx1 := strings.Index(line, "\x00")
|
||||
sidx2 := strings.Index(line, "\x01")
|
||||
spacing1 := strings.Repeat(" ", maxlen-sidx1)
|
||||
spacing2 := strings.Repeat(" ", (maxEnvLen-maxlen)-sidx2+sidx1)
|
||||
fmt.Fprintln(buf, line[:sidx1], spacing1, line[sidx1+1:sidx2], spacing2, wrap(maxEnvLen+3, 0, line[sidx2+1:]))
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func (o *Opts) getEnv(flag, k string) string {
|
||||
if k != "" {
|
||||
o.flagEnv[flag] = k
|
||||
return o.env.Getenv(k)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (o *Opts) Int64(envKey, flag, shortFlag string, defaultVal int64, usage string) (*int64, error) {
|
||||
if env := o.getEnv(flag, envKey); env != "" {
|
||||
envVal, err := strconv.ParseInt(env, 10, 64)
|
||||
if err != nil {
|
||||
return nil, UsageErrorf(`invalid environment variable %s. Expected int64. Found "%v".`, envKey, envVal)
|
||||
}
|
||||
defaultVal = envVal
|
||||
}
|
||||
|
||||
return o.Flags.Int64P(flag, shortFlag, defaultVal, usage), nil
|
||||
}
|
||||
|
||||
func (o *Opts) String(envKey, flag, shortFlag string, defaultVal, usage string) *string {
|
||||
if env := o.getEnv(flag, envKey); env != "" {
|
||||
defaultVal = env
|
||||
}
|
||||
|
||||
return o.Flags.StringP(flag, shortFlag, defaultVal, usage)
|
||||
}
|
||||
|
||||
func (o *Opts) Bool(envKey, flag, shortFlag string, defaultVal bool, usage string) (*bool, error) {
|
||||
if env := o.getEnv(flag, envKey); env != "" {
|
||||
if !boolyEnv(env) {
|
||||
return nil, UsageErrorf(`invalid environment variable %s. Expected bool. Found "%s".`, envKey, env)
|
||||
}
|
||||
if truthyEnv(env) {
|
||||
defaultVal = true
|
||||
} else {
|
||||
defaultVal = false
|
||||
}
|
||||
}
|
||||
|
||||
return o.Flags.BoolP(flag, shortFlag, defaultVal, usage), nil
|
||||
}
|
||||
|
||||
func boolyEnv(s string) bool {
|
||||
return falseyEnv(s) || truthyEnv(s)
|
||||
}
|
||||
|
||||
func falseyEnv(s string) bool {
|
||||
return s == "0" || s == "false"
|
||||
}
|
||||
|
||||
func truthyEnv(s string) bool {
|
||||
return s == "1" || s == "true"
|
||||
}
|
||||
|
|
@ -1,180 +0,0 @@
|
|||
// Package xmain provides a standard stub for the main of a command handling logging,
|
||||
// flags, signals and shutdown.
|
||||
package xmain
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"cdr.dev/slog"
|
||||
"cdr.dev/slog/sloggers/sloghuman"
|
||||
|
||||
"oss.terrastruct.com/xos"
|
||||
|
||||
"oss.terrastruct.com/cmdlog"
|
||||
|
||||
ctxlog "oss.terrastruct.com/d2/lib/log"
|
||||
)
|
||||
|
||||
type RunFunc func(context.Context, *State) error
|
||||
|
||||
func Main(run RunFunc) {
|
||||
name := ""
|
||||
args := []string(nil)
|
||||
if len(os.Args) > 0 {
|
||||
name = os.Args[0]
|
||||
args = os.Args[1:]
|
||||
}
|
||||
|
||||
ms := &State{
|
||||
Name: name,
|
||||
|
||||
Stdin: os.Stdin,
|
||||
Stdout: os.Stdout,
|
||||
Stderr: os.Stderr,
|
||||
|
||||
Env: xos.NewEnv(os.Environ()),
|
||||
}
|
||||
ms.Log = cmdlog.New(ms.Env, os.Stderr)
|
||||
ms.Opts = NewOpts(ms.Env, ms.Log, args)
|
||||
|
||||
sigs := make(chan os.Signal, 1)
|
||||
signal.Notify(sigs, os.Interrupt, syscall.SIGTERM)
|
||||
|
||||
err := ms.Main(context.Background(), sigs, run)
|
||||
if err != nil {
|
||||
code := 1
|
||||
msg := ""
|
||||
usage := false
|
||||
|
||||
var eerr ExitError
|
||||
var uerr UsageError
|
||||
if errors.As(err, &eerr) {
|
||||
code = eerr.Code
|
||||
msg = eerr.Message
|
||||
} else if errors.As(err, &uerr) {
|
||||
msg = err.Error()
|
||||
usage = true
|
||||
} else {
|
||||
msg = err.Error()
|
||||
}
|
||||
|
||||
if msg != "" {
|
||||
ms.Log.Error.Print(msg)
|
||||
if usage {
|
||||
ms.Log.Error.Print("Run with --help to see usage.")
|
||||
}
|
||||
}
|
||||
os.Exit(code)
|
||||
}
|
||||
}
|
||||
|
||||
type State struct {
|
||||
Name string
|
||||
|
||||
Stdin io.Reader
|
||||
Stdout io.WriteCloser
|
||||
Stderr io.WriteCloser
|
||||
|
||||
Log *cmdlog.Logger
|
||||
Env *xos.Env
|
||||
Opts *Opts
|
||||
}
|
||||
|
||||
func (ms *State) Main(ctx context.Context, sigs <-chan os.Signal, run func(context.Context, *State) error) error {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
defer close(done)
|
||||
done <- run(ctx, ms)
|
||||
}()
|
||||
|
||||
select {
|
||||
case err := <-done:
|
||||
return err
|
||||
case sig := <-sigs:
|
||||
ms.Log.Warn.Printf("received signal %v: shutting down...", sig)
|
||||
cancel()
|
||||
select {
|
||||
case err := <-done:
|
||||
if err != nil && !errors.Is(err, context.Canceled) {
|
||||
return fmt.Errorf("failed to shutdown: %w", err)
|
||||
}
|
||||
if sig == syscall.SIGTERM {
|
||||
// We successfully shutdown.
|
||||
return nil
|
||||
}
|
||||
return ExitError{Code: 1}
|
||||
case <-time.After(time.Minute):
|
||||
return ExitError{
|
||||
Code: 1,
|
||||
Message: "took longer than 1 minute to shutdown: exiting forcefully",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type ExitError struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
func ExitErrorf(code int, msg string, v ...interface{}) ExitError {
|
||||
return ExitError{
|
||||
Code: code,
|
||||
Message: fmt.Sprintf(msg, v...),
|
||||
}
|
||||
}
|
||||
|
||||
func (ee ExitError) Error() string {
|
||||
s := fmt.Sprintf("exiting with code %d", ee.Code)
|
||||
if ee.Message != "" {
|
||||
s += ": " + ee.Message
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
type UsageError struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
func UsageErrorf(msg string, v ...interface{}) UsageError {
|
||||
return UsageError{
|
||||
Message: fmt.Sprintf(msg, v...),
|
||||
}
|
||||
}
|
||||
|
||||
func (ue UsageError) Error() string {
|
||||
return fmt.Sprintf("bad usage: %s", ue.Message)
|
||||
}
|
||||
|
||||
func (ms *State) ReadPath(fp string) ([]byte, error) {
|
||||
if fp == "-" {
|
||||
return io.ReadAll(ms.Stdin)
|
||||
}
|
||||
return os.ReadFile(fp)
|
||||
}
|
||||
|
||||
func (ms *State) WritePath(fp string, p []byte) error {
|
||||
if fp == "-" {
|
||||
_, err := ms.Stdout.Write(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ms.Stdout.Close()
|
||||
}
|
||||
return os.WriteFile(fp, p, 0644)
|
||||
}
|
||||
|
||||
// TODO: remove after removing slog
|
||||
func DiscardSlog(ctx context.Context) context.Context {
|
||||
return ctxlog.With(ctx, slog.Make(sloghuman.Sink(io.Discard)))
|
||||
}
|
||||
15
main.go
15
main.go
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
|
@ -14,6 +15,8 @@ import (
|
|||
"github.com/spf13/pflag"
|
||||
"go.uber.org/multierr"
|
||||
|
||||
"oss.terrastruct.com/util-go/xmain"
|
||||
|
||||
"oss.terrastruct.com/d2/d2layouts/d2sequence"
|
||||
"oss.terrastruct.com/d2/d2lib"
|
||||
"oss.terrastruct.com/d2/d2plugin"
|
||||
|
|
@ -21,10 +24,13 @@ import (
|
|||
"oss.terrastruct.com/d2/d2themes"
|
||||
"oss.terrastruct.com/d2/d2themes/d2themescatalog"
|
||||
"oss.terrastruct.com/d2/lib/imgbundler"
|
||||
ctxlog "oss.terrastruct.com/d2/lib/log"
|
||||
"oss.terrastruct.com/d2/lib/png"
|
||||
"oss.terrastruct.com/d2/lib/textmeasure"
|
||||
"oss.terrastruct.com/d2/lib/version"
|
||||
"oss.terrastruct.com/d2/lib/xmain"
|
||||
|
||||
"cdr.dev/slog"
|
||||
"cdr.dev/slog/sloggers/sloghuman"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
|
@ -33,7 +39,7 @@ func main() {
|
|||
|
||||
func run(ctx context.Context, ms *xmain.State) (err error) {
|
||||
// :(
|
||||
ctx = xmain.DiscardSlog(ctx)
|
||||
ctx = DiscardSlog(ctx)
|
||||
|
||||
// These should be kept up-to-date with the d2 man page
|
||||
watchFlag, err := ms.Opts.Bool("D2_WATCH", "watch", "w", false, "watch for changes to input and live reload. Use $HOST and $PORT to specify the listening address.\n(default localhost:0, which is will open on a randomly available local port).")
|
||||
|
|
@ -257,3 +263,8 @@ func renameExt(fp string, newExt string) string {
|
|||
return strings.TrimSuffix(fp, ext) + newExt
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: remove after removing slog
|
||||
func DiscardSlog(ctx context.Context) context.Context {
|
||||
return ctxlog.With(ctx, slog.Make(sloghuman.Sink(io.Discard)))
|
||||
}
|
||||
|
|
|
|||
6
watch.go
6
watch.go
|
|
@ -21,10 +21,12 @@ import (
|
|||
|
||||
"oss.terrastruct.com/util-go/xbrowser"
|
||||
|
||||
"oss.terrastruct.com/util-go/xhttp"
|
||||
|
||||
"oss.terrastruct.com/util-go/xmain"
|
||||
|
||||
"oss.terrastruct.com/d2/d2plugin"
|
||||
"oss.terrastruct.com/d2/lib/png"
|
||||
"oss.terrastruct.com/d2/lib/xhttp"
|
||||
"oss.terrastruct.com/d2/lib/xmain"
|
||||
)
|
||||
|
||||
// Enabled with the build tag "dev".
|
||||
|
|
|
|||
Loading…
Reference in a new issue