aboutsummaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorFuwn <[email protected]>2024-10-13 01:11:14 -0700
committerFuwn <[email protected]>2024-10-13 01:11:14 -0700
commit0aeae8d523ecb762e296b938cff3e012551e27b7 (patch)
tree4b6b610e977a4bb610a219009c9e53d9481065d8 /internal
parentb9442b0b1401bed64f51af7968936ae94408e99b (diff)
downloadyae-0aeae8d523ecb762e296b938cff3e012551e27b7.tar.xz
yae-0aeae8d523ecb762e296b938cff3e012551e27b7.zip
refactor(yae): move commands and library to internal package
Diffstat (limited to 'internal')
-rw-r--r--internal/commands/add.go70
-rw-r--r--internal/commands/drop.go24
-rw-r--r--internal/commands/init.go19
-rw-r--r--internal/commands/update.go51
-rw-r--r--internal/yae/source.go124
-rw-r--r--internal/yae/sources.go53
-rw-r--r--internal/yae/utilities.go55
7 files changed, 396 insertions, 0 deletions
diff --git a/internal/commands/add.go b/internal/commands/add.go
new file mode 100644
index 0000000..4ee15ec
--- /dev/null
+++ b/internal/commands/add.go
@@ -0,0 +1,70 @@
+package commands
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/Fuwn/yae/internal/yae"
+ "github.com/urfave/cli/v2"
+)
+
+func Add(sources *yae.Sources) func(c *cli.Context) error {
+ return func(c *cli.Context) error {
+ if c.Args().Len() != 2 {
+ return fmt.Errorf("invalid number of arguments")
+ }
+
+ if sources.Exists(c.Args().Get(0)) {
+ return fmt.Errorf("source already exists")
+ }
+
+ source := yae.Source{
+ Unpack: c.Bool("unpack"),
+ Type: c.String("type"),
+ }
+ version := c.String("version")
+
+ if version != "" {
+ source.URLTemplate = c.Args().Get(1)
+ source.Version = c.String("version")
+
+ if strings.Contains(source.URLTemplate, "{version}") {
+ source.URL = strings.ReplaceAll(source.URLTemplate, "{version}", source.Version)
+ }
+ } else {
+ source.URL = c.Args().Get(1)
+ }
+
+ if source.Type == "git" && c.String("tag-predicate") != "" {
+ source.TagPredicate = c.String("tag-predicate")
+ }
+
+ if c.String("trim-tag-prefix") != "" {
+ source.TrimTagPrefix = c.String("trim-tag-prefix")
+ }
+
+ if c.Bool("pin") {
+ source.Pinned = true
+ }
+
+ if c.Bool("force") {
+ if source.Pinned {
+ return fmt.Errorf("cannot set a source to be statically forced and pinned at the same time")
+ }
+
+ source.Force = true
+ }
+
+ if sha256, err := yae.FetchSHA256(source.URL, c.Bool("unpack")); err != nil {
+ return err
+ } else {
+ source.SHA256 = sha256
+ }
+
+ if err := sources.Add(c.Args().Get(0), source); err != nil {
+ return err
+ }
+
+ return sources.Save(c.String("sources"))
+ }
+}
diff --git a/internal/commands/drop.go b/internal/commands/drop.go
new file mode 100644
index 0000000..58855df
--- /dev/null
+++ b/internal/commands/drop.go
@@ -0,0 +1,24 @@
+package commands
+
+import (
+ "fmt"
+
+ "github.com/Fuwn/yae/internal/yae"
+ "github.com/urfave/cli/v2"
+)
+
+func Drop(sources *yae.Sources) func(c *cli.Context) error {
+ return func(c *cli.Context) error {
+ if c.Args().Len() == 0 {
+ return fmt.Errorf("invalid number of arguments")
+ }
+
+ if !sources.Exists(c.Args().Get(0)) {
+ return fmt.Errorf("source does not exist")
+ }
+
+ sources.Drop(c.Args().Get(0))
+
+ return sources.Save(c.String("sources"))
+ }
+}
diff --git a/internal/commands/init.go b/internal/commands/init.go
new file mode 100644
index 0000000..f38ec96
--- /dev/null
+++ b/internal/commands/init.go
@@ -0,0 +1,19 @@
+package commands
+
+import (
+ "fmt"
+ "os"
+
+ "github.com/Fuwn/yae/internal/yae"
+ "github.com/urfave/cli/v2"
+)
+
+func Init(sources *yae.Sources) func(c *cli.Context) error {
+ return func(c *cli.Context) error {
+ if _, err := os.Stat(c.String("sources")); err == nil {
+ return fmt.Errorf("sources file already exists")
+ }
+
+ return sources.Save(c.String("sources"))
+ }
+}
diff --git a/internal/commands/update.go b/internal/commands/update.go
new file mode 100644
index 0000000..66ccfb3
--- /dev/null
+++ b/internal/commands/update.go
@@ -0,0 +1,51 @@
+package commands
+
+import (
+ "fmt"
+
+ "github.com/Fuwn/yae/internal/yae"
+ "github.com/urfave/cli/v2"
+)
+
+func Update(sources *yae.Sources) func(c *cli.Context) error {
+ return func(c *cli.Context) error {
+ updates := []string{}
+ force := c.Bool("force-hashed")
+ forcePinned := c.Bool("force-pinned")
+
+ if c.Args().Len() == 0 {
+ for name, source := range *sources {
+ if updated, err := source.Update(sources, name, force, forcePinned); err != nil {
+ return err
+ } else if updated {
+ updates = append(updates, name)
+ }
+ }
+ } else {
+ name := c.Args().Get(0)
+ source := (*sources)[name]
+
+ if updated, err := source.Update(sources, name, force, forcePinned); err != nil {
+ return err
+ } else if updated {
+ updates = append(updates, name)
+ }
+ }
+
+ if len(updates) > 0 {
+ if err := sources.Save(c.String("sources")); err != nil {
+ return err
+ }
+ }
+
+ if c.Bool("output-updated-list") {
+ for _, update := range updates {
+ fmt.Println(update)
+ }
+ } else if c.Bool("output-formatted-updated-list") {
+ fmt.Println(yae.Lister(updates))
+ }
+
+ return nil
+ }
+}
diff --git a/internal/yae/source.go b/internal/yae/source.go
new file mode 100644
index 0000000..185aba7
--- /dev/null
+++ b/internal/yae/source.go
@@ -0,0 +1,124 @@
+package yae
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/charmbracelet/log"
+)
+
+type Source struct {
+ URL string `json:"url"`
+ SHA256 string `json:"sha256"`
+ Unpack bool `json:"unpack"`
+ Type string `json:"type"`
+ Version string `json:"version,omitempty"`
+ URLTemplate string `json:"url_template,omitempty"`
+ TagPredicate string `json:"tag_predicate,omitempty"`
+ TrimTagPrefix string `json:"trim_tag_prefix,omitempty"`
+ Pinned bool `json:"pinned,omitempty"`
+ Force bool `json:"force,omitempty"`
+}
+
+func (source *Source) Update(sources *Sources, name string, force bool, forcePinned bool) (bool, error) {
+ log.Infof("checking %s", name)
+
+ updated := false
+
+ if !sources.Exists(name) {
+ log.Warnf("skipped %s: source does not exist", name)
+
+ return updated, nil
+ }
+
+ if source.Pinned && !forcePinned {
+ log.Infof("skipped %s: source is pinned", name)
+
+ return updated, nil
+ }
+
+ if source.Type == "git" {
+ log.Debugf("checking %s: remote git tag", name)
+
+ tag, err := source.fetchLatestGitTag()
+
+ if err != nil {
+ return updated, err
+ }
+
+ if tag != source.Version || force || source.Force {
+ if tag != source.Version {
+ log.Infof("bumped %s: %s -> %s", name, source.Version, tag)
+ }
+
+ if tag != source.Version {
+ updated = true
+ }
+
+ source.Version = tag
+
+ if strings.Contains(source.URLTemplate, "{version}") {
+ source.URL = strings.ReplaceAll(source.URLTemplate, "{version}", source.Version)
+
+ log.Debugf("patched %s: substituted url template", name)
+ }
+ } else {
+ log.Infof("skipped %s: version remains unchanged", name)
+
+ return updated, nil
+ }
+ }
+
+ log.Debugf("checking %s: sha256", name)
+
+ sha256, err := FetchSHA256(source.URL, source.Unpack)
+
+ if err != nil {
+ return updated, err
+ }
+
+ if sha256 != source.SHA256 {
+ log.Infof("rehashed %s: %s -> %s", name, source.SHA256, sha256)
+
+ source.SHA256 = sha256
+ updated = true
+ }
+
+ (*sources)[name] = *source
+
+ return updated, nil
+}
+
+func (source *Source) fetchLatestGitTag() (string, error) {
+ if source.Type == "git" {
+ repository := "https://github.com/" + strings.Split(source.URL, "/")[3] + "/" + strings.Split(source.URL, "/")[4]
+ remotes, err := command("bash", false, "-c", fmt.Sprintf("git ls-remote %s | awk -F'/' '{print $NF}' | sort -V", repository))
+
+ if err != nil {
+ return "", err
+ }
+
+ refs := strings.Split(remotes, "\n")
+ var latest string
+
+ if source.TagPredicate == "" {
+ latest = refs[len(refs)-2]
+ } else {
+ for i := len(refs) - 2; i >= 0; i-- {
+ if strings.Contains(refs[i], source.TagPredicate) {
+ latest = refs[i]
+
+ break
+ }
+ }
+ }
+
+ if source.TrimTagPrefix != "" {
+ latest = strings.TrimPrefix(latest, source.TrimTagPrefix)
+ }
+
+ return latest, nil
+ }
+
+ return "", fmt.Errorf("source is not a git repository")
+}
diff --git a/internal/yae/sources.go b/internal/yae/sources.go
new file mode 100644
index 0000000..f8cb2b0
--- /dev/null
+++ b/internal/yae/sources.go
@@ -0,0 +1,53 @@
+package yae
+
+import (
+ "encoding/json"
+ "fmt"
+ "os"
+)
+
+type Sources map[string]Source
+
+func (s *Sources) Add(name string, d Source) error {
+ if s.Exists(name) {
+ return fmt.Errorf("source already exists")
+ }
+
+ (*s)[name] = d
+
+ return nil
+}
+
+func (s *Sources) Exists(name string) bool {
+ _, ok := (*s)[name]
+
+ return ok
+}
+
+func (s *Sources) Drop(url string) {
+ delete((*s), url)
+}
+
+func (s *Sources) Save(path string) error {
+ file, err := os.Create(path)
+
+ if err != nil {
+ return err
+ }
+
+ encoder := json.NewEncoder(file)
+
+ encoder.SetIndent("", " ")
+
+ return encoder.Encode(s)
+}
+
+func (s *Sources) Load(path string) error {
+ file, err := os.Open(path)
+
+ if err != nil {
+ return err
+ }
+
+ return json.NewDecoder(file).Decode(s)
+}
diff --git a/internal/yae/utilities.go b/internal/yae/utilities.go
new file mode 100644
index 0000000..ef9d334
--- /dev/null
+++ b/internal/yae/utilities.go
@@ -0,0 +1,55 @@
+package yae
+
+import (
+ "fmt"
+ "os"
+ "os/exec"
+ "strings"
+)
+
+func FetchSHA256(url string, unpack bool) (string, error) {
+ arguments := []string{"--type", "sha256", url}
+
+ if unpack {
+ arguments = append([]string{"--unpack"}, arguments...)
+ }
+
+ output, err := command("nix-prefetch-url", false, arguments...)
+
+ if err != nil {
+ return "", err
+ }
+
+ lines := strings.Split(output, "\n")
+
+ return strings.Trim(lines[len(lines)-2], "\n"), nil
+}
+
+func command(name string, show bool, args ...string) (string, error) {
+ executable, err := exec.LookPath(name)
+ out := []byte{}
+
+ if show {
+ cmd := exec.Command(executable, args...)
+ cmd.Stdin = os.Stdin
+ cmd.Stderr = os.Stderr
+ out, err = cmd.Output()
+ } else {
+ cmd := exec.Command(executable, args...)
+ out, err = cmd.Output()
+ }
+
+ return string(out), err
+}
+
+func Lister(items []string) string {
+ if len(items) == 0 {
+ return ""
+ } else if len(items) == 1 {
+ return items[0]
+ } else if len(items) == 2 {
+ return fmt.Sprintf("%s & %s", items[0], items[1])
+ }
+
+ return fmt.Sprintf("%s, & %s", strings.Join(items[:len(items)-1], ", "), items[len(items)-1])
+}