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) }