mirror of
https://github.com/hanzoai/pack.git
synced 2026-09-30 23:43:13 +00:00
220 lines
4.6 KiB
Go
220 lines
4.6 KiB
Go
package app
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/BurntSushi/toml"
|
|
"github.com/bmatcuk/doublestar/v4"
|
|
"github.com/tailscale/hujson"
|
|
"gopkg.in/yaml.v2"
|
|
)
|
|
|
|
type App struct {
|
|
Source string
|
|
}
|
|
|
|
func NewApp(path string) (*App, error) {
|
|
var source string
|
|
|
|
if filepath.IsAbs(path) {
|
|
source = path
|
|
} else {
|
|
currentDir, err := os.Getwd()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
source, err = filepath.Abs(filepath.Join(currentDir, path))
|
|
if err != nil {
|
|
return nil, errors.New("failed to read app source directory")
|
|
}
|
|
}
|
|
|
|
if _, err := os.Stat(source); err != nil {
|
|
if os.IsNotExist(err) {
|
|
return nil, fmt.Errorf("directory %s does not exist", source)
|
|
}
|
|
return nil, fmt.Errorf("failed to check directory %s: %w", source, err)
|
|
}
|
|
|
|
return &App{Source: source}, nil
|
|
}
|
|
|
|
// findMatches returns a list of paths matching a glob pattern, filtered by isDir
|
|
func (a *App) findMatches(pattern string, isDir bool) ([]string, error) {
|
|
matches, err := a.findGlob(pattern)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var paths []string
|
|
for _, match := range matches {
|
|
fullPath := filepath.Join(a.Source, match)
|
|
|
|
info, err := os.Stat(fullPath)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
if info.IsDir() == isDir {
|
|
paths = append(paths, match)
|
|
}
|
|
}
|
|
return paths, nil
|
|
}
|
|
|
|
// FindFiles returns a list of file paths matching a glob pattern
|
|
func (a *App) FindFiles(pattern string) ([]string, error) {
|
|
return a.findMatches(pattern, false)
|
|
}
|
|
|
|
// FindDirectories returns a list of directory paths matching a glob pattern
|
|
func (a *App) FindDirectories(pattern string) ([]string, error) {
|
|
return a.findMatches(pattern, true)
|
|
}
|
|
|
|
// findGlob finds paths matching a glob pattern
|
|
func (a *App) findGlob(pattern string) ([]string, error) {
|
|
matches, err := doublestar.Glob(os.DirFS(a.Source), pattern)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return matches, nil
|
|
}
|
|
|
|
// HasMatch checks if a path matching a glob exists (files or directories)
|
|
func (a *App) HasMatch(pattern string) bool {
|
|
files, err := a.FindFiles(pattern)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
dirs, err := a.FindDirectories(pattern)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
return len(files) > 0 || len(dirs) > 0
|
|
}
|
|
|
|
func (a *App) FindFilesWithContent(pattern string, regex *regexp.Regexp) []string {
|
|
files, err := a.FindFiles(pattern)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
var matches []string
|
|
for _, file := range files {
|
|
content, err := a.ReadFile(file)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
if regex.MatchString(content) {
|
|
matches = append(matches, file)
|
|
}
|
|
}
|
|
|
|
return matches
|
|
}
|
|
|
|
// ReadFile reads the contents of a file
|
|
func (a *App) ReadFile(name string) (string, error) {
|
|
path := filepath.Join(a.Source, name)
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
relativePath, _ := a.stripSourcePath(path)
|
|
return "", fmt.Errorf("error reading %s: %w", relativePath, err)
|
|
}
|
|
|
|
return strings.ReplaceAll(string(data), "\r\n", "\n"), nil
|
|
}
|
|
|
|
// ReadJSON reads and parses a JSON file
|
|
func (a *App) ReadJSON(name string, v interface{}) error {
|
|
data, err := a.ReadFile(name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
jsonBytes, err := standardizeJSON([]byte(data))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
data = string(jsonBytes)
|
|
|
|
if err := json.Unmarshal([]byte(data), v); err != nil {
|
|
relativePath, _ := a.stripSourcePath(filepath.Join(a.Source, name))
|
|
return fmt.Errorf("error reading %s as JSON: %w", relativePath, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ReadYAML reads and parses a YAML file
|
|
func (a *App) ReadYAML(name string, v interface{}) error {
|
|
data, err := a.ReadFile(name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := yaml.Unmarshal([]byte(data), v); err != nil {
|
|
return fmt.Errorf("error reading %s as YAML: %w", name, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ReadTOML reads and parses a TOML file
|
|
func (a *App) ReadTOML(name string, v interface{}) error {
|
|
data, err := a.ReadFile(name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return toml.Unmarshal([]byte(data), v)
|
|
}
|
|
|
|
// IsFileExecutable checks if a path is an executable file
|
|
func (a *App) IsFileExecutable(name string) bool {
|
|
path := filepath.Join(a.Source, name)
|
|
info, err := os.Stat(path)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
if !info.Mode().IsRegular() {
|
|
return false
|
|
}
|
|
|
|
// Check executable bit
|
|
return info.Mode()&0111 != 0
|
|
}
|
|
|
|
// StripSourcePath converts an absolute path to a path relative to the app source directory
|
|
func (a *App) stripSourcePath(absPath string) (string, error) {
|
|
rel, err := filepath.Rel(a.Source, absPath)
|
|
if err != nil {
|
|
return "", errors.New("failed to parse source path")
|
|
}
|
|
return rel, nil
|
|
}
|
|
|
|
func standardizeJSON(b []byte) ([]byte, error) {
|
|
ast, err := hujson.Parse(b)
|
|
if err != nil {
|
|
return b, err
|
|
}
|
|
ast.Standardize()
|
|
return ast.Pack(), nil
|
|
}
|