@@ 1,13 @@
+Copyright (c) 2023 Chris Howey
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
@@ 1,120 @@
+package main
+
+import (
+ "archive/tar"
+ "compress/gzip"
+ "errors"
+ "flag"
+ "fmt"
+ "io"
+ "net/http"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strings"
+)
+
+// GetArchivePath
+func GetArchivePath(bin string) (string, error) {
+ out, err := exec.Command("go", "version", "-m", bin).Output()
+ if err != nil {
+ return "", err
+ }
+ lines := strings.Split(string(out), "\n")
+ for _, line := range lines {
+ if strings.Contains(line, "mod") {
+ parts := strings.Split(strings.TrimSpace(line), "\t")
+ if len(parts) == 4 {
+ path := parts[1]
+ version := parts[2]
+ urlparts := strings.Split(path, "/")
+ host := urlparts[0]
+ switch host {
+ case "github.com", "git.sr.ht":
+ return "https://" + path + "/archive/refs/tags/" + version + ".tar.gz", nil
+ }
+ }
+ }
+ }
+ return "", errors.New("unable to get archive url")
+}
+
+func main() {
+ var verbose bool
+ flag.BoolVar(&verbose, "v", false, "verbose")
+ flag.Parse()
+
+ var gobin string
+ if env, f := os.LookupEnv("HOME"); f {
+ gobin = env + "/go/bin"
+ }
+ if env, f := os.LookupEnv("GOPATH"); f {
+ gobin = env + "/bin"
+ }
+ if gobin == "" {
+ fmt.Println("unable to determine GOBIN")
+ os.Exit(1)
+ }
+ manpath := filepath.Join(filepath.Dir(gobin), "man")
+
+ binaries, _ := os.ReadDir(gobin)
+ bar := makebar(verbose, len(binaries))
+ for _, target := range binaries {
+ bar.Add(1)
+ targetname := target.Name()
+ bar.Set("prefix", targetname)
+ arcPath, aerr := GetArchivePath(gobin + "/" + targetname)
+ if aerr != nil {
+ if verbose {
+ fmt.Fprintf(os.Stderr, "%s: %s\n", targetname, aerr)
+ }
+ continue
+ }
+ arc, rerr := http.Get(arcPath)
+ if rerr != nil {
+ if verbose {
+ fmt.Fprintf(os.Stderr, "%s: %s\n", targetname, rerr)
+ }
+ continue
+ }
+ defer arc.Body.Close()
+
+ basename := filepath.Base(target.Name())
+
+ gr, gerr := gzip.NewReader(arc.Body)
+ if gerr != nil {
+ if verbose {
+ fmt.Fprintf(os.Stderr, "%s: %s\n", targetname, gerr)
+ }
+ continue
+ }
+ tr := tar.NewReader(gr)
+ for {
+ hdr, herr := tr.Next()
+ if herr == io.EOF {
+ break
+ }
+ if herr != nil {
+ if verbose {
+ fmt.Fprintf(os.Stderr, "%s: %s\n", targetname, herr)
+ }
+ break
+ }
+ if manpage, section, found := strings.Cut(filepath.Base(hdr.Name), "."); found && manpage == basename {
+ secdir, _ := strings.CutSuffix(section, ".gz")
+ fullmandir := manpath + "/man" + secdir
+ switch section {
+ case "1", "3", "5", "1.gz", "3.gz", "5.gz":
+ os.MkdirAll(fullmandir, 0700)
+ fullmanpath := fullmandir + "/" + manpage + "." + section
+ ofile, _ := os.Create(fullmanpath)
+ io.Copy(ofile, tr)
+ ofile.Close()
+ if verbose {
+ fmt.Fprintf(os.Stdout, "%s - %s\n", targetname, fullmanpath)
+ }
+ }
+ }
+ }
+ }
+}
@@ 1,54 @@
+package main
+
+import (
+ "fmt"
+ "io"
+ "time"
+
+ pb "github.com/cheggaaa/pb/v3"
+)
+
+var (
+ pbTemplate pb.ProgressBarTemplate = `{{with string . "prefix"}}{{.}} {{end}}{{percent . "% 3.0f%%"}} {{bar . "[" "=" ">" "_" "]"}} ({{counters . "%s/%s"}}, {{myspeed .}}) [{{etime .}}{{with myrtime . "%s"}}:{{.}}{{end}}]{{with string . "suffix"}} {{.}}{{end}}`
+
+ mySpeedEl pb.ElementFunc = func(state *pb.State, args ...string) string {
+ elapsedSeconds := time.Since(state.StartTime()).Seconds()
+ avgRate := float64(state.Current()) / elapsedSeconds
+ dur := ""
+ pRate := avgRate
+ if avgRate > 1 {
+ dur = "s"
+ } else if avgRate*60 > 1 {
+ dur = "min"
+ pRate = avgRate * 60
+ } else {
+ dur = "hr"
+ pRate = avgRate * 3600
+ }
+ return fmt.Sprintf("%.0f jobs/%s", pRate, dur)
+ }
+ myRemTime pb.ElementFunc = func(state *pb.State, args ...string) string {
+ if state.IsFinished() {
+ return ""
+ }
+ elapsedSeconds := time.Since(state.StartTime()).Seconds()
+ avgRate := float64(state.Current()) / elapsedSeconds
+ remain := float64(state.Total() - state.Value())
+ remainDur := time.Duration(remain/avgRate) * time.Second
+ return fmt.Sprintf("%s", remainDur)
+ }
+)
+
+func init() {
+ pb.RegisterElement("myspeed", mySpeedEl, false)
+ pb.RegisterElement("myrtime", myRemTime, false)
+}
+
+func makebar(fake bool, size int) *pb.ProgressBar {
+ if fake {
+ b := pb.New(size)
+ b.SetWriter(io.Discard)
+ return b
+ }
+ return pbTemplate.Start64(int64(size))
+}