Compare commits
	
		
			9 Commits
		
	
	
		
			ae1986cb81
			...
			v1.0.0
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 8da1f023a7 | |||
| 33b3a3d2b6 | |||
| 78536c3e19 | |||
| 3a5a333c62 | |||
| 5a2520e3b1 | |||
| 12d71dba1c | |||
| d94f8db27a | |||
| 913a279011 | |||
| af956110be | 
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -2,3 +2,4 @@
 | 
				
			|||||||
*.exe
 | 
					*.exe
 | 
				
			||||||
cln
 | 
					cln
 | 
				
			||||||
cln.log
 | 
					cln.log
 | 
				
			||||||
 | 
					.qodo
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										2
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								go.mod
									
									
									
									
									
								
							@@ -3,3 +3,5 @@ module cln
 | 
				
			|||||||
go 1.21.7
 | 
					go 1.21.7
 | 
				
			||||||
 | 
					
 | 
				
			||||||
require gopkg.in/yaml.v3 v3.0.1
 | 
					require gopkg.in/yaml.v3 v3.0.1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					require github.com/bmatcuk/doublestar/v4 v4.8.1
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										2
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								go.sum
									
									
									
									
									
								
							@@ -1,3 +1,5 @@
 | 
				
			|||||||
 | 
					github.com/bmatcuk/doublestar/v4 v4.8.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR5wKP38=
 | 
				
			||||||
 | 
					github.com/bmatcuk/doublestar/v4 v4.8.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
 | 
				
			||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 | 
					gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 | 
				
			||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
					gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
				
			||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 | 
					gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										140
									
								
								instruction.go
									
									
									
									
									
								
							
							
						
						
									
										140
									
								
								instruction.go
									
									
									
									
									
								
							@@ -4,9 +4,9 @@ import (
 | 
				
			|||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"path/filepath"
 | 
						"path/filepath"
 | 
				
			||||||
	"slices"
 | 
					 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/bmatcuk/doublestar/v4"
 | 
				
			||||||
	"gopkg.in/yaml.v3"
 | 
						"gopkg.in/yaml.v3"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -49,12 +49,38 @@ func (instruction *LinkInstruction) String() string {
 | 
				
			|||||||
		flagsStr = " [" + strings.Join(flags, ", ") + "]"
 | 
							flagsStr = " [" + strings.Join(flags, ", ") + "]"
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return fmt.Sprintf("%s%s%s → %s%s%s%s",
 | 
						return fmt.Sprintf("%s → %s%s",
 | 
				
			||||||
		SourceColor, instruction.Source, DefaultColor,
 | 
							FormatSourcePath(instruction.Source),
 | 
				
			||||||
		TargetColor, instruction.Target, DefaultColor,
 | 
							FormatTargetPath(instruction.Target),
 | 
				
			||||||
		flagsStr)
 | 
							flagsStr)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (instruction *LinkInstruction) Undo() {
 | 
				
			||||||
 | 
						if !FileExists(instruction.Target) {
 | 
				
			||||||
 | 
							LogInfo("%s does not exist, skipping", FormatTargetPath(instruction.Target))
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						isSymlink, err := IsSymlink(instruction.Target)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							LogError("could not determine whether %s is a sym link or not, stopping; err: %v",
 | 
				
			||||||
 | 
								FormatTargetPath(instruction.Target), err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if isSymlink {
 | 
				
			||||||
 | 
							LogInfo("Removing symlink at %s", FormatTargetPath(instruction.Target))
 | 
				
			||||||
 | 
							err = os.Remove(instruction.Target)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								LogError("could not remove symlink at %s; err: %v",
 | 
				
			||||||
 | 
									FormatTargetPath(instruction.Target), err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							LogSuccess("Removed symlink at %s", FormatTargetPath(instruction.Target))
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							LogInfo("%s is not a symlink, skipping", FormatTargetPath(instruction.Target))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func ParseInstruction(line, workdir string) (LinkInstruction, error) {
 | 
					func ParseInstruction(line, workdir string) (LinkInstruction, error) {
 | 
				
			||||||
	line = strings.TrimSpace(line)
 | 
						line = strings.TrimSpace(line)
 | 
				
			||||||
	if strings.HasPrefix(line, "#") {
 | 
						if strings.HasPrefix(line, "#") {
 | 
				
			||||||
@@ -131,13 +157,21 @@ func isTrue(value string) bool {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func (instruction *LinkInstruction) RunAsync(status chan (error)) {
 | 
					func (instruction *LinkInstruction) RunAsync(status chan (error)) {
 | 
				
			||||||
	defer close(status)
 | 
						defer close(status)
 | 
				
			||||||
 | 
						if undo {
 | 
				
			||||||
 | 
							instruction.Undo()
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if !FileExists(instruction.Source) {
 | 
						if !FileExists(instruction.Source) {
 | 
				
			||||||
		status <- fmt.Errorf("instruction source %s does not exist", FormatSourcePath(instruction.Source))
 | 
							status <- fmt.Errorf("instruction source %s does not exist", FormatSourcePath(instruction.Source))
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if !instruction.Force && AreSame(instruction.Source, instruction.Target) {
 | 
						if !instruction.Force && AreSame(instruction.Source, instruction.Target) {
 | 
				
			||||||
		status <- fmt.Errorf("source %s and target %s are the same, nothing to do...",
 | 
							//status <- fmt.Errorf("source %s and target %s are the same, nothing to do...",
 | 
				
			||||||
 | 
							//	FormatSourcePath(instruction.Source),
 | 
				
			||||||
 | 
							//	FormatTargetPath(instruction.Target))
 | 
				
			||||||
 | 
							LogInfo("Source %s and target %s are the same, nothing to do...",
 | 
				
			||||||
			FormatSourcePath(instruction.Source),
 | 
								FormatSourcePath(instruction.Source),
 | 
				
			||||||
			FormatTargetPath(instruction.Target))
 | 
								FormatTargetPath(instruction.Target))
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
@@ -222,7 +256,7 @@ func (instruction *LinkInstruction) RunAsync(status chan (error)) {
 | 
				
			|||||||
			err)
 | 
								err)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	LogInfo("Created symlink between %s and %s",
 | 
						LogSuccess("Created symlink between %s and %s",
 | 
				
			||||||
		FormatSourcePath(instruction.Source),
 | 
							FormatSourcePath(instruction.Source),
 | 
				
			||||||
		FormatTargetPath(instruction.Target))
 | 
							FormatTargetPath(instruction.Target))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -248,27 +282,26 @@ func ParseYAMLFile(filename, workdir string) ([]LinkInstruction, error) {
 | 
				
			|||||||
		config.Links = instructions
 | 
							config.Links = instructions
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	toRemove := []int{}
 | 
						expanded := []LinkInstruction{}
 | 
				
			||||||
	for i, link := range config.Links {
 | 
						for _, link := range config.Links {
 | 
				
			||||||
		if strings.Contains(link.Source, "*") {
 | 
							LogSource("Expanding pattern source %s in YAML file %s", link.Source, filename)
 | 
				
			||||||
			LogSource("Expanding wildcard source %s in YAML file %s", link.Source, filename)
 | 
							newlinks, err := ExpandPattern(link.Source, workdir, link.Target)
 | 
				
			||||||
			newlinks, err := ExpandWildcard(link.Source, workdir, link.Target)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
				return nil, fmt.Errorf("error expanding wildcard: %w", err)
 | 
								return nil, fmt.Errorf("error expanding pattern: %w", err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
			LogInfo("Expanded wildcard source %s in YAML file %s to %d links",
 | 
							// "Clone" the original link instruction for each expanded link
 | 
				
			||||||
 | 
							for i := range newlinks {
 | 
				
			||||||
 | 
								newlinks[i].Delete = link.Delete
 | 
				
			||||||
 | 
								newlinks[i].Hard = link.Hard
 | 
				
			||||||
 | 
								newlinks[i].Force = link.Force
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							LogInfo("Expanded pattern %s in YAML file %s to %d links",
 | 
				
			||||||
			FormatSourcePath(link.Source), FormatSourcePath(filename), len(newlinks))
 | 
								FormatSourcePath(link.Source), FormatSourcePath(filename), len(newlinks))
 | 
				
			||||||
			config.Links = append(config.Links, newlinks...)
 | 
							expanded = append(expanded, newlinks...)
 | 
				
			||||||
			toRemove = append(toRemove, i)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for i := len(toRemove) - 1; i >= 0; i-- {
 | 
						for i := range expanded {
 | 
				
			||||||
		config.Links = slices.Delete(config.Links, toRemove[i], 1)
 | 
							link := &expanded[i]
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	for i := range config.Links {
 | 
					 | 
				
			||||||
		link := &config.Links[i]
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		link.Tidy()
 | 
							link.Tidy()
 | 
				
			||||||
		link.Source, _ = ConvertHome(link.Source)
 | 
							link.Source, _ = ConvertHome(link.Source)
 | 
				
			||||||
@@ -277,32 +310,69 @@ func ParseYAMLFile(filename, workdir string) ([]LinkInstruction, error) {
 | 
				
			|||||||
		link.Target = NormalizePath(link.Target, workdir)
 | 
							link.Target = NormalizePath(link.Target, workdir)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// If Delete is true, Force must also be true
 | 
							// If Delete is true, Force must also be true
 | 
				
			||||||
		if config.Links[i].Delete {
 | 
							if link.Delete {
 | 
				
			||||||
			config.Links[i].Force = true
 | 
								link.Force = true
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return config.Links, nil
 | 
						return expanded, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func ExpandWildcard(source, workdir, target string) (links []LinkInstruction, err error) {
 | 
					func ExpandPattern(source, workdir, target string) (links []LinkInstruction, err error) {
 | 
				
			||||||
	dir := filepath.Dir(source)
 | 
						static, pattern := doublestar.SplitPattern(source)
 | 
				
			||||||
	pattern := filepath.Base(source)
 | 
						if static == "" {
 | 
				
			||||||
 | 
							static = workdir
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						LogInfo("Static part: %s", static)
 | 
				
			||||||
 | 
						LogInfo("Pattern part: %s", pattern)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	files, err := filepath.Glob(filepath.Join(workdir, dir, pattern))
 | 
						files, err := doublestar.Glob(os.DirFS(static), pattern)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, fmt.Errorf("error expanding wildcard: %w", err)
 | 
							return nil, fmt.Errorf("error expanding pattern: %w", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						targetIsFile := false
 | 
				
			||||||
 | 
						if info, err := os.Stat(target); err == nil && !info.IsDir() {
 | 
				
			||||||
 | 
							targetIsFile = true
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, file := range files {
 | 
						for _, file := range files {
 | 
				
			||||||
		link := LinkInstruction{
 | 
							if info, err := os.Stat(file); err == nil && info.IsDir() {
 | 
				
			||||||
			Source: file,
 | 
								// We don't care about matched directories
 | 
				
			||||||
			Target: filepath.Join(target, filepath.Base(file)),
 | 
								// We want files within them
 | 
				
			||||||
 | 
								if len(files) == 1 {
 | 
				
			||||||
 | 
									// Special case: if there is only one file, and it's a directory
 | 
				
			||||||
 | 
									// This should only ever happen if our source is a path (and not a glob!)
 | 
				
			||||||
 | 
									// And our target is a path, a directory
 | 
				
			||||||
 | 
									// ...but it will also happen if the source IS a glob and it happens to match ONE directory
 | 
				
			||||||
 | 
									// I think that should happen rarely enough to not be an issue...
 | 
				
			||||||
 | 
									links = append(links, LinkInstruction{
 | 
				
			||||||
 | 
										Source: filepath.Join(static, file),
 | 
				
			||||||
 | 
										Target: target,
 | 
				
			||||||
 | 
									})
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		links = append(links, link)
 | 
								LogInfo("Skipping directory %s", file)
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	LogInfo("Expanded wildcard source %s to %d links", FormatSourcePath(source), len(links))
 | 
							var targetPath string
 | 
				
			||||||
 | 
							if targetIsFile && len(files) == 1 {
 | 
				
			||||||
 | 
								// Special case: target is a file, and glob matches exactly one file.
 | 
				
			||||||
 | 
								// Use target directly (don't append filename).
 | 
				
			||||||
 | 
								targetPath = target
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								// Default: append filename to target dir.
 | 
				
			||||||
 | 
								targetPath = filepath.Join(target, file)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							links = append(links, LinkInstruction{
 | 
				
			||||||
 | 
								Source: filepath.Join(static, file),
 | 
				
			||||||
 | 
								Target: targetPath,
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						LogInfo("Expanded pattern %s to %d links", FormatSourcePath(source), len(links))
 | 
				
			||||||
	return
 | 
						return
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,6 +14,7 @@ const (
 | 
				
			|||||||
	TargetPrefix    = "TARGET"
 | 
						TargetPrefix    = "TARGET"
 | 
				
			||||||
	PathPrefix      = "PATH"
 | 
						PathPrefix      = "PATH"
 | 
				
			||||||
	ImportantPrefix = "IMPORTANT"
 | 
						ImportantPrefix = "IMPORTANT"
 | 
				
			||||||
 | 
						SuccessPrefix   = "DONE"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// LogInfo logs an informational message
 | 
					// LogInfo logs an informational message
 | 
				
			||||||
@@ -22,6 +23,12 @@ func LogInfo(format string, args ...interface{}) {
 | 
				
			|||||||
	log.Printf("%s[%s]%s %s", BGreen, InfoPrefix, Reset, message)
 | 
						log.Printf("%s[%s]%s %s", BGreen, InfoPrefix, Reset, message)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// LogSuccess logs a success message
 | 
				
			||||||
 | 
					func LogSuccess(format string, args ...interface{}) {
 | 
				
			||||||
 | 
						message := fmt.Sprintf(format, args...)
 | 
				
			||||||
 | 
						log.Printf("%s%s[%s]%s %s", BBlue, On_Blue, SuccessPrefix, Reset, message)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// LogSource logs a message about a source file/path with proper coloring
 | 
					// LogSource logs a message about a source file/path with proper coloring
 | 
				
			||||||
func LogSource(format string, args ...interface{}) {
 | 
					func LogSource(format string, args ...interface{}) {
 | 
				
			||||||
	message := fmt.Sprintf(format, args...)
 | 
						message := fmt.Sprintf(format, args...)
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										36
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										36
									
								
								main.go
									
									
									
									
									
								
							@@ -22,12 +22,15 @@ const PathColor = Green
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
var FileRegex, _ = regexp.Compile(`sync\.ya?ml$`)
 | 
					var FileRegex, _ = regexp.Compile(`sync\.ya?ml$`)
 | 
				
			||||||
var programName = os.Args[0]
 | 
					var programName = os.Args[0]
 | 
				
			||||||
 | 
					var undo = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func main() {
 | 
					func main() {
 | 
				
			||||||
	recurse := flag.String("r", "", "recurse into directories")
 | 
						recurse := flag.String("r", "", "recurse into directories")
 | 
				
			||||||
	file := flag.String("f", "", "file to read instructions from")
 | 
						file := flag.String("f", "", "file to read instructions from")
 | 
				
			||||||
	debug := flag.Bool("d", false, "debug")
 | 
						debug := flag.Bool("d", false, "debug")
 | 
				
			||||||
 | 
						undoF := flag.Bool("u", false, "undo")
 | 
				
			||||||
	flag.Parse()
 | 
						flag.Parse()
 | 
				
			||||||
 | 
						undo = *undoF
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if *debug {
 | 
						if *debug {
 | 
				
			||||||
		log.SetFlags(log.Lmicroseconds | log.Lshortfile)
 | 
							log.SetFlags(log.Lmicroseconds | log.Lshortfile)
 | 
				
			||||||
@@ -59,12 +62,15 @@ func main() {
 | 
				
			|||||||
		LogInfo("Reading from command line arguments")
 | 
							LogInfo("Reading from command line arguments")
 | 
				
			||||||
		go ReadFromArgs(instructions, status)
 | 
							go ReadFromArgs(instructions, status)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	case IsPipeInput():
 | 
						// case IsPipeInput():
 | 
				
			||||||
		LogInfo("Reading from stdin pipe")
 | 
						// 	LogInfo("Reading from stdin pipe")
 | 
				
			||||||
		go ReadFromStdin(instructions, status)
 | 
						// 	go ReadFromStdin(instructions, status)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	default:
 | 
						default:
 | 
				
			||||||
		if _, err := os.Stat("sync.yaml"); err == nil {
 | 
							if _, err := os.Stat("sync"); err == nil {
 | 
				
			||||||
 | 
								LogInfo("Using default sync file")
 | 
				
			||||||
 | 
								go ReadFromFile("sync", instructions, status, true)
 | 
				
			||||||
 | 
							} else if _, err := os.Stat("sync.yaml"); err == nil {
 | 
				
			||||||
			LogInfo("Using default sync.yaml file")
 | 
								LogInfo("Using default sync.yaml file")
 | 
				
			||||||
			go ReadFromFile("sync.yaml", instructions, status, true)
 | 
								go ReadFromFile("sync.yaml", instructions, status, true)
 | 
				
			||||||
		} else if _, err := os.Stat("sync.yml"); err == nil {
 | 
							} else if _, err := os.Stat("sync.yml"); err == nil {
 | 
				
			||||||
@@ -221,6 +227,28 @@ func ReadFromFile(input string, output chan *LinkInstruction, status chan error,
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Handle CSV format (legacy)
 | 
				
			||||||
 | 
						file, err := os.Open(input)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Fatalf("Failed to open file %s%s%s: %s%+v%s",
 | 
				
			||||||
 | 
								SourceColor, input, DefaultColor, ErrorColor, err, DefaultColor)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer file.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						scanner := bufio.NewScanner(file)
 | 
				
			||||||
 | 
						for scanner.Scan() {
 | 
				
			||||||
 | 
							line := scanner.Text()
 | 
				
			||||||
 | 
							instruction, err := ParseInstruction(line, filepath.Dir(input))
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Printf("Error parsing line: %s'%s'%s, error: %s%+v%s",
 | 
				
			||||||
 | 
									SourceColor, line, DefaultColor, ErrorColor, err, DefaultColor)
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							log.Printf("Read instruction: %s", instruction.String())
 | 
				
			||||||
 | 
							output <- &instruction
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func ReadFromArgs(output chan *LinkInstruction, status chan error) {
 | 
					func ReadFromArgs(output chan *LinkInstruction, status chan error) {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										48
									
								
								release.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								release.sh
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,48 @@
 | 
				
			|||||||
 | 
					#!/bin/bash
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					echo "Figuring out the tag..."
 | 
				
			||||||
 | 
					TAG=$(git describe --tags --exact-match 2>/dev/null || echo "")
 | 
				
			||||||
 | 
					if [ -z "$TAG" ]; then
 | 
				
			||||||
 | 
					  # Get the latest tag
 | 
				
			||||||
 | 
					  LATEST_TAG=$(git describe --tags $(git rev-list --tags --max-count=1))
 | 
				
			||||||
 | 
					  # Increment the patch version
 | 
				
			||||||
 | 
					  IFS='.' read -r -a VERSION_PARTS <<< "$LATEST_TAG"
 | 
				
			||||||
 | 
					  VERSION_PARTS[2]=$((VERSION_PARTS[2]+1))
 | 
				
			||||||
 | 
					  TAG="${VERSION_PARTS[0]}.${VERSION_PARTS[1]}.${VERSION_PARTS[2]}"
 | 
				
			||||||
 | 
					  # Create a new tag
 | 
				
			||||||
 | 
					  git tag $TAG
 | 
				
			||||||
 | 
					  git push origin $TAG
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
 | 
					echo "Tag: $TAG"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					echo "Building the thing..."
 | 
				
			||||||
 | 
					go build .
 | 
				
			||||||
 | 
					go install .
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					echo "Creating a release..."
 | 
				
			||||||
 | 
					TOKEN="$GITEA_API_KEY"
 | 
				
			||||||
 | 
					GITEA="https://git.site.quack-lab.dev"
 | 
				
			||||||
 | 
					REPO="dave/synclib"
 | 
				
			||||||
 | 
					# Create a release
 | 
				
			||||||
 | 
					RELEASE_RESPONSE=$(curl -s -X POST \
 | 
				
			||||||
 | 
					  -H "Authorization: token $TOKEN" \
 | 
				
			||||||
 | 
					  -H "Accept: application/json" \
 | 
				
			||||||
 | 
					  -H "Content-Type: application/json" \
 | 
				
			||||||
 | 
					  -d '{
 | 
				
			||||||
 | 
					    "tag_name": "'"$TAG"'",
 | 
				
			||||||
 | 
					    "name": "'"$TAG"'",
 | 
				
			||||||
 | 
					    "draft": false,
 | 
				
			||||||
 | 
					    "prerelease": false
 | 
				
			||||||
 | 
					  }' \
 | 
				
			||||||
 | 
					  $GITEA/api/v1/repos/$REPO/releases)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Extract the release ID
 | 
				
			||||||
 | 
					echo $RELEASE_RESPONSE
 | 
				
			||||||
 | 
					RELEASE_ID=$(echo $RELEASE_RESPONSE | awk -F'"id":' '{print $2+0; exit}')
 | 
				
			||||||
 | 
					echo "Release ID: $RELEASE_ID"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					echo "Uploading the things..."
 | 
				
			||||||
 | 
					curl -X POST \
 | 
				
			||||||
 | 
					  -H "Authorization: token $TOKEN" \
 | 
				
			||||||
 | 
					  -F "attachment=@cln.exe" \
 | 
				
			||||||
 | 
					  "$GITEA/api/v1/repos/$REPO/releases/${RELEASE_ID}/assets?name=cln.exe"
 | 
				
			||||||
		Reference in New Issue
	
	Block a user