You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
115 lines
3.8 KiB
Go
115 lines
3.8 KiB
Go
12 years ago
|
package am
|
||
|
|
||
|
import (
|
||
|
"io/ioutil"
|
||
|
"os"
|
||
|
"path"
|
||
|
"path/filepath"
|
||
|
"strings"
|
||
|
)
|
||
|
|
||
|
// an asset manager is responsible for managing references to files contained
|
||
|
// in source-controlled directories that associate to Go packages. I suspect
|
||
|
// that there's some overlap here with the standard library go/build package.
|
||
|
type Manager struct {
|
||
|
basePath string // base path on the local filesystem
|
||
|
baseURLPath string // base path for public-facing URLS
|
||
|
}
|
||
|
|
||
|
// given an import path for a Go package, builds an asset manager for that
|
||
|
// package. Doesn't actually check if that package exists; this is kinda
|
||
|
// hazardous right now. ¯\_(ツ)_/¯
|
||
|
func New(importPath, urlBase string) *Manager {
|
||
|
return &Manager{basePath: pkgDir(importPath), baseURLPath: urlBase}
|
||
|
}
|
||
|
|
||
|
func (m *Manager) AbsPath(parts ...string) string {
|
||
|
return filepath.Join(m.basePath, filepath.Join(parts...))
|
||
|
}
|
||
|
|
||
|
func (m *Manager) Open(parts ...string) (*os.File, error) {
|
||
|
return os.Open(m.AbsPath(parts...))
|
||
|
}
|
||
|
|
||
|
func (m *Manager) URLPath(parts ...string) string {
|
||
|
return path.Join(m.baseURLPath, path.Join(parts...))
|
||
|
}
|
||
|
|
||
|
func (m *Manager) ReadFile(parts ...string) ([]byte, error) {
|
||
|
f, err := m.Open(parts...)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
defer f.Close()
|
||
|
return ioutil.ReadAll(f)
|
||
|
}
|
||
|
|
||
|
// getPathDirs gets the user's GOPATH environment variable and splits it along
|
||
|
// the OS-specific path separator, returning a slice of strings, one per path
|
||
|
// directory.
|
||
|
func getPathDirs() []string {
|
||
|
path := os.Getenv("GOPATH")
|
||
|
if path == "" {
|
||
|
panic("am: GOPATH not set")
|
||
|
}
|
||
|
return strings.Split(path, string(os.PathListSeparator))
|
||
|
}
|
||
|
|
||
|
// getPkgDirCandidates gets the list of possible locations for a given import
|
||
|
// string. The paths are not guaranteed to exist or to even be valid; results
|
||
|
// are derived from the user's $GOPATH environment variable, which is not
|
||
|
// necessarily clean.
|
||
|
func getPkgDirCandidates(importPath string) []string {
|
||
|
pathDirs := getPathDirs()
|
||
|
importParts := strings.Split(importPath, "/")
|
||
|
candidates := make([]string, 0, len(pathDirs))
|
||
|
for _, pathDir := range pathDirs {
|
||
|
srcDir := filepath.Join(pathDir, "src")
|
||
|
candidates = append(candidates, filepath.Join(srcDir, filepath.Join(importParts...)))
|
||
|
}
|
||
|
return candidates
|
||
|
}
|
||
|
|
||
|
// existingDirectories accepts a slice of strings and returns a slice of
|
||
|
// strings representing which of the given strings corresponds to an existing
|
||
|
// directory. Note that this is prone to timing attacks, but it is presumed to
|
||
|
// not matter for this application; this may be an unsafe strategy to copy into
|
||
|
// other projects.
|
||
|
func existingDirectories(candidates []string) []string {
|
||
|
dirs := make([]string, 0, len(candidates))
|
||
|
for _, path := range candidates {
|
||
|
if isDir(path) {
|
||
|
dirs = append(dirs, path)
|
||
|
}
|
||
|
}
|
||
|
return dirs
|
||
|
}
|
||
|
|
||
|
// isDir takes a file path and returns a boolean representing whether the path
|
||
|
// is or is not a valid directory. Again, this is vulnerable to timing attacks
|
||
|
// and should be considered a Very Bad Idea in most contexts.
|
||
|
func isDir(path string) bool {
|
||
|
fi, err := os.Stat(path)
|
||
|
if err != nil {
|
||
|
return false
|
||
|
}
|
||
|
return fi.IsDir()
|
||
|
}
|
||
|
|
||
|
// getPkgDir takes an import path and returns a string representing the
|
||
|
// directory path of the package on the current machine. This is generally a
|
||
|
// stupid thing to do, because we're generally not worried about the location
|
||
|
// of a package's source code files, since they may be out of sync with the
|
||
|
// actual binary, but in our case, we're using it to look up assets that have
|
||
|
// been made go-gettable. If no package dir can be found, an empty string is
|
||
|
// returned. The package may reasonably be installed into multiple workspaces.
|
||
|
// In this case, it's the first package found, as dictated by the user's
|
||
|
// $GOPATH environment variable.
|
||
|
func pkgDir(importPath string) string {
|
||
|
dirs := existingDirectories(getPkgDirCandidates(importPath))
|
||
|
if len(dirs) == 0 {
|
||
|
return ""
|
||
|
}
|
||
|
return dirs[0]
|
||
|
}
|