Implement yaml format

This commit is contained in:
2025-02-26 11:14:36 +01:00
parent edaa699c20
commit 125bf78c16
9 changed files with 269 additions and 42 deletions

View File

@@ -5,17 +5,21 @@ import (
"log"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"gopkg.in/yaml.v3"
)
type LinkInstruction struct {
Source string
Target string
Force bool
Hard bool
Delete bool
Source string `yaml:"source"`
Target string `yaml:"target"`
Force bool `yaml:"force,omitempty"`
Hard bool `yaml:"hard,omitempty"`
Delete bool `yaml:"delete,omitempty"`
}
type YAMLConfig struct {
Links []LinkInstruction `yaml:"links"`
}
func (instruction *LinkInstruction) Tidy() {
@@ -29,7 +33,26 @@ func (instruction *LinkInstruction) Tidy() {
}
func (instruction *LinkInstruction) String() string {
return fmt.Sprintf("%s%s%s%s%s%s%s%s%s%s%s%s%s", SourceColor, instruction.Source, DefaultColor, deliminer, TargetColor, instruction.Target, DefaultColor, deliminer, strconv.FormatBool(instruction.Force), deliminer, strconv.FormatBool(instruction.Hard), deliminer, strconv.FormatBool(instruction.Delete))
var flags []string
if instruction.Force {
flags = append(flags, "force=true")
}
if instruction.Hard {
flags = append(flags, "hard=true")
}
if instruction.Delete {
flags = append(flags, "delete=true")
}
flagsStr := ""
if len(flags) > 0 {
flagsStr = " [" + strings.Join(flags, ", ") + "]"
}
return fmt.Sprintf("%s%s%s → %s%s%s%s",
SourceColor, instruction.Source, DefaultColor,
TargetColor, instruction.Target, DefaultColor,
flagsStr)
}
func ParseInstruction(line, workdir string) (LinkInstruction, error) {
@@ -37,6 +60,7 @@ func ParseInstruction(line, workdir string) (LinkInstruction, error) {
if strings.HasPrefix(line, "#") {
return LinkInstruction{}, fmt.Errorf("comment line")
}
parts := strings.Split(line, deliminer)
instruction := LinkInstruction{}
@@ -46,19 +70,48 @@ func ParseInstruction(line, workdir string) (LinkInstruction, error) {
instruction.Source = strings.TrimSpace(parts[0])
instruction.Target = strings.TrimSpace(parts[1])
instruction.Force = false
if len(parts) > 2 {
res, _ := regexp.MatchString(`^(?i)\s*T|TRUE\s*$`, parts[2])
instruction.Force = res
}
if len(parts) > 3 {
res, _ := regexp.MatchString(`^(?i)\s*T|TRUE\s*$`, parts[3])
instruction.Hard = res
}
if len(parts) > 4 {
res, _ := regexp.MatchString(`^(?i)\s*T|TRUE\s*$`, parts[4])
instruction.Delete = res
instruction.Force = res
for i := 2; i < len(parts); i++ {
flagPart := strings.TrimSpace(parts[i])
// Support for legacy format (backward compatibility)
if !strings.Contains(flagPart, "=") {
// Legacy format: positional boolean flags
switch i {
case 2: // Force flag (3rd position)
instruction.Force = isTrue(flagPart)
case 3: // Hard flag (4th position)
instruction.Hard = isTrue(flagPart)
case 4: // Delete flag (5th position)
instruction.Delete = isTrue(flagPart)
if instruction.Delete {
instruction.Force = true // Delete implies Force
}
}
continue
}
// New format: named flags (name=value)
nameValue := strings.SplitN(flagPart, "=", 2)
if len(nameValue) != 2 {
// Skip malformed flags
continue
}
flagName := strings.ToLower(strings.TrimSpace(nameValue[0]))
flagValue := strings.TrimSpace(nameValue[1])
switch flagName {
case "force", "f":
instruction.Force = isTrue(flagValue)
case "hard", "h":
instruction.Hard = isTrue(flagValue)
case "delete", "d":
instruction.Delete = isTrue(flagValue)
if instruction.Delete {
instruction.Force = true // Delete implies Force
}
}
}
instruction.Tidy()
@@ -71,6 +124,11 @@ func ParseInstruction(line, workdir string) (LinkInstruction, error) {
return instruction, nil
}
func isTrue(value string) bool {
value = strings.ToLower(strings.TrimSpace(value))
return value == "true" || value == "t" || value == "yes" || value == "y" || value == "1"
}
func (instruction *LinkInstruction) RunAsync(status chan (error)) {
defer close(status)
if !FileExists(instruction.Source) {
@@ -155,3 +213,43 @@ func (instruction *LinkInstruction) RunAsync(status chan (error)) {
status <- nil
}
func ParseYAMLFile(filename, workdir string) ([]LinkInstruction, error) {
data, err := os.ReadFile(filename)
if err != nil {
return nil, fmt.Errorf("error reading YAML file: %w", err)
}
// First try to parse as a list of link instructions
var config YAMLConfig
err = yaml.Unmarshal(data, &config)
if err != nil || len(config.Links) == 0 {
// If that fails, try parsing as a direct list of instructions
var instructions []LinkInstruction
err = yaml.Unmarshal(data, &instructions)
if err != nil {
return nil, fmt.Errorf("error parsing YAML: %w", err)
}
config.Links = instructions
}
for i := range config.Links {
config.Links[i].Tidy()
config.Links[i].Source, _ = ConvertHome(config.Links[i].Source)
config.Links[i].Target, _ = ConvertHome(config.Links[i].Target)
config.Links[i].Source = NormalizePath(config.Links[i].Source, workdir)
config.Links[i].Target = NormalizePath(config.Links[i].Target, workdir)
// If Delete is true, Force must also be true
if config.Links[i].Delete {
config.Links[i].Force = true
}
}
return config.Links, nil
}
func IsYAMLFile(filename string) bool {
ext := strings.ToLower(filepath.Ext(filename))
return ext == ".yaml" || ext == ".yml"
}