Uses the Hex.pm API to fetch information about a user's packages formatted using a template:
go run src/packages.go --template=packages/packages.md.template <username>
package main
import (
"context"
"encoding/json"
"flag"
"fmt"
"io"
"log"
"net/http"
"os"
"path"
"strings"
"text/template"
"time"
)
const (
version = "0.1.0"
)
type State struct {
user string
template string
file *os.File
timeout time.Duration
verbose int
}
type User struct {
Email string `json:"email"`
FullName string `json:"full_name"`
Handles struct {
GitHub string `json:"GitHub"`
} `json:"handles"`
InsertedAt time.Time `json:"inserted_at"`
OwnedPackages map[string]string `json:"owned_packages"`
Packages []struct {
HTMLURL string `json:"html_url"`
Name string `json:"name"`
Repository string `json:"repository"`
URL string `json:"url"`
} `json:"packages"`
UpdatedAt time.Time `json:"updated_at"`
URL string `json:"url"`
Username string `json:"username"`
}
type Package struct {
Configs struct {
ErlangMk string `json:"erlang.mk"`
MixExs string `json:"mix.exs"`
RebarConfig string `json:"rebar.config"`
} `json:"configs"`
DocsHTMLURL string `json:"docs_html_url"`
Downloads struct {
All int `json:"all"`
Day int `json:"day"`
Recent int `json:"recent"`
Week int `json:"week"`
} `json:"downloads"`
HTMLURL string `json:"html_url"`
InsertedAt time.Time `json:"inserted_at"`
LatestStableVersion string `json:"latest_stable_version"`
LatestVersion string `json:"latest_version"`
Meta struct {
Description string `json:"description"`
Licenses []string `json:"licenses"`
Links struct {
Github string `json:"Github"`
} `json:"links"`
Maintainers []string `json:"maintainers"`
} `json:"meta"`
Name string `json:"name"`
Owners []struct {
Email string `json:"email"`
URL string `json:"url"`
Username string `json:"username"`
} `json:"owners"`
Releases []struct {
HasDocs bool `json:"has_docs"`
InsertedAt time.Time `json:"inserted_at"`
URL string `json:"url"`
Version string `json:"version"`
} `json:"releases"`
Repository string `json:"repository"`
Retirements struct{} `json:"retirements"`
UpdatedAt time.Time `json:"updated_at"`
URL string `json:"url"`
}
func args() *State {
flag.Usage = func() {
_, _ = fmt.Fprintf(os.Stderr, `%s v%s
Usage: %s [<option>] <user>
`, path.Base(os.Args[0]), version, os.Args[0])
flag.PrintDefaults()
}
filePath := flag.String("file", "",
"Write output to file")
timeout := flag.Duration("timeout", 1*time.Minute,
"HTTP request timeout")
template := flag.String("template", "packages.md.template",
"Path to template file")
verbose := flag.Int("verbose", 0,
"Enable debug messages")
flag.Parse()
if flag.NArg() == 0 {
flag.Usage()
os.Exit(1)
}
user := flag.Arg(0)
content, err := os.ReadFile(*template)
if err != nil {
flag.Usage()
os.Exit(1)
}
file := os.Stdout
if *filePath != "" {
file, err = os.OpenFile(
*filePath,
os.O_WRONLY|os.O_TRUNC|os.O_CREATE,
os.ModePerm,
)
if err != nil {
flag.Usage()
os.Exit(1)
}
}
return &State{
user: user,
file: file,
template: string(content),
timeout: *timeout,
verbose: *verbose,
}
}
func main() {
state := args()
users, err := state.users()
if err != nil {
log.Fatalf("%s: %+v", err, state.user)
}
pkgs := make([]Package, 0, len(users.Packages))
for _, pkg := range users.Packages {
j, err := state.packages(pkg.URL)
if err != nil {
log.Fatalf("%s: %+v", err, pkg)
}
pkgs = append(pkgs, *j)
}
if err := state.render(pkgs); err != nil {
log.Fatalf("%s: %+v", err, pkgs)
}
}
func (state *State) users() (*User, error) {
uri := fmt.Sprintf("https://hex.pm/api/users/%s", state.user)
body, err := state.request(uri)
if err != nil {
return nil, err
}
users := &User{}
if err := json.Unmarshal(body, users); err != nil {
return nil, err
}
if state.verbose > 1 {
log.Printf("%+v\n", users)
}
return users, nil
}
func (state *State) packages(uri string) (*Package, error) {
body, err := state.request(uri)
if err != nil {
return nil, err
}
packages := &Package{}
if err := json.Unmarshal(body, packages); err != nil {
return nil, err
}
if state.verbose > 1 {
log.Printf("%+v\n", packages)
}
return packages, nil
}
func (state *State) request(uri string) ([]byte, error) {
if state.verbose > 0 {
log.Println(uri)
}
req, err := http.NewRequest(http.MethodGet, uri, nil)
if err != nil {
log.Fatal(err)
}
ctx, cancel := context.WithTimeout(
context.Background(),
state.timeout,
)
defer cancel()
req = req.WithContext(ctx)
c := &http.Client{}
res, err := c.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
return io.ReadAll(res.Body)
}
func (state *State) render(pkg []Package) error {
funcMap := template.FuncMap{
"strip": strip,
}
tmpl, err := template.New("template").Funcs(funcMap).Parse(state.template)
if err != nil {
return err
}
if err := tmpl.Execute(state.file, pkg); err != nil {
return err
}
return nil
}
func strip(s string) string {
return strings.ReplaceAll(s, "\n", " ")
}
(markdown)