mirror of
https://github.com/fiatjaf/nak.git
synced 2025-12-09 00:58:50 +00:00
nip: display markdown directly, default to list.
This commit is contained in:
193
nip.go
193
nip.go
@@ -9,30 +9,25 @@ import (
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/glamour"
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
type nipInfo struct {
|
||||
nip, desc, link string
|
||||
}
|
||||
|
||||
var nip = &cli.Command{
|
||||
Name: "nip",
|
||||
Usage: "get the description of a NIP from its number",
|
||||
Description: `fetches the NIPs README from GitHub and parses it to find the description of the given NIP number.
|
||||
|
||||
example:
|
||||
nak nip 1
|
||||
nak nip list
|
||||
nak nip open 1`,
|
||||
ArgsUsage: "<NIP number>",
|
||||
Usage: "list NIPs or get the description of a NIP from its number",
|
||||
Description: `lists NIPs, fetches and displays NIP text, or opens a NIP page in the browser.
|
||||
|
||||
examples:
|
||||
nak nip # list all NIPs
|
||||
nak nip 29 # shows nip29 details
|
||||
nak nip open 29 # opens nip29 in browser`,
|
||||
ArgsUsage: "[NIP number]",
|
||||
Commands: []*cli.Command{
|
||||
{
|
||||
Name: "list",
|
||||
Usage: "list all NIPs",
|
||||
Action: func(ctx context.Context, c *cli.Command) error {
|
||||
return iterateNips(func(nip, desc, link string) bool {
|
||||
stdout(nip + ": " + desc)
|
||||
return true
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "open",
|
||||
Usage: "open the NIP page in the browser",
|
||||
@@ -55,17 +50,12 @@ example:
|
||||
reqNum = normalize(reqNum)
|
||||
|
||||
foundLink := ""
|
||||
err := iterateNips(func(nip, desc, link string) bool {
|
||||
nipNum := normalize(nip)
|
||||
for info := range listnips() {
|
||||
nipNum := normalize(info.nip)
|
||||
if nipNum == reqNum {
|
||||
foundLink = link
|
||||
return false
|
||||
foundLink = info.link
|
||||
break
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if foundLink == "" {
|
||||
@@ -92,7 +82,11 @@ example:
|
||||
Action: func(ctx context.Context, c *cli.Command) error {
|
||||
reqNum := c.Args().First()
|
||||
if reqNum == "" {
|
||||
return fmt.Errorf("missing NIP number")
|
||||
// list all NIPs
|
||||
for info := range listnips() {
|
||||
stdout(info.nip + ": " + info.desc)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
normalize := func(s string) string {
|
||||
@@ -107,80 +101,101 @@ example:
|
||||
|
||||
reqNum = normalize(reqNum)
|
||||
|
||||
found := false
|
||||
err := iterateNips(func(nip, desc, link string) bool {
|
||||
nipNum := normalize(nip)
|
||||
var foundLink string
|
||||
for info := range listnips() {
|
||||
nipNum := normalize(info.nip)
|
||||
|
||||
if nipNum == reqNum {
|
||||
stdout(strings.TrimSpace(desc))
|
||||
found = true
|
||||
return false
|
||||
foundLink = info.link
|
||||
break
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !found {
|
||||
if foundLink == "" {
|
||||
return fmt.Errorf("NIP-%s not found", strings.ToUpper(reqNum))
|
||||
}
|
||||
|
||||
// fetch the NIP markdown
|
||||
url := "https://raw.githubusercontent.com/nostr-protocol/nips/master/" + foundLink
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to fetch NIP: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read NIP: %w", err)
|
||||
}
|
||||
|
||||
// render markdown
|
||||
rendered, err := glamour.Render(string(body), "auto")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to render markdown: %w", err)
|
||||
}
|
||||
|
||||
fmt.Print(rendered)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func iterateNips(yield func(nip, desc, link string) bool) error {
|
||||
resp, err := http.Get("https://raw.githubusercontent.com/nostr-protocol/nips/master/README.md")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to fetch NIPs README: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read NIPs README: %w", err)
|
||||
}
|
||||
bodyStr := string(body)
|
||||
epoch := strings.Index(bodyStr, "## List")
|
||||
|
||||
lines := strings.SplitSeq(bodyStr[epoch+8:], "\n")
|
||||
for line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if strings.HasPrefix(line, "##") {
|
||||
break
|
||||
func listnips() <-chan nipInfo {
|
||||
ch := make(chan nipInfo)
|
||||
go func() {
|
||||
defer close(ch)
|
||||
resp, err := http.Get("https://raw.githubusercontent.com/nostr-protocol/nips/master/README.md")
|
||||
if err != nil {
|
||||
// TODO: handle error? but since chan, maybe send error somehow, but for now, just close
|
||||
return
|
||||
}
|
||||
if !strings.HasPrefix(line, "- [NIP-") {
|
||||
continue
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
bodyStr := string(body)
|
||||
epoch := strings.Index(bodyStr, "## List")
|
||||
if epoch == -1 {
|
||||
return
|
||||
}
|
||||
|
||||
start := strings.Index(line, "[")
|
||||
end := strings.Index(line, "]")
|
||||
if start == -1 || end == -1 || end < start {
|
||||
continue
|
||||
lines := strings.SplitSeq(bodyStr[epoch+8:], "\n")
|
||||
for line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if strings.HasPrefix(line, "##") {
|
||||
break
|
||||
}
|
||||
if !strings.HasPrefix(line, "- [NIP-") {
|
||||
continue
|
||||
}
|
||||
|
||||
start := strings.Index(line, "[")
|
||||
end := strings.Index(line, "]")
|
||||
if start == -1 || end == -1 || end < start {
|
||||
continue
|
||||
}
|
||||
|
||||
content := line[start+1 : end]
|
||||
|
||||
parts := strings.SplitN(content, ":", 2)
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
nipPart := parts[0]
|
||||
descPart := parts[1]
|
||||
|
||||
rest := line[end+1:]
|
||||
linkStart := strings.Index(rest, "(")
|
||||
linkEnd := strings.Index(rest, ")")
|
||||
link := ""
|
||||
if linkStart != -1 && linkEnd != -1 && linkEnd > linkStart {
|
||||
link = rest[linkStart+1 : linkEnd]
|
||||
}
|
||||
|
||||
ch <- nipInfo{nipPart, strings.TrimSpace(descPart), link}
|
||||
}
|
||||
|
||||
content := line[start+1 : end]
|
||||
|
||||
parts := strings.SplitN(content, ":", 2)
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
nipPart := parts[0]
|
||||
descPart := parts[1]
|
||||
|
||||
rest := line[end+1:]
|
||||
linkStart := strings.Index(rest, "(")
|
||||
linkEnd := strings.Index(rest, ")")
|
||||
link := ""
|
||||
if linkStart != -1 && linkEnd != -1 && linkEnd > linkStart {
|
||||
link = rest[linkStart+1 : linkEnd]
|
||||
}
|
||||
|
||||
if !yield(nipPart, strings.TrimSpace(descPart), link) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}()
|
||||
return ch
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user