Git Repository
Stackyard
Stackyard scannt lokale Entwicklungsordner und bündelt Projekte, Git-Status, TODOs, Toolchains, Docker-Compose-Setups, Datei- und Launcher-Funktionen sowie Operations-Informationen in einer lokalen WebUI.
HTTPS
https://zanvex.de/git/stackyard.gitinternal/config/config.go
Zum Verzeichnispackage config
import (
"errors"
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
"gopkg.in/yaml.v3"
)
var defaultIgnoreDirs = []string{
"node_modules",
".venv",
"venv",
"vendor",
"target",
"dist",
"build",
".cache",
}
type Config struct {
Server ServerConfig `yaml:"server"`
Database DatabaseConfig `yaml:"database"`
Roots []RootConfig `yaml:"roots"`
Scanner ScannerConfig `yaml:"scanner"`
Launchers LaunchersConfig `yaml:"launchers"`
Monitoring MonitoringConfig `yaml:"monitoring"`
}
type MonitoringConfig struct {
RefreshSeconds int `yaml:"refresh_seconds"`
}
type ServerConfig struct {
Listen string `yaml:"listen"`
}
type DatabaseConfig struct {
Path string `yaml:"path"`
}
type RootConfig struct {
Name string `yaml:"name"`
Path string `yaml:"path"`
}
type ScannerConfig struct {
MaxDepth int `yaml:"max_depth"`
IgnoreDirs []string `yaml:"ignore_dirs"`
}
type LaunchersConfig struct {
VSCode LauncherConfig `yaml:"vscode"`
Sublime LauncherConfig `yaml:"sublime"`
Terminal LauncherConfig `yaml:"terminal"`
Explorer LauncherConfig `yaml:"explorer"`
}
type LauncherConfig struct {
Enabled bool `yaml:"enabled"`
Command string `yaml:"command"`
}
func Load(path string) (Config, error) {
if path == "" {
path = DefaultPath()
}
data, err := os.ReadFile(path)
if err != nil {
return Config{}, fmt.Errorf("read config %q: %w", path, err)
}
var cfg Config
if err := yaml.Unmarshal(data, &cfg); err != nil {
return Config{}, fmt.Errorf("parse config %q: %w", path, err)
}
baseDir := filepath.Dir(path)
if err := cfg.applyDefaults(baseDir); err != nil {
return Config{}, err
}
return cfg, nil
}
func DefaultPath() string {
executable, err := os.Executable()
return defaultPath(executable, err)
}
func defaultPath(executable string, err error) string {
if err != nil || strings.TrimSpace(executable) == "" {
return "config.yaml"
}
return filepath.Join(filepath.Dir(executable), "config.yaml")
}
func (c *Config) applyDefaults(baseDir string) error {
if c.Server.Listen == "" {
c.Server.Listen = "127.0.0.1:8799"
}
if c.Scanner.MaxDepth <= 0 {
c.Scanner.MaxDepth = 2
}
if c.Monitoring.RefreshSeconds <= 0 {
c.Monitoring.RefreshSeconds = 10
}
if len(c.Scanner.IgnoreDirs) == 0 {
c.Scanner.IgnoreDirs = append([]string(nil), defaultIgnoreDirs...)
}
if len(c.Roots) == 0 {
return errors.New("config requires at least one root")
}
for i := range c.Roots {
root := &c.Roots[i]
if root.Name == "" {
root.Name = fmt.Sprintf("root-%d", i+1)
}
if root.Path == "" {
return fmt.Errorf("root %q has empty path", root.Name)
}
resolved, err := expandPath(root.Path, baseDir)
if err != nil {
return fmt.Errorf("resolve root %q path: %w", root.Name, err)
}
root.Path = resolved
}
if c.Database.Path != "" {
resolved, err := expandPath(c.Database.Path, baseDir)
if err != nil {
return fmt.Errorf("resolve database path: %w", err)
}
c.Database.Path = resolved
}
applyLauncherDefault(&c.Launchers.VSCode, "code")
applyLauncherDefault(&c.Launchers.Sublime, "subl")
if !c.Launchers.Terminal.Enabled {
c.Launchers.Terminal.Enabled = true
}
if !c.Launchers.Explorer.Enabled {
c.Launchers.Explorer.Enabled = true
}
return nil
}
func applyLauncherDefault(cfg *LauncherConfig, command string) {
if !cfg.Enabled && strings.TrimSpace(cfg.Command) == "" {
cfg.Enabled = true
}
if strings.TrimSpace(cfg.Command) == "" {
cfg.Command = command
}
}
func expandPath(value, baseDir string) (string, error) {
if value == "" {
return "", nil
}
if translated, ok := translateWindowsPath(value); ok {
value = translated
}
if strings.HasPrefix(value, "~/") || value == "~" {
home, err := os.UserHomeDir()
if err != nil {
return "", fmt.Errorf("determine home directory: %w", err)
}
if value == "~" {
value = home
} else {
value = filepath.Join(home, value[2:])
}
}
if filepath.IsAbs(value) {
return filepath.Clean(value), nil
}
return filepath.Clean(filepath.Join(baseDir, value)), nil
}
func translateWindowsPath(value string) (string, bool) {
if len(value) < 3 {
return "", false
}
normalized := strings.ReplaceAll(value, "\\", "/")
drive := normalized[0]
if normalized[1] != ':' || normalized[2] != '/' {
return "", false
}
if !((drive >= 'A' && drive <= 'Z') || (drive >= 'a' && drive <= 'z')) {
return "", false
}
if runtime.GOOS == "windows" {
return filepath.Clean(normalized), true
}
mountDrive := strings.ToLower(string(drive))
candidate := filepath.Join("/mnt", mountDrive, strings.TrimPrefix(normalized[3:], "/"))
if _, err := os.Stat(filepath.Join("/mnt", mountDrive)); err == nil {
return filepath.Clean(candidate), true
}
return filepath.Clean(candidate), true
}