// Package hnblogs provides access to the blogs.hn dataset. // // The dataset is vendored into this repository as blogs.json and compiled into // the package with go:embed, so nothing here touches the network at runtime and // the results are stable for a given version of the module. Refresh the // vendored copy with "make update-data" and commit the result. package hnblogs import ( _ "embed" "encoding/json" "fmt" "math/rand" "sync" ) // BlogsURL is the upstream source of blogs.json. Nothing reads it at runtime; // it documents where "make update-data" downloads the vendored copy from. const BlogsURL = "https://raw.githubusercontent.com/surprisetalk/blogs.hn/main/blogs.json" // blogsJSON is the vendored dataset, refreshed by "make update-data". // //go:embed blogs.json var blogsJSON []byte var ( blogs []Blog loadError error once sync.Once ) // Blog represents a single blog entry. type Blog struct { URL string `json:"url"` Title string `json:"title"` About string `json:"about"` Now string `json:"now"` Feed string `json:"feed"` Desc string `json:"desc"` } // GetBlogs returns the embedded list of blogs, decoding it on first call and // memoizing the result for subsequent calls. // // It performs no I/O: the data is compiled into the binary, so the only error // it can return is a malformed embedded blogs.json, which would mean the // committed dataset is broken. func GetBlogs() ([]Blog, error) { once.Do(func() { var decoded []Blog if err := json.Unmarshal(blogsJSON, &decoded); err != nil { loadError = fmt.Errorf("failed to decode embedded blogs JSON: %w", err) return } blogs = decoded }) return blogs, loadError } // RandomBlog returns a random blog from the list of blogs. func RandomBlog() (Blog, error) { blogs, err := GetBlogs() if err != nil { return Blog{}, err } return blogs[rand.Intn(len(blogs))], nil } // RandomBlogs returns n random blogs from the list of blogs. func RandomBlogs(n int) ([]Blog, error) { blogs, err := GetBlogs() if err != nil { return nil, err } if n <= 0 || n > len(blogs) { return nil, fmt.Errorf("invalid number of blogs requested") } selected := make([]Blog, n) for i := range selected { selected[i] = blogs[rand.Intn(len(blogs))] } return selected, nil } // NthBlog returns the nth blog from the list of blogs. func NthBlog(n int) (Blog, error) { blogs, err := GetBlogs() if err != nil { return Blog{}, err } if n < 0 || n >= len(blogs) { return Blog{}, fmt.Errorf("index out of range") } return blogs[n], nil }