Compare commits

..

No commits in common. "master" and "v1.3.1" have entirely different histories.

15 changed files with 273 additions and 656 deletions

View File

@ -1,105 +0,0 @@
package main
import (
"fmt"
"path/filepath"
"strings"
"src.lwithers.me.uk/go/htpack/cmd/htpacker/packer"
)
type ctGlobEntry struct {
pattern, contentType string
pathComponents int
}
type ctGlobList []ctGlobEntry
func parseGlobs(flags []string) (ctGlobList, error) {
var ctGlobs ctGlobList
for _, flag := range flags {
// split pattern:content-type
pos := strings.LastIndexByte(flag, ':')
if pos == -1 {
return nil, &parseGlobError{
Value: flag,
Err: "must be pattern:content-type",
}
}
pattern, ct := flag[:pos], flag[pos+1:]
// patterns starting with "/" must match the entire directory
// prefix; otherwise, an arbitrary number of path components are
// allowed prior to the prefix
var pathComponents int
if strings.HasPrefix(pattern, "/") {
pathComponents = -1
pattern = strings.TrimPrefix(pattern, "/")
} else {
pathComponents = 1 + strings.Count(pattern, "/")
}
// test that the pattern's syntax is valid
if _, err := filepath.Match(pattern, "test"); err != nil {
return nil, &parseGlobError{
Value: flag,
Err: err.Error(),
}
}
ctGlobs = append(ctGlobs, ctGlobEntry{
pattern: pattern,
contentType: ct,
pathComponents: pathComponents,
})
}
return ctGlobs, nil
}
// ApplyContentTypes will scan the list of files to pack, matching by filename,
// and on match will apply the given content type.
func (ctGlobs ctGlobList) ApplyContentTypes(ftp packer.FilesToPack) {
for name := range ftp {
for _, entry := range ctGlobs {
testName := trimPathComponents(name, entry.pathComponents)
matched, _ := filepath.Match(entry.pattern, testName)
if matched {
f := ftp[name]
f.ContentType = entry.contentType
ftp[name] = f
break
}
}
}
}
func trimPathComponents(name string, components int) string {
name = strings.TrimPrefix(name, "/") // FilesToPack keys = absolute path
// if we are matching the full prefix, don't otherwise manipulate the
// name
if components < 0 {
return name
}
// otherwise, trim the number of components remaining in the path so
// that we are only matching the trailing path components from the
// FilesToPack key
parts := 1 + strings.Count(name, "/")
for ; parts > components; parts-- {
pos := strings.IndexByte(name, '/')
name = name[pos+1:]
}
return name
}
// parseGlobError is returned from parseGlobs on error.
type parseGlobError struct {
Value string
Err string
}
func (pge *parseGlobError) Error() string {
return fmt.Sprintf("--content-type entry %q: %s", pge.Value, pge.Err)
}

View File

@ -1,109 +0,0 @@
package main
import (
"testing"
"src.lwithers.me.uk/go/htpack/cmd/htpacker/packer"
)
func TestParseGlobs(t *testing.T) {
ctGlobs, err := parseGlobs([]string{
"*.foo:text/html",
"*.bar:text/plain",
"baz/qux/*.js:application/javascript",
"/abs/file:image/png",
})
if err != nil {
t.Fatal(err)
}
check := func(pos int, pattern, contentType string, pathComponents int) {
if pos >= len(ctGlobs) {
t.Errorf("entry %d not present", pos)
return
}
if pattern != ctGlobs[pos].pattern {
t.Errorf("entry %d: expected pattern %q but got %q",
pos, pattern, ctGlobs[pos].pattern)
}
if contentType != ctGlobs[pos].contentType {
t.Errorf("entry %d: expected content type %q but got %q",
pos, contentType, ctGlobs[pos].contentType)
}
if pathComponents != ctGlobs[pos].pathComponents {
t.Errorf("entry %d: expected num. path components %d but got %d",
pos, pathComponents, ctGlobs[pos].pathComponents)
}
}
check(0, "*.foo", "text/html", 1)
check(1, "*.bar", "text/plain", 1)
check(2, "baz/qux/*.js", "application/javascript", 3)
check(3, "abs/file", "image/png", -1)
}
func TestParseGlobsErrSep(t *testing.T) {
const badValue = "hello/dave.js" // missing ":" separator
_, err := parseGlobs([]string{badValue})
switch err := err.(type) {
case *parseGlobError:
if err.Value != badValue {
t.Errorf("expected value %q but got %q", badValue, err.Value)
}
case nil:
t.Fatal("expected error")
default:
t.Errorf("unexpected error type %T (value %v)", err, err)
}
}
func TestParseGlobsErrPattern(t *testing.T) {
const badValue = "[-z]:foo/bar" // malformed character class
_, err := parseGlobs([]string{badValue})
switch err := err.(type) {
case *parseGlobError:
if err.Value != badValue {
t.Errorf("expected value %q but got %q", badValue, err.Value)
}
case nil:
t.Fatal("expected error")
default:
t.Errorf("unexpected error type %T (value %v)", err, err)
}
}
func TestApplyContentTypes(t *testing.T) {
// XXX: we program our _expectation_ of content-type into the Filename field
ftp := packer.FilesToPack{
"foo.txt": packer.FileToPack{Filename: "text/plain"},
"baz/foo.txt": packer.FileToPack{Filename: "text/plain"},
"baz/qux.png": packer.FileToPack{Filename: "image/png"},
"foo/qux.png": packer.FileToPack{},
"foo/baz/qux.png": packer.FileToPack{Filename: "image/png"},
"bar.jpeg": packer.FileToPack{},
"foo/baz/bar.jpeg": packer.FileToPack{},
"baz/bar.jpeg": packer.FileToPack{Filename: "image/jpeg"},
}
ctGlobs, err := parseGlobs([]string{
"*.txt:text/plain", // should match anywhere
"baz/qux.png:image/png", // won't match /foo/qux.png
"/baz/bar.jpeg:image/jpeg", // exact prefix match
})
if err != nil {
t.Fatal(err)
}
ctGlobs.ApplyContentTypes(ftp)
for k, v := range ftp {
if v.Filename != v.ContentType {
t.Errorf("filename %q: expected content type %q but got %q",
k, v.Filename, v.ContentType)
}
}
}

View File

@ -1,24 +1,17 @@
module src.lwithers.me.uk/go/htpack/cmd/htpacker module src.lwithers.me.uk/go/htpack/cmd/htpacker
go 1.22 go 1.12
require ( require (
github.com/andybalholm/brotli v1.1.0 github.com/andybalholm/brotli v1.0.0
github.com/foobaz/go-zopfli v0.0.0-20140122214029-7432051485e2 github.com/foobaz/go-zopfli v0.0.0-20140122214029-7432051485e2
github.com/gosuri/uiprogress v0.0.1 github.com/kr/pretty v0.1.0 // indirect
github.com/spf13/cobra v1.8.1 github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381
golang.org/x/sys v0.22.0 github.com/spf13/cobra v0.0.5
gopkg.in/yaml.v2 v2.4.0 github.com/vbauerster/mpb/v4 v4.11.2
src.lwithers.me.uk/go/htpack v1.3.3 golang.org/x/sys v0.0.0-20200116001909-b77594299b42
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
gopkg.in/yaml.v2 v2.2.2
src.lwithers.me.uk/go/htpack v1.1.5
src.lwithers.me.uk/go/writefile v1.0.1 src.lwithers.me.uk/go/writefile v1.0.1
) )
require (
github.com/gogo/protobuf v1.3.2 // indirect
github.com/gosuri/uilive v0.0.4 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/kr/pretty v0.1.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/spf13/pflag v1.0.5 // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
)

View File

@ -1,68 +1,75 @@
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= github.com/VividCortex/ewma v1.1.1 h1:MnEK4VOv6n0RSY4vtRe3h11qjxL3+t0B8yOL8iMXdcM=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA=
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
github.com/andybalholm/brotli v1.0.0 h1:7UCwP93aiSfvWpapti8g88vVVGp2qqtGyePsSuDafo4=
github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/foobaz/go-zopfli v0.0.0-20140122214029-7432051485e2 h1:VA6jElpcJ+wkwEBufbnVkSBCA2TEnxdRppjRT5Kvh0A= github.com/foobaz/go-zopfli v0.0.0-20140122214029-7432051485e2 h1:VA6jElpcJ+wkwEBufbnVkSBCA2TEnxdRppjRT5Kvh0A=
github.com/foobaz/go-zopfli v0.0.0-20140122214029-7432051485e2/go.mod h1:Yi95+RbwKz7uGndSuUhoq7LJKh8qH8DT9fnL4ewU30k= github.com/foobaz/go-zopfli v0.0.0-20140122214029-7432051485e2/go.mod h1:Yi95+RbwKz7uGndSuUhoq7LJKh8qH8DT9fnL4ewU30k=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
github.com/gosuri/uilive v0.0.4 h1:hUEBpQDj8D8jXgtCdBu7sWsy5sbW/5GhuO8KBwJ2jyY= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/gosuri/uilive v0.0.4/go.mod h1:V/epo5LjjlDE5RJUcqx8dbw+zc93y5Ya3yg8tfZ74VI= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/gosuri/uiprogress v0.0.1 h1:0kpv/XY/qTmFWl/SkaJykZXrBBzwwadmW8fRb7RJSxw= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/gosuri/uiprogress v0.0.1/go.mod h1:C1RTYn4Sc7iEyf6j8ft5dyoZ4212h8G1ol9QQluh5+0= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381 h1:bqDmpDG49ZRnB5PcgP0RXtQvnMSgIF14M7CBd2shtXs=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/vbauerster/mpb/v4 v4.11.2 h1:ynkUoKzi65DZ1UsQPx7sgi/KN6G9f7br+Us2nKm35AM=
github.com/vbauerster/mpb/v4 v4.11.2/go.mod h1:jIuIRCltGJUnm6DCyPVkwjlLUk4nHTH+m4eD14CdFF0=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191112222119-e1110fd1c708 h1:pXVtWnwHkrWD9ru3sDxY/qFK/bfc0egRovX91EjWjf4=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20191112222119-e1110fd1c708/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191113165036-4c7a9d0fe056/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1 h1:gZpLHxUX5BdYLA08Lj4YCJNN/jk7KtquiArPoeX0WvA=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= src.lwithers.me.uk/go/htpack v1.1.5 h1:2JzgqLZ1ROYc53+96NezfJ3S9fkwHZNd6QgJhMXnlSE=
src.lwithers.me.uk/go/htpack v1.3.3 h1:Xvl6qR9HfSblmCgPyu+ACQ9o3aLQSIy3l8CrMbzj/jc= src.lwithers.me.uk/go/htpack v1.1.5/go.mod h1:JWofpm01RJbCTIyKfIPftUsxk6KlFkrYwyHgCVdKY+s=
src.lwithers.me.uk/go/htpack v1.3.3/go.mod h1:qKgCBgZ6iiiuYOxZkYOPVpXLBzp6gXEd4A0ksxgR6Nk=
src.lwithers.me.uk/go/writefile v1.0.1 h1:bwBGtvyZfCxFIM14e1aYgJWlZuowKkwJx53OJlUPd0s= src.lwithers.me.uk/go/writefile v1.0.1 h1:bwBGtvyZfCxFIM14e1aYgJWlZuowKkwJx53OJlUPd0s=
src.lwithers.me.uk/go/writefile v1.0.1/go.mod h1:NahlmRCtB7kg4ai+zHZgxXdUs+MR8VqWG8mql35TsxA= src.lwithers.me.uk/go/writefile v1.0.1/go.mod h1:NahlmRCtB7kg4ai+zHZgxXdUs+MR8VqWG8mql35TsxA=

View File

@ -16,20 +16,6 @@ import (
var packCmd = &cobra.Command{ var packCmd = &cobra.Command{
Use: "pack", Use: "pack",
Short: "creates a packfile from a YAML spec or set of files/dirs", Short: "creates a packfile from a YAML spec or set of files/dirs",
Long: `When given a YAML spec file (a template for which can be generated
with the "yaml" command), files will be packed exactly as per the spec. The
--content-type flag cannot be used and no extra files can be specified.
When given a list of files and directories to pack, the content type for each
file will be automatically detected. It is possible to override the content
type by specifying one or more --content-type flags. These take an argument in
the form "pattern:content/type". The pattern is matched using common glob
(* = wildcard), very similar to .gitignore. If the pattern contains any
directory names, these must match the final components of the file to pack's
path. If the pattern starts with a "/", then the full path must be matched
exactly.
`,
RunE: func(c *cobra.Command, args []string) error { RunE: func(c *cobra.Command, args []string) error {
// convert "out" to an absolute path, so that it will still // convert "out" to an absolute path, so that it will still
// work after chdir // work after chdir
@ -65,16 +51,6 @@ exactly.
} }
} }
// parse content-type globs
ctGlobList, err := c.Flags().GetStringArray("content-type")
if err != nil {
return err
}
ctGlobs, err := parseGlobs(ctGlobList)
if err != nil {
return err
}
// if "spec" is not present, then we expect a list of input // if "spec" is not present, then we expect a list of input
// files, and we'll build a spec from them // files, and we'll build a spec from them
if spec == "" { if spec == "" {
@ -82,16 +58,12 @@ exactly.
return errors.New("need --yaml, " + return errors.New("need --yaml, " +
"or one or more filenames") "or one or more filenames")
} }
err = PackFiles2(c, args, ctGlobs, out) err = PackFiles(c, args, out)
} else { } else {
if len(args) != 0 { if len(args) != 0 {
return errors.New("cannot specify files " + return errors.New("cannot specify files " +
"when using --yaml") "when using --yaml")
} }
if ctGlobs != nil {
return errors.New("cannot specify --content-type " +
"when using --yaml")
}
err = PackSpec(c, spec, out) err = PackSpec(c, spec, out)
} }
if err != nil { if err != nil {
@ -111,20 +83,13 @@ func init() {
"YAML specification file (if not present, just pack files)") "YAML specification file (if not present, just pack files)")
packCmd.Flags().StringP("chdir", "C", "", packCmd.Flags().StringP("chdir", "C", "",
"Change to directory before searching for input files") "Change to directory before searching for input files")
packCmd.Flags().StringArrayP("content-type", "", nil,
"Override content type for pattern, e.g. \"*.foo=bar/baz\" (like .gitignore)")
} }
func PackFiles(c *cobra.Command, args []string, out string) error { func PackFiles(c *cobra.Command, args []string, out string) error {
return PackFiles2(c, args, nil, out)
}
func PackFiles2(c *cobra.Command, args []string, ctGlobs ctGlobList, out string) error {
ftp, err := filesFromList(args) ftp, err := filesFromList(args)
if err != nil { if err != nil {
return err return err
} }
ctGlobs.ApplyContentTypes(ftp)
return doPack(ftp, out) return doPack(ftp, out)
} }
@ -144,7 +109,7 @@ func PackSpec(c *cobra.Command, spec, out string) error {
} }
func doPack(ftp packer.FilesToPack, out string) error { func doPack(ftp packer.FilesToPack, out string) error {
prog := newUiProgress(ftp) prog := mpbProgress(ftp)
err := packer.Pack2(ftp, out, prog) err := packer.Pack2(ftp, out, prog)
prog.Complete() prog.Complete()

View File

@ -0,0 +1,144 @@
package main
import (
"github.com/logrusorgru/aurora"
"github.com/vbauerster/mpb/v4"
"github.com/vbauerster/mpb/v4/decor"
"src.lwithers.me.uk/go/htpack/cmd/htpacker/packer"
)
const mpbBarStyle = "[██░]"
// mpbProgress returns a new progress object that keeps the user informed via
// the visual mpb library.
func mpbProgress(ftp packer.FilesToPack) *mpbProg {
mp := new(mpbProg)
mp.un.max = len(ftp)
for _, f := range ftp {
if !f.DisableCompression && !f.DisableGzip {
mp.gzip.max++
}
if !f.DisableCompression && !f.DisableBrotli {
mp.brotli.max++
}
}
mp.p = mpb.New()
mp.un.bar = mp.p.AddBar(int64(mp.un.max),
mpb.PrependDecorators(barName("uncompressed")),
mpb.BarStyle(mpbBarStyle),
mpb.AppendDecorators(&mp.un))
if mp.gzip.max > 0 {
mp.gzip.bar = mp.p.AddBar(int64(mp.gzip.max),
mpb.PrependDecorators(barName("gzip")),
mpb.BarStyle(mpbBarStyle),
mpb.AppendDecorators(&mp.gzip))
}
if mp.brotli.max > 0 {
mp.brotli.bar = mp.p.AddBar(int64(mp.brotli.max),
mpb.PrependDecorators(barName("brotli")),
mpb.BarStyle(mpbBarStyle),
mpb.AppendDecorators(&mp.brotli))
}
return mp
}
func barName(n string) decor.Decorator {
return decor.Name(aurora.Magenta(n).String(), decor.WCSyncWidth)
}
// mpbProg is the mpb progress tracker. It has one bar for each type of
// compression, and its methods simply dispatch onto the type-specific
// bars.
type mpbProg struct {
un, gzip, brotli mpbProg1
p *mpb.Progress
}
func (mp *mpbProg) Count(_ int) {
}
func (mp *mpbProg) Begin(filename, compression string) {
switch compression {
case "uncompressed":
mp.un.Begin(filename)
case "gzip":
mp.gzip.Begin(filename)
case "brotli":
mp.brotli.Begin(filename)
default:
return
}
}
func (mp *mpbProg) End(filename, compression string) {
switch compression {
case "uncompressed":
mp.un.End(filename)
case "gzip":
mp.gzip.End(filename)
case "brotli":
mp.brotli.End(filename)
default:
return
}
}
func (mp *mpbProg) Complete() {
mp.un.Complete()
mp.gzip.Complete()
mp.brotli.Complete()
mp.p.Wait()
}
// mpbProg1 is a type-specific progress bar. In addition to holding state and
// methods for updating the bar, it also implements decor.Decor.
type mpbProg1 struct {
max int // number of items we expect
done int // number of items completed
cur []string // list of currently-packing filenames
bar *mpb.Bar
// embedding this type lets us implement decor.Decor
decor.WC
}
func (mp1 *mpbProg1) Decor(stat *decor.Statistics) string {
if stat.Completed {
return ""
}
switch len(mp1.cur) {
case 0:
return aurora.Gray(8, "(idle)").String()
case 1:
return aurora.Blue(mp1.cur[0]).String()
default:
return aurora.Sprintf(aurora.Green("%s + %d more"), aurora.Blue(mp1.cur[0]), len(mp1.cur)-1)
}
}
func (mp1 *mpbProg1) Begin(filename string) {
mp1.cur = append(mp1.cur, filename)
}
func (mp1 *mpbProg1) End(filename string) {
for i, v := range mp1.cur {
if v == filename {
mp1.cur[i] = mp1.cur[len(mp1.cur)-1]
mp1.cur = mp1.cur[:len(mp1.cur)-1]
break
}
}
mp1.done++
if mp1.bar != nil {
mp1.bar.SetCurrent(int64(mp1.done))
}
}
func (mp1 *mpbProg1) Complete() {
if mp1.bar != nil {
mp1.bar.SetTotal(int64(mp1.max), true)
}
}

View File

@ -64,11 +64,6 @@ func (ignoreProgress) Begin(_, _ string) {}
func (ignoreProgress) End(_, _ string) {} func (ignoreProgress) End(_, _ string) {}
const ( const (
// minCompressionFileSize is the minimum filesize we need before
// considering compression. Note this must be at least 2, to avoid
// known bugs in go-zopfli.
minCompressionFileSize = 128
// minCompressionSaving means we'll only use the compressed version of // minCompressionSaving means we'll only use the compressed version of
// the file if it's at least this many bytes smaller than the original. // the file if it's at least this many bytes smaller than the original.
// Chosen somewhat arbitrarily; we have to add an HTTP header, and the // Chosen somewhat arbitrarily; we have to add an HTTP header, and the
@ -364,7 +359,7 @@ func (p *packer) packFile(path string, fileToPack FileToPack) {
defer p.progress.End(fileToPack.Filename, "gzip") defer p.progress.End(fileToPack.Filename, "gzip")
if err := p.Gzip(data, info); err != nil { if err := p.Gzip(data, info); err != nil {
return fmt.Errorf("gzip compression of %s "+ return fmt.Errorf("gzip compression of %s "+
"failed: %w", fileToPack.Filename, err) "failed: %v", fileToPack.Filename, err)
} }
return nil return nil
}) })
@ -375,7 +370,7 @@ func (p *packer) packFile(path string, fileToPack FileToPack) {
defer p.progress.End(fileToPack.Filename, "brotli") defer p.progress.End(fileToPack.Filename, "brotli")
if err := p.Brotli(data, info); err != nil { if err := p.Brotli(data, info); err != nil {
return fmt.Errorf("brotli compression of %s "+ return fmt.Errorf("brotli compression of %s "+
"failed: %w", fileToPack.Filename, err) "failed: %v", fileToPack.Filename, err)
} }
return nil return nil
}) })
@ -438,10 +433,6 @@ func (p *packer) Uncompressed(srcPath string, dir *packed.File) error {
// Gzip will gzip input data to a temporary file, and then append that to the // Gzip will gzip input data to a temporary file, and then append that to the
// output file. // output file.
func (p *packer) Gzip(data []byte, dir *packed.File) error { func (p *packer) Gzip(data []byte, dir *packed.File) error {
if len(data) < minCompressionFileSize {
return nil
}
// write via temporary file // write via temporary file
tmpfile, err := ioutil.TempFile("", "") tmpfile, err := ioutil.TempFile("", "")
if err != nil { if err != nil {
@ -483,9 +474,6 @@ func (p *packer) Gzip(data []byte, dir *packed.File) error {
// Brotli will compress input data to a temporary file, and then append that to // Brotli will compress input data to a temporary file, and then append that to
// the output file. // the output file.
func (p *packer) Brotli(data []byte, dir *packed.File) error { func (p *packer) Brotli(data []byte, dir *packed.File) error {
if len(data) < minCompressionFileSize {
return nil
}
// write via temporary file // write via temporary file
tmpfile, err := ioutil.TempFile("", "") tmpfile, err := ioutil.TempFile("", "")
if err != nil { if err != nil {

View File

@ -1,131 +0,0 @@
package main
import (
"bytes"
"slices"
"sync"
"github.com/gosuri/uiprogress"
"src.lwithers.me.uk/go/htpack/cmd/htpacker/packer"
)
type uiProgress struct {
p *uiprogress.Progress
uncompressed, gzip, brotli *uiProgressBar
}
func newUiProgress(ftp packer.FilesToPack) *uiProgress {
up := &uiProgress{
p: uiprogress.New(),
}
up.uncompressed = newUiProgressBar(up.p, len(ftp), "uncompressed")
var nGzip, nBrotli int
for _, f := range ftp {
if !f.DisableCompression && !f.DisableGzip {
nGzip++
}
if !f.DisableCompression && !f.DisableBrotli {
nBrotli++
}
}
if nGzip > 0 {
up.gzip = newUiProgressBar(up.p, nGzip, "gzip")
}
if nBrotli > 0 {
up.brotli = newUiProgressBar(up.p, nGzip, "brotli")
}
up.p.Start()
return up
}
func (up *uiProgress) Count(_ int) {
}
func (up *uiProgress) Begin(filename, compression string) {
up.bar(compression).begin(filename)
}
func (up *uiProgress) End(filename, compression string) {
up.bar(compression).end(filename)
}
func (up *uiProgress) bar(compression string) *uiProgressBar {
switch compression {
case "uncompressed":
return up.uncompressed
case "gzip":
return up.gzip
case "brotli":
return up.brotli
}
return nil
}
func (up *uiProgress) Complete() {
up.p.Stop()
}
type uiProgressBar struct {
bar *uiprogress.Bar
lock sync.Mutex
inflight []string
}
func newUiProgressBar(p *uiprogress.Progress, total int, compression string) *uiProgressBar {
bar := &uiProgressBar{
bar: p.AddBar(total).AppendCompleted(),
}
var buf bytes.Buffer
bar.bar.PrependFunc(func(*uiprogress.Bar) string {
bar.lock.Lock()
defer bar.lock.Unlock()
buf.Reset()
buf.WriteString(compression)
if len(bar.inflight) > 0 {
buf.WriteString(" (")
for i, f := range bar.inflight {
if i > 0 {
buf.WriteString(", ")
}
buf.WriteString(f)
}
buf.WriteRune(')')
}
if buf.Len() < 40 {
buf.WriteString(" ")
buf.Truncate(40)
} else if buf.Len() > 40 {
buf.Truncate(39)
buf.WriteString("…")
}
return buf.String()
})
return bar
}
func (bar *uiProgressBar) begin(filename string) {
if bar == nil {
return
}
bar.lock.Lock()
defer bar.lock.Unlock()
bar.inflight = append(bar.inflight, filename)
}
func (bar *uiProgressBar) end(filename string) {
if bar == nil {
return
}
bar.lock.Lock()
defer bar.lock.Unlock()
bar.bar.Incr()
if idx := slices.Index(bar.inflight, filename); idx != -1 {
bar.inflight = slices.Delete(bar.inflight, idx, idx+1)
}
}

View File

@ -150,7 +150,7 @@ func filesFromListR(prefix, arg string, ftp packer.FilesToPack) error {
case ".css": case ".css":
ctype = "text/css" ctype = "text/css"
case ".js": case ".js":
ctype = "text/javascript" ctype = "application/javascript"
case ".json": case ".json":
ctype = "application/json" ctype = "application/json"
case ".svg": case ".svg":

View File

@ -1,15 +1,8 @@
module src.lwithers.me.uk/go/htpack/cmd/packserver module src.lwithers.me.uk/go/htpack/cmd/packserver
go 1.22 go 1.13
require ( require (
github.com/spf13/cobra v1.8.1 github.com/spf13/cobra v0.0.5
src.lwithers.me.uk/go/htpack v1.3.3 src.lwithers.me.uk/go/htpack v1.2.0
)
require (
github.com/gogo/protobuf v1.3.2 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/sys v0.22.0 // indirect
) )

View File

@ -1,45 +1,42 @@
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1 h1:gZpLHxUX5BdYLA08Lj4YCJNN/jk7KtquiArPoeX0WvA=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
src.lwithers.me.uk/go/htpack v1.3.3 h1:Xvl6qR9HfSblmCgPyu+ACQ9o3aLQSIy3l8CrMbzj/jc= src.lwithers.me.uk/go/htpack v1.2.0 h1:z0bI8KalSD5kPYOcnuqZRPun80Ww0wbnjMkYYuv+xAI=
src.lwithers.me.uk/go/htpack v1.3.3/go.mod h1:qKgCBgZ6iiiuYOxZkYOPVpXLBzp6gXEd4A0ksxgR6Nk= src.lwithers.me.uk/go/htpack v1.2.0/go.mod h1:JWofpm01RJbCTIyKfIPftUsxk6KlFkrYwyHgCVdKY+s=

View File

@ -5,18 +5,12 @@ package main
import ( import (
"bufio" "bufio"
"context"
"errors" "errors"
"fmt" "fmt"
"net/http" "net/http"
"os" "os"
"os/signal"
"path/filepath" "path/filepath"
"sort"
"strings" "strings"
"sync/atomic"
"syscall"
"time"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"src.lwithers.me.uk/go/htpack" "src.lwithers.me.uk/go/htpack"
@ -57,10 +51,6 @@ func main() {
"Tell client how long it can cache data for; 0 means no caching") "Tell client how long it can cache data for; 0 means no caching")
rootCmd.Flags().String("fallback-404", "", rootCmd.Flags().String("fallback-404", "",
"Name of file to return if response would be 404 (spa.html or similar)") "Name of file to return if response would be 404 (spa.html or similar)")
rootCmd.Flags().String("frames", "sameorigin",
"Override X-Frame-Options header (can be sameorigin, deny, allow)")
rootCmd.Flags().Duration("graceful-shutdown-delay", 3*time.Second,
"Number of seconds to wait after receiving SIGTERM before initiating graceful shutdown")
if err := rootCmd.Execute(); err != nil { if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, err)
@ -92,23 +82,6 @@ func run(c *cobra.Command, args []string) error {
certFile = keyFile certFile = keyFile
} }
// parse frames header
framesHeader := "SAMEORIGIN"
frames, err := c.Flags().GetString("frames")
if err != nil {
return err
}
switch frames {
case "sameorigin":
framesHeader = "SAMEORIGIN"
case "allow":
framesHeader = ""
case "deny":
framesHeader = "DENY"
default:
return errors.New("--frames must be one of sameorigin, deny, allow")
}
// parse extra headers // parse extra headers
extraHeaders := make(http.Header) extraHeaders := make(http.Header)
hdrs, err := c.Flags().GetStringSlice("header") hdrs, err := c.Flags().GetStringSlice("header")
@ -159,15 +132,6 @@ func run(c *cobra.Command, args []string) error {
return err return err
} }
// graceful shutdown delay must be > 0
gracefulShutdownDelay, err := c.Flags().GetDuration("graceful-shutdown-delay")
if err != nil {
return err
}
if gracefulShutdownDelay <= 0 {
return errors.New("graceful shutdown delay must be > 0s")
}
// verify .htpack specifications // verify .htpack specifications
if len(args) == 0 { if len(args) == 0 {
return errors.New("must specify one or more .htpack files") return errors.New("must specify one or more .htpack files")
@ -193,7 +157,6 @@ func run(c *cobra.Command, args []string) error {
} }
// load packfiles, registering handlers as we go // load packfiles, registering handlers as we go
router := &routerHandler{}
for prefix, packfile := range packPaths { for prefix, packfile := range packPaths {
packHandler, err := htpack.New(packfile) packHandler, err := htpack.New(packfile)
if err != nil { if err != nil {
@ -206,61 +169,27 @@ func run(c *cobra.Command, args []string) error {
return fmt.Errorf("%s: fallback-404 resource %q "+ return fmt.Errorf("%s: fallback-404 resource %q "+
"not found in packfile", prefix, fallback404File) "not found in packfile", prefix, fallback404File)
} }
packHandler.SetHeader("X-Frame-Options", framesHeader)
var handler http.Handler = &addHeaders{ handler := &addHeaders{
extraHeaders: extraHeaders, extraHeaders: extraHeaders,
handler: packHandler, handler: packHandler,
} }
if prefix != "/" { if prefix != "/" {
handler = http.StripPrefix(prefix, handler) http.Handle(prefix+"/",
http.StripPrefix(prefix, handler))
} else {
http.Handle("/", handler)
} }
router.AddRoute(prefix, handler)
} }
// HTTP server object setup
sv := &http.Server{
Addr: bindAddr,
Handler: router,
}
// register SIGINT, SIGTERM handler
sigch := make(chan os.Signal, 1)
signal.Notify(sigch, syscall.SIGINT, syscall.SIGTERM)
var (
// if we are shut down by a signal, then http.ListenAndServe()
// returns straight away, but we actually need to wait for
// Shutdown() to complete prior to returning / exiting
isSignalled atomic.Bool
signalDone = make(chan struct{})
)
go func() {
<-sigch
time.Sleep(gracefulShutdownDelay)
isSignalled.Store(true)
shutctx, shutcancel := context.WithTimeout(context.Background(), gracefulShutdownDelay)
sv.Shutdown(shutctx)
shutcancel()
close(signalDone)
}()
// main server loop // main server loop
if keyFile == "" { if keyFile == "" {
err = sv.ListenAndServe() err = http.ListenAndServe(bindAddr, nil)
} else { } else {
err = sv.ListenAndServeTLS(certFile, keyFile) err = http.ListenAndServeTLS(bindAddr, certFile, keyFile, nil)
} }
if err != nil {
// if we were shut down by a signal, wait for Shutdown() to return
if isSignalled.Load() {
<-signalDone
}
switch err {
case nil, http.ErrServerClosed:
// OK
default:
fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, err)
os.Exit(1) os.Exit(1)
} }
@ -311,53 +240,3 @@ func (ah *addHeaders) ServeHTTP(w http.ResponseWriter, r *http.Request) {
} }
ah.handler.ServeHTTP(w, r) ah.handler.ServeHTTP(w, r)
} }
// routeEntry is used within routerHandler to map a specific prefix to a
// specific handler.
type routeEntry struct {
// prefix is a path prefix with trailing "/" such as "/foo/".
prefix string
// handler for the request if prefix matches.
handler http.Handler
}
// routerHandler holds a list of routes sorted by longest-prefix-first.
type routerHandler struct {
// entries are the list of prefixes, with longest prefix strings first.
// The sorting ensures we can iterate through from the start and match
// "/dir/subdir/" in preference to just "/dir/".
entries []routeEntry
}
// AddRoute adds a new entry into the handler. It is not concurrency safe; the
// handler should not be in use.
func (rh *routerHandler) AddRoute(prefix string, handler http.Handler) {
if !strings.HasSuffix(prefix, "/") {
prefix += "/"
}
rh.entries = append(rh.entries, routeEntry{
prefix: prefix,
handler: handler,
})
sort.Slice(rh.entries, func(i, j int) bool {
l1, l2 := len(rh.entries[i].prefix), len(rh.entries[j].prefix)
if l1 > l2 {
return true
}
if l1 == l2 {
return rh.entries[i].prefix < rh.entries[j].prefix
}
return false
})
}
func (rh *routerHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
for _, entry := range rh.entries {
if strings.HasPrefix(r.URL.Path, entry.prefix) {
entry.handler.ServeHTTP(w, r)
return
}
}
http.NotFound(w, r)
}

4
go.mod
View File

@ -2,7 +2,7 @@ module src.lwithers.me.uk/go/htpack
require ( require (
github.com/gogo/protobuf v1.3.2 github.com/gogo/protobuf v1.3.2
golang.org/x/sys v0.22.0 golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e
) )
go 1.22 go 1.13

4
go.sum
View File

@ -19,8 +19,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e h1:CsOuNlbOuf0mzxJIefr6Q4uAUetRUwZE4qt7VfzP+xo=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

View File

@ -15,15 +15,11 @@
*/ */
package packed package packed
import ( import proto "github.com/gogo/protobuf/proto"
fmt "fmt" import fmt "fmt"
import math "math"
proto "github.com/gogo/protobuf/proto" import io "io"
math "math"
io "io"
)
// Reference imports to suppress errors if they are not otherwise used. // Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal var _ = proto.Marshal