diff --git a/disk/disk.go b/disk/disk.go index 9803168..1a38d66 100644 --- a/disk/disk.go +++ b/disk/disk.go @@ -60,7 +60,8 @@ func unpack(src, name string) error { } uaIface, err := archiver.ByExtension(src) if err != nil { - if filepath.Ext(src) == binExt { + ext := filepath.Ext(src) + if ext == binExt || len(ext) > 3 { dest = filepath.Join(dest, name+binExt) if err := os.Rename(src, dest); err != nil { return err diff --git a/forge/forge.go b/forge/forge.go index 4a092a1..7f109f0 100644 --- a/forge/forge.go +++ b/forge/forge.go @@ -10,7 +10,7 @@ import ( ) var ( - amd64Re = regexp.MustCompile(`amd64|x86_64|64-bit|[^m]64`) + amd64Re = regexp.MustCompile(`amd64|x86_64|64-bit|[^mhv]64`) linuxRe = regexp.MustCompile(`linux`) windowsRe = regexp.MustCompile(`windows|\Awin`) ) @@ -23,11 +23,13 @@ type Release struct { type Asset struct { Name string DownloadURL string + Version string // Release.Name } type Forger interface { Name() string - Latest() (Release, error) + latestRelease() (Release, error) + tagRelease(tag string) (Release, error) } func splitURI(uri string) (string, []string, error) { @@ -44,7 +46,7 @@ func splitURI(uri string) (string, []string, error) { return u.String(), paths, nil } -func Latest(f Forger) (Asset, error) { +func TagRelease(f Forger, tag string) (Asset, error) { var asset Asset var re *regexp.Regexp @@ -57,7 +59,41 @@ func Latest(f Forger) (Asset, error) { return asset, fmt.Errorf("%q is not a supported OS", runtime.GOOS) } - release, err := f.Latest() + release, err := f.tagRelease(tag) + if err != nil { + return asset, fmt.Errorf("could not get %q release: %w", tag, err) + } + + for _, a := range release.Assets { + if amd64Re.MatchString(a.Name) && re.MatchString(a.Name) { + fmt.Printf("found %q\n", a.Name) + asset = a + break + } + } + + if asset.Name == "" { + return asset, errors.New("no release found for this OS") + } + + asset.Version = release.Name + return asset, nil +} + +func LatestRelease(f Forger) (Asset, error) { + var asset Asset + + var re *regexp.Regexp + switch runtime.GOOS { + case "linux": + re = linuxRe + case "windows": + re = windowsRe + default: + return asset, fmt.Errorf("%q is not a supported OS", runtime.GOOS) + } + + release, err := f.latestRelease() if err != nil { return asset, fmt.Errorf("could not get latest release: %w", err) } @@ -74,5 +110,6 @@ func Latest(f Forger) (Asset, error) { return asset, errors.New("no release found for this OS") } + asset.Version = release.Name return asset, nil } diff --git a/forge/gitea.go b/forge/gitea.go index fc443f9..4badb61 100644 --- a/forge/gitea.go +++ b/forge/gitea.go @@ -7,8 +7,6 @@ import ( "net/http" ) -var _ Forger = (*Gitea)(nil) - type Gitea struct { BaseURL string Owner string @@ -40,7 +38,7 @@ func (g Gitea) Name() string { return g.Repo } -func (g Gitea) Latest() (Release, error) { +func (g Gitea) latestRelease() (Release, error) { var release Release var releases []GiteaRelease @@ -70,3 +68,29 @@ func (g Gitea) Latest() (Release, error) { return release, nil } + +func (g Gitea) tagRelease(tag string) (Release, error) { + var release Release + + u := fmt.Sprintf("%s/api/v1/repos/%s/%s/releases/tags/%s", g.BaseURL, g.Owner, g.Repo, tag) + res, err := http.Get(u) + if err != nil { + return release, err + } + defer res.Body.Close() + + var r GiteaRelease + if err := json.NewDecoder(res.Body).Decode(&r); err != nil { + return release, err + } + + release.Name = r.TagName + for _, a := range r.Assets { + release.Assets = append(release.Assets, Asset{ + Name: a.Name, + DownloadURL: a.BrowserDownloadURL, + }) + } + + return release, nil +} diff --git a/forge/github.go b/forge/github.go index 38eb0d9..f24ee86 100644 --- a/forge/github.go +++ b/forge/github.go @@ -6,8 +6,6 @@ import ( "net/http" ) -var _ Forger = (*GitHub)(nil) - type GitHub struct { Owner string Repo string @@ -36,7 +34,7 @@ func (g GitHub) Name() string { return g.Repo } -func (g GitHub) Latest() (Release, error) { +func (g GitHub) latestRelease() (Release, error) { var release Release var r GitHubRelease @@ -61,3 +59,29 @@ func (g GitHub) Latest() (Release, error) { return release, nil } + +func (g GitHub) tagRelease(tag string) (Release, error) { + var release Release + + var r GitHubRelease + u := fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/tags/%s", g.Owner, g.Repo, tag) + res, err := http.Get(u) + if err != nil { + return release, err + } + defer res.Body.Close() + + if err := json.NewDecoder(res.Body).Decode(&r); err != nil { + return release, err + } + + release.Name = r.TagName + for _, a := range r.Assets { + release.Assets = append(release.Assets, Asset{ + Name: a.Name, + DownloadURL: a.BrowserDownloadURL, + }) + } + + return release, nil +} diff --git a/main.go b/main.go index 47f9bf8..e06f3b6 100644 --- a/main.go +++ b/main.go @@ -9,7 +9,6 @@ import ( "go.jolheiser.com/eget/disk" "go.jolheiser.com/eget/forge" "go.jolheiser.com/eget/meta" - "go.jolheiser.com/eget/shell" ) var Version = "develop" @@ -24,18 +23,16 @@ func main() { fs.BoolVar(updateFlag, "u", *updateFlag, "--update") deleteFlag := flag.Bool("delete", false, "Delete package") fs.BoolVar(deleteFlag, "d", *deleteFlag, "--delete") - nuFlag := fs.Bool("nu", false, "Write out Nu environment") + syncFlag := fs.Bool("sync", false, "Sync packages") if err := fs.Parse(os.Args[1:]); err != nil { fs.Usage() return } - if *nuFlag { - nu, err := shell.Nu() - if err != nil { + if *syncFlag { + if err := sync(); err != nil { panic(err) } - fmt.Println(nu) return } @@ -68,7 +65,7 @@ func main() { return } - asset, err := forge.Latest(f) + asset, err := forge.LatestRelease(f) if err != nil { panic(err) } @@ -78,7 +75,7 @@ func main() { if err != nil { panic(err) } - if strings.EqualFold(asset.Name, m.Packages[name]) { + if strings.EqualFold(asset.Version, m.Packages[name].Version) { fmt.Printf("%q is up-to-date\n", name) return } @@ -91,7 +88,7 @@ func main() { panic(err) } - if err := meta.Upsert(name, asset.Name); err != nil { + if err := meta.Upsert(name, uri, asset.Version); err != nil { panic(err) } @@ -106,3 +103,36 @@ func uriName(uri string) string { parts := strings.FieldsFunc(uri, func(r rune) bool { return r == '/' }) return parts[len(parts)-1] } + +func sync() error { + m, err := meta.Read() + if err != nil { + return err + } + + var f forge.Forger + for name, pkg := range m.Packages { + fmt.Printf("syncing %q\n", name) + if err := os.RemoveAll(disk.Path(name)); err != nil { + return err + } + f, err = forge.NewGitea(pkg.Repo) + if strings.HasPrefix(pkg.Repo, "github") { + f, err = forge.NewGitHub(pkg.Repo) + } + if err != nil { + return err + } + + asset, err := forge.TagRelease(f, pkg.Version) + if err != nil { + return err + } + + if err := disk.Install(name, asset); err != nil { + return err + } + } + + return nil +} diff --git a/meta/meta.go b/meta/meta.go index f67fc8e..b19b877 100644 --- a/meta/meta.go +++ b/meta/meta.go @@ -8,31 +8,44 @@ import ( "os" "strings" + "go.jolheiser.com/eget/disk" + "github.com/adrg/xdg" ) type Meta struct { - Packages map[string]string `json:"packages"` + Packages map[string]Package `json:"packages"` } -func Upsert(name, version string) error { +type Package struct { + Repo string `json:"repo"` + Version string `json:"version"` +} + +func Upsert(name, repo, version string) error { m, err := Read() if err != nil { return err } for n := range m.Packages { if strings.EqualFold(n, name) { - m.Packages[n] = version + m.Packages[n] = Package{ + Repo: repo, + Version: version, + } return save(m) } } - m.Packages[name] = version + m.Packages[name] = Package{ + Repo: repo, + Version: version, + } return save(m) } func Read() (Meta, error) { m := Meta{ - Packages: make(map[string]string), + Packages: make(map[string]Package), } fp, err := metaPath() if err != nil { @@ -68,7 +81,11 @@ func Remove(name string) error { } func metaPath() (string, error) { - return xdg.DataFile("eget/meta.json") + return xdg.ConfigFile("eget/packages.json") +} + +func nushellPath() (string, error) { + return xdg.DataFile("eget/eget.nu") } func save(m Meta) error { @@ -80,6 +97,39 @@ func save(m Meta) error { if err != nil { return fmt.Errorf("could not create meta file: %w", err) } + if err := json.NewEncoder(fi).Encode(m); err != nil { + return err + } + if err := fi.Close(); err != nil { + return err + } + + fp, err = nushellPath() + if err != nil { + return fmt.Errorf("could not get nushell path: %w", err) + } + fi, err = os.Create(fp) + if err != nil { + return fmt.Errorf("could not create nushell file: %w", err) + } defer fi.Close() - return json.NewEncoder(fi).Encode(m) + out, err := m.nushell() + if err != nil { + return fmt.Errorf("could not generate nushell environment: %w", err) + } + fi.WriteString(out) + + return nil +} + +func (m Meta) nushell() (string, error) { + tmpl := "let-env PATH = ($env.PATH | append '%s')\n" + + var out strings.Builder + out.WriteString("# managed by eget; DO NOT EDIT\n\n") + for name := range m.Packages { + out.WriteString(fmt.Sprintf(tmpl, disk.Path(name))) + } + + return out.String(), nil } diff --git a/shell/nu.go b/shell/nu.go deleted file mode 100644 index e498851..0000000 --- a/shell/nu.go +++ /dev/null @@ -1,25 +0,0 @@ -package shell - -import ( - "fmt" - "strings" - - "go.jolheiser.com/eget/disk" - "go.jolheiser.com/eget/meta" -) - -func Nu() (string, error) { - tmpl := "let-env PATH = ($env.PATH | append '%s')\n" - m, err := meta.Read() - if err != nil { - return "", err - } - - var out strings.Builder - out.WriteString("# managed by eget; DO NOT EDIT\n\n") - for name := range m.Packages { - out.WriteString(fmt.Sprintf(tmpl, disk.Path(name))) - } - - return out.String(), nil -}