229 lines
5.0 KiB
Go
229 lines
5.0 KiB
Go
package core
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"regexp"
|
|
"runtime"
|
|
"strings"
|
|
|
|
lua "github.com/yuin/gopher-lua"
|
|
)
|
|
|
|
const (
|
|
ConfigFileName = "config.lua"
|
|
FilesDirName = "files"
|
|
)
|
|
|
|
type packageConfig struct {
|
|
targets map[string]string
|
|
disabled map[string]bool
|
|
ignore []string
|
|
compiledIgnores []*regexp.Regexp
|
|
}
|
|
|
|
var ErrTargetDisabled = errors.New("target disabled for this platform")
|
|
|
|
func LoadConfig(path string) (*packageConfig, error) {
|
|
L := lua.NewState()
|
|
defer L.Close()
|
|
L.OpenLibs()
|
|
|
|
if err := L.DoFile(path); err != nil {
|
|
return nil, err
|
|
}
|
|
if L.GetTop() == 0 {
|
|
return nil, errors.New("config.lua must return a table")
|
|
}
|
|
|
|
value := L.Get(-1)
|
|
tbl, ok := value.(*lua.LTable)
|
|
if !ok {
|
|
return nil, errors.New("config.lua must return a table")
|
|
}
|
|
|
|
targetVal := tbl.RawGetString("target")
|
|
targetTbl, ok := targetVal.(*lua.LTable)
|
|
if !ok {
|
|
return nil, errors.New("config.target must be a table")
|
|
}
|
|
|
|
targets := make(map[string]string)
|
|
disabled := make(map[string]bool)
|
|
targetTbl.ForEach(func(k, v lua.LValue) {
|
|
ks, ok := k.(lua.LString)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
if v == lua.LNil || v == lua.LFalse {
|
|
disabled[string(ks)] = true
|
|
return
|
|
}
|
|
|
|
vs, ok := v.(lua.LString)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
targets[string(ks)] = ExpandHome(string(vs))
|
|
})
|
|
|
|
if len(targets) == 0 && len(disabled) == 0 {
|
|
return nil, errors.New("config.target is empty")
|
|
}
|
|
|
|
ignore, err := ParseIgnore(tbl)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
compiledIgnores, err := CompileIgnorePatterns(ignore)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &packageConfig{targets: targets, disabled: disabled, ignore: ignore, compiledIgnores: compiledIgnores}, nil
|
|
}
|
|
|
|
func SelectTarget(cfg *packageConfig) (string, error) {
|
|
osKey := OSKey()
|
|
if cfg.disabled[osKey] {
|
|
return "", ErrTargetDisabled
|
|
}
|
|
if target, ok := cfg.targets[osKey]; ok {
|
|
return ExpandHome(target), nil
|
|
}
|
|
if target, ok := cfg.targets["default"]; ok {
|
|
return ExpandHome(target), nil
|
|
}
|
|
return "", fmt.Errorf("missing target for %s and default", osKey)
|
|
}
|
|
|
|
func OSKey() string {
|
|
osKey := runtime.GOOS
|
|
if osKey == "darwin" {
|
|
osKey = "macos"
|
|
}
|
|
return osKey
|
|
}
|
|
|
|
func VariantDirName() string {
|
|
return FilesDirName + "." + OSKey()
|
|
}
|
|
|
|
func ParseIgnore(cfgTbl *lua.LTable) ([]string, error) {
|
|
ignoreVal := cfgTbl.RawGetString("ignore")
|
|
if ignoreVal == lua.LNil {
|
|
return nil, nil
|
|
}
|
|
|
|
ignoreTbl, ok := ignoreVal.(*lua.LTable)
|
|
if !ok {
|
|
return nil, errors.New("config.ignore must be an array of strings")
|
|
}
|
|
|
|
ignore := make([]string, 0, ignoreTbl.Len())
|
|
var parseErr error
|
|
ignoreTbl.ForEach(func(k, v lua.LValue) {
|
|
if parseErr != nil {
|
|
return
|
|
}
|
|
|
|
if _, ok := k.(lua.LNumber); !ok {
|
|
parseErr = errors.New("config.ignore must be an array of strings")
|
|
return
|
|
}
|
|
|
|
s, ok := v.(lua.LString)
|
|
if !ok {
|
|
parseErr = errors.New("config.ignore must contain only strings")
|
|
return
|
|
}
|
|
|
|
pattern := strings.TrimSpace(string(s))
|
|
if pattern == "" {
|
|
parseErr = errors.New("config.ignore cannot contain empty patterns")
|
|
return
|
|
}
|
|
|
|
ignore = append(ignore, pattern)
|
|
})
|
|
if parseErr != nil {
|
|
return nil, parseErr
|
|
}
|
|
return ignore, nil
|
|
}
|
|
|
|
func CompileIgnorePatterns(patterns []string) ([]*regexp.Regexp, error) {
|
|
compiled := make([]*regexp.Regexp, 0, len(patterns))
|
|
for _, pattern := range patterns {
|
|
re, err := GlobToRegexp(pattern)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid ignore pattern %q: %w", pattern, err)
|
|
}
|
|
compiled = append(compiled, re)
|
|
}
|
|
return compiled, nil
|
|
}
|
|
|
|
func GlobToRegexp(pattern string) (*regexp.Regexp, error) {
|
|
pattern = strings.ReplaceAll(pattern, "\\", "/")
|
|
pattern = strings.TrimPrefix(pattern, "./")
|
|
if strings.HasPrefix(pattern, "/") {
|
|
pattern = strings.TrimPrefix(pattern, "/")
|
|
}
|
|
|
|
var b strings.Builder
|
|
b.WriteString("^")
|
|
for i := 0; i < len(pattern); {
|
|
if i+1 < len(pattern) && pattern[i] == '*' && pattern[i+1] == '*' {
|
|
b.WriteString(".*")
|
|
i += 2
|
|
continue
|
|
}
|
|
|
|
ch := pattern[i]
|
|
switch ch {
|
|
case '*':
|
|
b.WriteString("[^/]*")
|
|
case '?':
|
|
b.WriteString("[^/]")
|
|
default:
|
|
b.WriteString(regexp.QuoteMeta(string(ch)))
|
|
}
|
|
i++
|
|
}
|
|
b.WriteString("$")
|
|
|
|
return regexp.Compile(b.String())
|
|
}
|
|
|
|
func ShouldIgnorePath(rel string, cfg *packageConfig) bool {
|
|
if cfg == nil || len(cfg.compiledIgnores) == 0 {
|
|
return false
|
|
}
|
|
|
|
normalized := path.Clean(filepath.ToSlash(rel))
|
|
for _, re := range cfg.compiledIgnores {
|
|
if re.MatchString(normalized) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func WriteConfig(path, targetRoot string) error {
|
|
osKey := "linux"
|
|
if runtime.GOOS == "darwin" {
|
|
osKey = "macos"
|
|
}
|
|
|
|
prettyTarget := CompressHome(targetRoot)
|
|
content := fmt.Sprintf("---@class SigilConfig\n---@field target table<string, string|boolean>\n---@field ignore? string[]\n\n---@type SigilConfig\nlocal config = {\n\ttarget = {\n\t\t%s = %q,\n\t\tdefault = %q,\n\t},\n\tignore = {\n\t\t-- \"**/.DS_Store\",\n\t\t-- \"**/*.tmp\",\n\t\t-- \"cache/**\",\n\t},\n}\n\nreturn config\n", osKey, prettyTarget, prettyTarget)
|
|
return os.WriteFile(path, []byte(content), 0o644)
|
|
}
|