htpack/cmd/htpacker/inspector.go

125 lines
2.9 KiB
Go

package main
import (
"errors"
"fmt"
"os"
"github.com/spf13/cobra"
"src.lwithers.me.uk/go/htpack/packed"
)
var inspectCmd = &cobra.Command{
Use: "inspect",
Short: "View contents of an htpack file",
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return errors.New("must specify one or more files")
}
var exitCode int
for _, filename := range args {
if err := Inspect(filename); err != nil {
fmt.Fprintf(os.Stderr, "%s: %v\n",
filename, err)
exitCode = 1
}
}
os.Exit(exitCode)
return nil
},
}
// Inspect a packfile.
// TODO: verify etag; verify integrity of compressed data.
func Inspect(filename string) error {
f, err := os.Open(filename)
if err != nil {
return err
}
defer f.Close()
hdr, dir, err := packed.Load(f)
if hdr != nil {
fmt.Printf("Header: %#v\n", hdr)
}
if dir != nil {
fmt.Printf("%d files:\n", len(dir.Files))
for path, info := range dir.Files {
fmt.Printf(" • %s\n"+
" · Etag: %s\n"+
" · Content type: %s\n"+
" · Uncompressed: %s (offset %d)\n",
path, info.Etag, info.ContentType,
printSize(info.Uncompressed.Length),
info.Uncompressed.Offset)
if info.Gzip != nil {
fmt.Printf(" · Gzipped: %s (offset %d)\n",
printSize(info.Gzip.Length), info.Gzip.Offset)
}
if info.Brotli != nil {
fmt.Printf(" · Brotli: %s (offset %d)\n",
printSize(info.Brotli.Length), info.Brotli.Offset)
}
}
inspectSummary(dir)
}
return err
}
func inspectSummary(dir *packed.Directory) {
var (
n, ngzip, nbrotli int
s, sgzip, sbrotli uint64
)
for _, f := range dir.Files {
n++
s += f.Uncompressed.Length
if f.Gzip != nil {
ngzip++
sgzip += f.Gzip.Length
}
if f.Brotli != nil {
nbrotli++
sbrotli += f.Brotli.Length
}
}
fmt.Printf("Uncompressed:\n\tFiles: %d\n\tSize: %s\n",
n, printSize(s))
if ngzip > 0 {
fmt.Printf("gzip compressed:\n\tFiles: %d (%.1f%% of total)\n"+
"\tSize: %s\n\tRatio: %.1f%%\n",
ngzip, 100*float64(ngzip)/float64(n),
printSize(sgzip), 100*float64(sgzip)/float64(s))
}
if nbrotli > 0 {
fmt.Printf("brotli compressed:\n\tFiles: %d (%.1f%% of total)\n"+
"\tSize: %s\n\tRatio: %.1f%%\n",
nbrotli, 100*float64(nbrotli)/float64(n),
printSize(sbrotli), 100*float64(sbrotli)/float64(s))
}
}
func printSize(size uint64) string {
switch {
case size < 1<<10:
return fmt.Sprintf("%d bytes", size)
case size < 1<<15:
return fmt.Sprintf("%.2f KiB", float64(size)/(1<<10))
case size < 1<<20:
return fmt.Sprintf("%.1f KiB", float64(size)/(1<<10))
case size < 1<<25:
return fmt.Sprintf("%.2f MiB", float64(size)/(1<<20))
case size < 1<<30:
return fmt.Sprintf("%.1f MiB", float64(size)/(1<<20))
case size < 1<<35:
return fmt.Sprintf("%.2f GiB", float64(size)/(1<<30))
default:
return fmt.Sprintf("%.1f GiB", float64(size)/(1<<30))
}
}