mirror of
https://github.com/fiatjaf/nak.git
synced 2025-12-09 17:18:50 +00:00
Compare commits
1 Commits
210cf66d5f
...
v0.17.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa2b9c461c |
415
git.go
415
git.go
@@ -8,7 +8,6 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"fiatjaf.com/nostr"
|
"fiatjaf.com/nostr"
|
||||||
"fiatjaf.com/nostr/nip19"
|
"fiatjaf.com/nostr/nip19"
|
||||||
@@ -93,9 +92,6 @@ aside from those, there is also:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var defaultOwner string
|
|
||||||
var defaultIdentifier string
|
|
||||||
|
|
||||||
// check if nip34.json already exists
|
// check if nip34.json already exists
|
||||||
existingConfig, err := readNip34ConfigFile("")
|
existingConfig, err := readNip34ConfigFile("")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -103,115 +99,44 @@ aside from those, there is also:
|
|||||||
if !c.Bool("force") && !c.Bool("interactive") {
|
if !c.Bool("force") && !c.Bool("interactive") {
|
||||||
return fmt.Errorf("nip34.json already exists, use --force to overwrite or --interactive to update")
|
return fmt.Errorf("nip34.json already exists, use --force to overwrite or --interactive to update")
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultIdentifier = existingConfig.Identifier
|
|
||||||
defaultOwner = existingConfig.Owner
|
|
||||||
} else {
|
|
||||||
// extract info from nostr:// git remotes (this is just for migrating from ngit)
|
|
||||||
if output, err := exec.Command("git", "remote", "-v").Output(); err == nil {
|
|
||||||
remotes := strings.Split(strings.TrimSpace(string(output)), "\n")
|
|
||||||
for _, remote := range remotes {
|
|
||||||
if strings.Contains(remote, "nostr://") {
|
|
||||||
parts := strings.Fields(remote)
|
|
||||||
if len(parts) >= 2 {
|
|
||||||
nostrURL := parts[1]
|
|
||||||
// parse nostr://npub.../relay_hostname/identifier
|
|
||||||
if remoteOwner, remoteIdentifier, relays, err := parseRepositoryAddress(ctx, nostrURL); err == nil && len(relays) > 0 {
|
|
||||||
defaultIdentifier = remoteIdentifier
|
|
||||||
defaultOwner = nip19.EncodeNpub(remoteOwner)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// get repository base directory name for defaults
|
// get repository base directory name for defaults
|
||||||
if defaultIdentifier == "" {
|
cwd, err := os.Getwd()
|
||||||
cwd, err := os.Getwd()
|
if err != nil {
|
||||||
if err != nil {
|
return fmt.Errorf("failed to get current directory: %w", err)
|
||||||
return fmt.Errorf("failed to get current directory: %w", err)
|
}
|
||||||
|
baseName := filepath.Base(cwd)
|
||||||
|
|
||||||
|
// get earliest unique commit
|
||||||
|
var earliestCommit string
|
||||||
|
if output, err := exec.Command("git", "rev-list", "--max-parents=0", "HEAD").Output(); err == nil {
|
||||||
|
earliest := strings.Split(strings.TrimSpace(string(output)), "\n")
|
||||||
|
if len(earliest) > 0 {
|
||||||
|
earliestCommit = earliest[0]
|
||||||
}
|
}
|
||||||
defaultIdentifier = filepath.Base(cwd)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// prompt for identifier first
|
// extract clone URLs from nostr:// git remotes
|
||||||
var identifier string
|
// (this is just for migrating from ngit)
|
||||||
if c.String("identifier") != "" {
|
var defaultCloneURLs []string
|
||||||
identifier = c.String("identifier")
|
if output, err := exec.Command("git", "remote", "-v").Output(); err == nil {
|
||||||
} else if c.Bool("interactive") {
|
remotes := strings.Split(strings.TrimSpace(string(output)), "\n")
|
||||||
if err := survey.AskOne(&survey.Input{
|
for _, remote := range remotes {
|
||||||
Message: "identifier",
|
if strings.Contains(remote, "nostr://") {
|
||||||
Default: defaultIdentifier,
|
parts := strings.Fields(remote)
|
||||||
}, &identifier); err != nil {
|
if len(parts) >= 2 {
|
||||||
return err
|
nostrURL := parts[1]
|
||||||
}
|
// parse nostr://npub.../relay_hostname/identifier
|
||||||
} else {
|
if owner, identifier, relays, err := parseRepositoryAddress(ctx, nostrURL); err == nil && len(relays) > 0 {
|
||||||
identifier = defaultIdentifier
|
relayURL := relays[0]
|
||||||
}
|
// convert to https://relay_hostname/npub.../identifier.git
|
||||||
|
cloneURL := fmt.Sprintf("http%s/%s/%s.git",
|
||||||
// prompt for owner pubkey
|
relayURL[2:], nip19.EncodeNpub(owner), identifier)
|
||||||
var owner nostr.PubKey
|
defaultCloneURLs = appendUnique(defaultCloneURLs, cloneURL)
|
||||||
var ownerStr string
|
}
|
||||||
if c.String("owner") != "" {
|
}
|
||||||
owner, err = parsePubKey(ownerStr)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid owner pubkey: %w", err)
|
|
||||||
}
|
|
||||||
ownerStr = nip19.EncodeNpub(owner)
|
|
||||||
} else if c.Bool("interactive") {
|
|
||||||
for {
|
|
||||||
if err := survey.AskOne(&survey.Input{
|
|
||||||
Message: "owner (npub or hex)",
|
|
||||||
Default: defaultOwner,
|
|
||||||
}, &ownerStr); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
owner, err = parsePubKey(ownerStr)
|
|
||||||
if err == nil {
|
|
||||||
ownerStr = nip19.EncodeNpub(owner)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return fmt.Errorf("owner pubkey is required (use --owner or --interactive)")
|
|
||||||
}
|
|
||||||
|
|
||||||
// try to fetch existing repository announcement (kind 30617)
|
|
||||||
var fetchedRepo *nip34.Repository
|
|
||||||
if existingConfig.Identifier == "" {
|
|
||||||
log(" searching for existing events... ")
|
|
||||||
repo, _, err := fetchRepositoryAndState(ctx, owner, identifier, nil)
|
|
||||||
if err == nil && repo.Event.ID != nostr.ZeroID {
|
|
||||||
fetchedRepo = &repo
|
|
||||||
log("found one from %s.\n", repo.Event.CreatedAt.Time().Format(time.DateOnly))
|
|
||||||
} else {
|
|
||||||
log("none found.\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// set config with fetched values or defaults
|
|
||||||
var config Nip34Config
|
|
||||||
if fetchedRepo != nil {
|
|
||||||
config = RepositoryToConfig(*fetchedRepo)
|
|
||||||
} else if existingConfig.Identifier != "" {
|
|
||||||
config = existingConfig
|
|
||||||
} else {
|
|
||||||
// get earliest unique commit
|
|
||||||
var earliestCommit string
|
|
||||||
if output, err := exec.Command("git", "rev-list", "--max-parents=0", "HEAD").Output(); err == nil {
|
|
||||||
earliestCommit = strings.TrimSpace(string(output))
|
|
||||||
}
|
|
||||||
|
|
||||||
config = Nip34Config{
|
|
||||||
Identifier: identifier,
|
|
||||||
Owner: ownerStr,
|
|
||||||
Name: identifier,
|
|
||||||
Description: "",
|
|
||||||
Web: []string{},
|
|
||||||
GraspServers: []string{"gitnostr.com", "relay.ngit.dev"},
|
|
||||||
EarliestUniqueCommit: earliestCommit,
|
|
||||||
Maintainers: []string{},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,84 +161,21 @@ aside from those, there is also:
|
|||||||
return defaultVals
|
return defaultVals
|
||||||
}
|
}
|
||||||
|
|
||||||
// override with flags and existing config
|
config := Nip34Config{
|
||||||
config.Identifier = getValue(existingConfig.Identifier, c.String("identifier"), config.Identifier)
|
Identifier: getValue(existingConfig.Identifier, c.String("identifier"), baseName),
|
||||||
config.Name = getValue(existingConfig.Name, c.String("name"), config.Name)
|
Name: getValue(existingConfig.Name, c.String("name"), baseName),
|
||||||
config.Description = getValue(existingConfig.Description, c.String("description"), config.Description)
|
Description: getValue(existingConfig.Description, c.String("description"), ""),
|
||||||
config.Web = getSliceValue(existingConfig.Web, c.StringSlice("web"), config.Web)
|
Web: getSliceValue(existingConfig.Web, c.StringSlice("web"), []string{}),
|
||||||
config.Owner = getValue(existingConfig.Owner, c.String("owner"), config.Owner)
|
Owner: getValue(existingConfig.Owner, c.String("owner"), ""),
|
||||||
config.GraspServers = getSliceValue(existingConfig.GraspServers, c.StringSlice("grasp-servers"), config.GraspServers)
|
GraspServers: getSliceValue(existingConfig.GraspServers, c.StringSlice("grasp-servers"), []string{"gitnostr.com", "relay.ngit.dev"}),
|
||||||
config.EarliestUniqueCommit = getValue(existingConfig.EarliestUniqueCommit, c.String("earliest-unique-commit"), config.EarliestUniqueCommit)
|
EarliestUniqueCommit: getValue(existingConfig.EarliestUniqueCommit, c.String("earliest-unique-commit"), earliestCommit),
|
||||||
config.Maintainers = getSliceValue(existingConfig.Maintainers, c.StringSlice("maintainers"), config.Maintainers)
|
Maintainers: getSliceValue(existingConfig.Maintainers, c.StringSlice("maintainers"), []string{}),
|
||||||
|
}
|
||||||
|
|
||||||
if c.Bool("interactive") {
|
if c.Bool("interactive") {
|
||||||
// prompt for name
|
if err := promptForConfig(&config); err != nil {
|
||||||
if err := survey.AskOne(&survey.Input{
|
|
||||||
Message: "name",
|
|
||||||
Default: config.Name,
|
|
||||||
}, &config.Name); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// prompt for description
|
|
||||||
if err := survey.AskOne(&survey.Input{
|
|
||||||
Message: "description",
|
|
||||||
Default: config.Description,
|
|
||||||
}, &config.Description); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// prompt for grasp servers
|
|
||||||
graspServers, err := promptForStringList("grasp servers", config.GraspServers, []string{
|
|
||||||
"gitnostr.com",
|
|
||||||
"relay.ngit.dev",
|
|
||||||
"pyramid.fiatjaf.com",
|
|
||||||
"git.shakespeare.dyi",
|
|
||||||
}, graspServerHost, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
config.GraspServers = graspServers
|
|
||||||
|
|
||||||
// prompt for web URLs
|
|
||||||
webURLs, err := promptForStringList("web URLs", config.Web, []string{
|
|
||||||
fmt.Sprintf("https://gitworkshop.dev/%s/%s",
|
|
||||||
nip19.EncodeNpub(nostr.MustPubKeyFromHex(config.Owner)),
|
|
||||||
config.Identifier,
|
|
||||||
),
|
|
||||||
}, func(s string) string {
|
|
||||||
return "http" + nostr.NormalizeURL(s)[2:]
|
|
||||||
}, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
config.Web = webURLs
|
|
||||||
|
|
||||||
// prompt for earliest unique commit
|
|
||||||
if err := survey.AskOne(&survey.Input{
|
|
||||||
Message: "earliest unique commit",
|
|
||||||
Default: config.EarliestUniqueCommit,
|
|
||||||
}, &config.EarliestUniqueCommit); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prompt for maintainers
|
|
||||||
maintainers, err := promptForStringList("maintainers", config.Maintainers, []string{}, nil, func(s string) bool {
|
|
||||||
pk, err := parsePubKey(s)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if pk.Hex() == config.Owner {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
config.Maintainers = maintainers
|
|
||||||
|
|
||||||
log("\n")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := config.Validate(); err != nil {
|
if err := config.Validate(); err != nil {
|
||||||
@@ -335,7 +197,7 @@ aside from those, there is also:
|
|||||||
|
|
||||||
log("edit %s if needed, then run %s to publish.\n",
|
log("edit %s if needed, then run %s to publish.\n",
|
||||||
color.CyanString("nip34.json"),
|
color.CyanString("nip34.json"),
|
||||||
color.CyanString("nak git sync"))
|
color.CyanString("nak git announce"))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
@@ -404,7 +266,22 @@ aside from those, there is also:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// write nip34.json inside cloned directory
|
// write nip34.json inside cloned directory
|
||||||
localConfig := RepositoryToConfig(repo)
|
localConfig := Nip34Config{
|
||||||
|
Identifier: repo.ID,
|
||||||
|
Name: repo.Name,
|
||||||
|
Description: repo.Description,
|
||||||
|
Web: repo.Web,
|
||||||
|
Owner: nip19.EncodeNpub(repo.Event.PubKey),
|
||||||
|
GraspServers: make([]string, 0, len(repo.Relays)),
|
||||||
|
EarliestUniqueCommit: repo.EarliestUniqueCommitID,
|
||||||
|
Maintainers: make([]string, 0, len(repo.Maintainers)),
|
||||||
|
}
|
||||||
|
for _, r := range repo.Relays {
|
||||||
|
localConfig.GraspServers = append(localConfig.GraspServers, nostr.NormalizeURL(r))
|
||||||
|
}
|
||||||
|
for _, m := range repo.Maintainers {
|
||||||
|
localConfig.Maintainers = append(localConfig.Maintainers, nip19.EncodeNpub(m))
|
||||||
|
}
|
||||||
|
|
||||||
if err := localConfig.Validate(); err != nil {
|
if err := localConfig.Validate(); err != nil {
|
||||||
return fmt.Errorf("invalid config: %w", err)
|
return fmt.Errorf("invalid config: %w", err)
|
||||||
@@ -546,7 +423,8 @@ aside from those, there is also:
|
|||||||
pushSuccesses := 0
|
pushSuccesses := 0
|
||||||
for _, relay := range repo.Relays {
|
for _, relay := range repo.Relays {
|
||||||
relayURL := nostr.NormalizeURL(relay)
|
relayURL := nostr.NormalizeURL(relay)
|
||||||
remoteName := gitRemoteName(relayURL)
|
remoteName := "nip34/grasp/" + strings.TrimPrefix(relayURL, "wss://")
|
||||||
|
remoteName = strings.TrimPrefix(remoteName, "ws://")
|
||||||
|
|
||||||
log("pushing to %s...\n", color.CyanString(remoteName))
|
log("pushing to %s...\n", color.CyanString(remoteName))
|
||||||
pushArgs := []string{"push", remoteName, fmt.Sprintf("%s:refs/heads/%s", localBranch, remoteBranch)}
|
pushArgs := []string{"push", remoteName, fmt.Sprintf("%s:refs/heads/%s", localBranch, remoteBranch)}
|
||||||
@@ -739,45 +617,50 @@ aside from those, there is also:
|
|||||||
|
|
||||||
func promptForStringList(
|
func promptForStringList(
|
||||||
name string,
|
name string,
|
||||||
|
existing []string,
|
||||||
defaults []string,
|
defaults []string,
|
||||||
alternatives []string,
|
|
||||||
normalize func(string) string,
|
normalize func(string) string,
|
||||||
validate func(string) bool,
|
validate func(string) bool,
|
||||||
) ([]string, error) {
|
) ([]string, error) {
|
||||||
options := make([]string, 0, len(defaults)+len(alternatives)+1)
|
options := make([]string, 0, len(defaults)+len(existing)+1)
|
||||||
options = append(options, defaults...)
|
options = append(options, defaults...)
|
||||||
|
options = append(options, "add another")
|
||||||
|
|
||||||
// add existing not in options
|
// add existing not in options
|
||||||
for _, item := range alternatives {
|
for _, item := range existing {
|
||||||
if !slices.Contains(options, item) {
|
if !slices.Contains(options, item) {
|
||||||
options = append(options, item)
|
options = append(options, item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
options = append(options, "add another")
|
selected := make([]string, len(existing))
|
||||||
|
copy(selected, existing)
|
||||||
selected := make([]string, len(defaults))
|
|
||||||
copy(selected, defaults)
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
newSelected := []string{}
|
prompt := &survey.MultiSelect{
|
||||||
if err := survey.AskOne(&survey.MultiSelect{
|
|
||||||
Message: name,
|
Message: name,
|
||||||
Options: options,
|
Options: options,
|
||||||
Default: selected,
|
Default: selected,
|
||||||
PageSize: 20,
|
PageSize: 20,
|
||||||
}, &newSelected); err != nil {
|
}
|
||||||
|
|
||||||
|
if err := survey.AskOne(prompt, &selected); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
selected = newSelected
|
|
||||||
|
|
||||||
if slices.Contains(selected, "add another") {
|
if slices.Contains(selected, "add another") {
|
||||||
selected = slices.DeleteFunc(selected, func(s string) bool { return s == "add another" })
|
selected = slices.DeleteFunc(selected, func(s string) bool { return s == "add another" })
|
||||||
|
|
||||||
|
singular := name
|
||||||
|
if strings.HasSuffix(singular, "s") {
|
||||||
|
singular = singular[:len(singular)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
newPrompt := &survey.Input{
|
||||||
|
Message: fmt.Sprintf("enter new %s", singular),
|
||||||
|
}
|
||||||
var newItem string
|
var newItem string
|
||||||
if err := survey.AskOne(&survey.Input{
|
if err := survey.AskOne(newPrompt, &newItem); err != nil {
|
||||||
Message: fmt.Sprintf("enter new %s", strings.TrimSuffix(name, "s")),
|
|
||||||
}, &newItem); err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -807,6 +690,97 @@ func promptForStringList(
|
|||||||
return selected, nil
|
return selected, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func promptForConfig(config *Nip34Config) error {
|
||||||
|
log("\nenter repository details (use arrow keys to navigate, space to select/deselect, enter to confirm):\n\n")
|
||||||
|
|
||||||
|
// prompt for identifier
|
||||||
|
identifierPrompt := &survey.Input{
|
||||||
|
Message: "identifier",
|
||||||
|
Default: config.Identifier,
|
||||||
|
}
|
||||||
|
if err := survey.AskOne(identifierPrompt, &config.Identifier); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// prompt for name
|
||||||
|
namePrompt := &survey.Input{
|
||||||
|
Message: "name",
|
||||||
|
Default: config.Name,
|
||||||
|
}
|
||||||
|
if err := survey.AskOne(namePrompt, &config.Name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// prompt for description
|
||||||
|
descPrompt := &survey.Input{
|
||||||
|
Message: "description",
|
||||||
|
Default: config.Description,
|
||||||
|
}
|
||||||
|
if err := survey.AskOne(descPrompt, &config.Description); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// prompt for owner
|
||||||
|
for {
|
||||||
|
ownerPrompt := &survey.Input{
|
||||||
|
Message: "owner (npub or hex)",
|
||||||
|
Default: config.Owner,
|
||||||
|
}
|
||||||
|
if err := survey.AskOne(ownerPrompt, &config.Owner); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if pubkey, err := parsePubKey(config.Owner); err == nil {
|
||||||
|
config.Owner = pubkey.Hex()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// prompt for grasp servers
|
||||||
|
graspServers, err := promptForStringList("grasp servers", config.GraspServers, []string{
|
||||||
|
"gitnostr.com",
|
||||||
|
"relay.ngit.dev",
|
||||||
|
"pyramid.fiatjaf.com",
|
||||||
|
"git.shakespeare.dyi",
|
||||||
|
}, graspServerHost, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
config.GraspServers = graspServers
|
||||||
|
|
||||||
|
// prompt for web URLs
|
||||||
|
webURLs, err := promptForStringList("web URLs", config.Web, []string{
|
||||||
|
fmt.Sprintf("https://gitworkshop.dev/%s/%s",
|
||||||
|
nip19.EncodeNpub(nostr.MustPubKeyFromHex(config.Owner)),
|
||||||
|
config.Identifier,
|
||||||
|
),
|
||||||
|
}, func(s string) string {
|
||||||
|
return "http" + nostr.NormalizeURL(s)[2:]
|
||||||
|
}, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
config.Web = webURLs
|
||||||
|
|
||||||
|
// Prompt for maintainers
|
||||||
|
maintainers, err := promptForStringList("maintainers", config.Maintainers, []string{}, nil, func(s string) bool {
|
||||||
|
pk, err := parsePubKey(s)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if pk.Hex() == config.Owner {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
config.Maintainers = maintainers
|
||||||
|
|
||||||
|
log("\n")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func gitSync(ctx context.Context, signer nostr.Keyer) (nip34.Repository, *nip34.RepositoryState, error) {
|
func gitSync(ctx context.Context, signer nostr.Keyer) (nip34.Repository, *nip34.RepositoryState, error) {
|
||||||
// read current nip34.json
|
// read current nip34.json
|
||||||
localConfig, err := readNip34ConfigFile("")
|
localConfig, err := readNip34ConfigFile("")
|
||||||
@@ -940,7 +914,7 @@ func gitSync(ctx context.Context, signer nostr.Keyer) (nip34.Repository, *nip34.
|
|||||||
func fetchFromRemotes(ctx context.Context, targetDir string, repo nip34.Repository) {
|
func fetchFromRemotes(ctx context.Context, targetDir string, repo nip34.Repository) {
|
||||||
// fetch from each grasp remote
|
// fetch from each grasp remote
|
||||||
for _, grasp := range repo.Relays {
|
for _, grasp := range repo.Relays {
|
||||||
remoteName := gitRemoteName(grasp)
|
remoteName := "nip34/grasp/" + strings.Split(grasp, "/")[2]
|
||||||
|
|
||||||
logverbose("fetching from %s...\n", remoteName)
|
logverbose("fetching from %s...\n", remoteName)
|
||||||
fetchCmd := exec.Command("git", "fetch", remoteName)
|
fetchCmd := exec.Command("git", "fetch", remoteName)
|
||||||
@@ -966,16 +940,14 @@ func gitSetupRemotes(ctx context.Context, dir string, repo nip34.Repository) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// delete all nip34/grasp/ remotes that we don't have anymore in repo
|
// delete all nip34/grasp/ remotes
|
||||||
remotes := strings.Split(strings.TrimSpace(string(output)), "\n")
|
remotes := strings.Split(strings.TrimSpace(string(output)), "\n")
|
||||||
for i, remote := range remotes {
|
for i, remote := range remotes {
|
||||||
remote = strings.TrimSpace(remote)
|
remote = strings.TrimSpace(remote)
|
||||||
remotes[i] = remote
|
remotes[i] = remote
|
||||||
|
|
||||||
if strings.HasPrefix(remote, "nip34/grasp/") {
|
if strings.HasPrefix(remote, "nip34/grasp/") {
|
||||||
graspURL := rebuildGraspURLFromRemote(remote)
|
if !slices.Contains(repo.Relays, nostr.NormalizeURL(remote[12:])) {
|
||||||
|
|
||||||
if !slices.Contains(repo.Relays, nostr.NormalizeURL(graspURL)) {
|
|
||||||
delCmd := exec.Command("git", "remote", "remove", remote)
|
delCmd := exec.Command("git", "remote", "remove", remote)
|
||||||
if dir != "" {
|
if dir != "" {
|
||||||
delCmd.Dir = dir
|
delCmd.Dir = dir
|
||||||
@@ -989,7 +961,7 @@ func gitSetupRemotes(ctx context.Context, dir string, repo nip34.Repository) {
|
|||||||
|
|
||||||
// create new remotes for each grasp server
|
// create new remotes for each grasp server
|
||||||
for _, relay := range repo.Relays {
|
for _, relay := range repo.Relays {
|
||||||
remote := gitRemoteName(relay)
|
remote := "nip34/grasp/" + strings.TrimPrefix(relay, "wss://")
|
||||||
|
|
||||||
if !slices.Contains(remotes, remote) {
|
if !slices.Contains(remotes, remote) {
|
||||||
// construct the git URL
|
// construct the git URL
|
||||||
@@ -1395,6 +1367,8 @@ func figureOutBranches(c *cli.Command, refspec string, isPush bool) (
|
|||||||
return localBranch, remoteBranch, nil
|
return localBranch, remoteBranch, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func graspServerHost(s string) string { return strings.SplitN(nostr.NormalizeURL(s), "/", 3)[2] }
|
||||||
|
|
||||||
type Nip34Config struct {
|
type Nip34Config struct {
|
||||||
Identifier string `json:"identifier"`
|
Identifier string `json:"identifier"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
@@ -1406,26 +1380,6 @@ type Nip34Config struct {
|
|||||||
Maintainers []string `json:"maintainers"`
|
Maintainers []string `json:"maintainers"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func RepositoryToConfig(repo nip34.Repository) Nip34Config {
|
|
||||||
config := Nip34Config{
|
|
||||||
Identifier: repo.ID,
|
|
||||||
Name: repo.Name,
|
|
||||||
Description: repo.Description,
|
|
||||||
Web: repo.Web,
|
|
||||||
Owner: nip19.EncodeNpub(repo.Event.PubKey),
|
|
||||||
GraspServers: make([]string, 0, len(repo.Relays)),
|
|
||||||
EarliestUniqueCommit: repo.EarliestUniqueCommitID,
|
|
||||||
Maintainers: make([]string, 0, len(repo.Maintainers)),
|
|
||||||
}
|
|
||||||
for _, r := range repo.Relays {
|
|
||||||
config.GraspServers = append(config.GraspServers, graspServerHost(r))
|
|
||||||
}
|
|
||||||
for _, m := range repo.Maintainers {
|
|
||||||
config.Maintainers = append(config.Maintainers, nip19.EncodeNpub(m))
|
|
||||||
}
|
|
||||||
return config
|
|
||||||
}
|
|
||||||
|
|
||||||
func (localConfig Nip34Config) Validate() error {
|
func (localConfig Nip34Config) Validate() error {
|
||||||
_, err := parsePubKey(localConfig.Owner)
|
_, err := parsePubKey(localConfig.Owner)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1476,18 +1430,3 @@ func (localConfig Nip34Config) ToRepository() nip34.Repository {
|
|||||||
|
|
||||||
return localRepo
|
return localRepo
|
||||||
}
|
}
|
||||||
|
|
||||||
func gitRemoteName(graspURL string) string {
|
|
||||||
host := graspServerHost(graspURL)
|
|
||||||
host = strings.Replace(host, ":", "__", 1)
|
|
||||||
return "nip34/grasp/" + host
|
|
||||||
}
|
|
||||||
|
|
||||||
func rebuildGraspURLFromRemote(remoteName string) string {
|
|
||||||
host := strings.TrimPrefix(remoteName, "nip34/grasp/")
|
|
||||||
return strings.Replace(host, "__", ":", 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func graspServerHost(s string) string {
|
|
||||||
return strings.SplitN(nostr.NormalizeURL(s), "/", 3)[2]
|
|
||||||
}
|
|
||||||
|
|||||||
23
go.mod
23
go.mod
@@ -4,7 +4,7 @@ go 1.25
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
fiatjaf.com/lib v0.3.1
|
fiatjaf.com/lib v0.3.1
|
||||||
fiatjaf.com/nostr v0.0.0-20251126101225-44130595c606
|
fiatjaf.com/nostr v0.0.0-20251124002842-de54dd1fa4b8
|
||||||
github.com/AlecAivazis/survey/v2 v2.3.7
|
github.com/AlecAivazis/survey/v2 v2.3.7
|
||||||
github.com/bep/debounce v1.2.1
|
github.com/bep/debounce v1.2.1
|
||||||
github.com/btcsuite/btcd/btcec/v2 v2.3.6
|
github.com/btcsuite/btcd/btcec/v2 v2.3.6
|
||||||
@@ -31,29 +31,18 @@ require (
|
|||||||
require (
|
require (
|
||||||
github.com/FastFilter/xorfilter v0.2.1 // indirect
|
github.com/FastFilter/xorfilter v0.2.1 // indirect
|
||||||
github.com/ImVexed/fasturl v0.0.0-20230304231329-4e41488060f3 // indirect
|
github.com/ImVexed/fasturl v0.0.0-20230304231329-4e41488060f3 // indirect
|
||||||
github.com/alecthomas/chroma/v2 v2.14.0 // indirect
|
|
||||||
github.com/andybalholm/brotli v1.1.1 // indirect
|
github.com/andybalholm/brotli v1.1.1 // indirect
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
|
||||||
github.com/aymerick/douceur v0.2.0 // indirect
|
|
||||||
github.com/bluekeyes/go-gitdiff v0.7.1 // indirect
|
github.com/bluekeyes/go-gitdiff v0.7.1 // indirect
|
||||||
github.com/btcsuite/btcd v0.24.2 // indirect
|
github.com/btcsuite/btcd v0.24.2 // indirect
|
||||||
github.com/btcsuite/btcd/btcutil v1.1.5 // indirect
|
github.com/btcsuite/btcd/btcutil v1.1.5 // indirect
|
||||||
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect
|
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
|
|
||||||
github.com/charmbracelet/glamour v0.10.0 // indirect
|
|
||||||
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 // indirect
|
|
||||||
github.com/charmbracelet/x/ansi v0.8.0 // indirect
|
|
||||||
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
|
|
||||||
github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf // indirect
|
|
||||||
github.com/charmbracelet/x/term v0.2.1 // indirect
|
|
||||||
github.com/chzyer/logex v1.1.10 // indirect
|
github.com/chzyer/logex v1.1.10 // indirect
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 // indirect
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 // indirect
|
||||||
github.com/coder/websocket v1.8.14 // indirect
|
github.com/coder/websocket v1.8.14 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/decred/dcrd/crypto/blake256 v1.1.0 // indirect
|
github.com/decred/dcrd/crypto/blake256 v1.1.0 // indirect
|
||||||
github.com/dgraph-io/ristretto/v2 v2.3.0 // indirect
|
github.com/dgraph-io/ristretto/v2 v2.3.0 // indirect
|
||||||
github.com/dlclark/regexp2 v1.11.0 // indirect
|
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/elliotchance/pie/v2 v2.7.0 // indirect
|
github.com/elliotchance/pie/v2 v2.7.0 // indirect
|
||||||
github.com/elnosh/gonuts v0.4.2 // indirect
|
github.com/elnosh/gonuts v0.4.2 // indirect
|
||||||
@@ -61,7 +50,6 @@ require (
|
|||||||
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
||||||
github.com/go-git/go-git/v5 v5.16.3 // indirect
|
github.com/go-git/go-git/v5 v5.16.3 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/gorilla/css v1.0.1 // indirect
|
|
||||||
github.com/hablullah/go-hijri v1.0.2 // indirect
|
github.com/hablullah/go-hijri v1.0.2 // indirect
|
||||||
github.com/hablullah/go-juliandays v1.0.0 // indirect
|
github.com/hablullah/go-juliandays v1.0.0 // indirect
|
||||||
github.com/jalaali/go-jalaali v0.0.0-20210801064154-80525e88d958 // indirect
|
github.com/jalaali/go-jalaali v0.0.0-20210801064154-80525e88d958 // indirect
|
||||||
@@ -69,18 +57,12 @@ require (
|
|||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||||
github.com/klauspost/compress v1.18.0 // indirect
|
github.com/klauspost/compress v1.18.0 // indirect
|
||||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
|
||||||
github.com/magefile/mage v1.14.0 // indirect
|
github.com/magefile/mage v1.14.0 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
|
||||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
|
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
|
||||||
github.com/microcosm-cc/bluemonday v1.0.27 // indirect
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/muesli/reflow v0.3.0 // indirect
|
|
||||||
github.com/muesli/termenv v0.16.0 // indirect
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
|
||||||
github.com/rs/cors v1.11.1 // indirect
|
github.com/rs/cors v1.11.1 // indirect
|
||||||
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 // indirect
|
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 // indirect
|
||||||
github.com/templexxx/cpu v0.0.1 // indirect
|
github.com/templexxx/cpu v0.0.1 // indirect
|
||||||
@@ -93,9 +75,6 @@ require (
|
|||||||
github.com/valyala/fasthttp v1.59.0 // indirect
|
github.com/valyala/fasthttp v1.59.0 // indirect
|
||||||
github.com/wasilibs/go-re2 v1.3.0 // indirect
|
github.com/wasilibs/go-re2 v1.3.0 // indirect
|
||||||
github.com/x448/float16 v0.8.4 // indirect
|
github.com/x448/float16 v0.8.4 // indirect
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
|
||||||
github.com/yuin/goldmark v1.7.8 // indirect
|
|
||||||
github.com/yuin/goldmark-emoji v1.0.5 // indirect
|
|
||||||
go.etcd.io/bbolt v1.4.2 // indirect
|
go.etcd.io/bbolt v1.4.2 // indirect
|
||||||
golang.org/x/crypto v0.39.0 // indirect
|
golang.org/x/crypto v0.39.0 // indirect
|
||||||
golang.org/x/net v0.41.0 // indirect
|
golang.org/x/net v0.41.0 // indirect
|
||||||
|
|||||||
50
go.sum
50
go.sum
@@ -1,7 +1,7 @@
|
|||||||
fiatjaf.com/lib v0.3.1 h1:/oFQwNtFRfV+ukmOCxfBEAuayoLwXp4wu2/fz5iHpwA=
|
fiatjaf.com/lib v0.3.1 h1:/oFQwNtFRfV+ukmOCxfBEAuayoLwXp4wu2/fz5iHpwA=
|
||||||
fiatjaf.com/lib v0.3.1/go.mod h1:Ycqq3+mJ9jAWu7XjbQI1cVr+OFgnHn79dQR5oTII47g=
|
fiatjaf.com/lib v0.3.1/go.mod h1:Ycqq3+mJ9jAWu7XjbQI1cVr+OFgnHn79dQR5oTII47g=
|
||||||
fiatjaf.com/nostr v0.0.0-20251126101225-44130595c606 h1:wQHJ0TFA0Fuq92p/6u6AbsBFq6ZVToSdxV6puXVIruI=
|
fiatjaf.com/nostr v0.0.0-20251124002842-de54dd1fa4b8 h1:R16mnlJ3qvVar7G4rzY+Z+mEAf2O6wpHTlRlHAt2Od8=
|
||||||
fiatjaf.com/nostr v0.0.0-20251126101225-44130595c606/go.mod h1:ue7yw0zHfZj23Ml2kVSdBx0ENEaZiuvGxs/8VEN93FU=
|
fiatjaf.com/nostr v0.0.0-20251124002842-de54dd1fa4b8/go.mod h1:QEGyTgAjjTFwDx2BJGZiCdmoAcWA/G+sQy7wDqKzSPU=
|
||||||
github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ=
|
github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ=
|
||||||
github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo=
|
github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo=
|
||||||
github.com/FastFilter/xorfilter v0.2.1 h1:lbdeLG9BdpquK64ZsleBS8B4xO/QW1IM0gMzF7KaBKc=
|
github.com/FastFilter/xorfilter v0.2.1 h1:lbdeLG9BdpquK64ZsleBS8B4xO/QW1IM0gMzF7KaBKc=
|
||||||
@@ -13,14 +13,8 @@ github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDe
|
|||||||
github.com/PowerDNS/lmdb-go v1.9.3 h1:AUMY2pZT8WRpkEv39I9Id3MuoHd+NZbTVpNhruVkPTg=
|
github.com/PowerDNS/lmdb-go v1.9.3 h1:AUMY2pZT8WRpkEv39I9Id3MuoHd+NZbTVpNhruVkPTg=
|
||||||
github.com/PowerDNS/lmdb-go v1.9.3/go.mod h1:TE0l+EZK8Z1B4dx070ZxkWTlp8RG1mjN0/+FkFRQMtU=
|
github.com/PowerDNS/lmdb-go v1.9.3/go.mod h1:TE0l+EZK8Z1B4dx070ZxkWTlp8RG1mjN0/+FkFRQMtU=
|
||||||
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
|
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
|
||||||
github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E=
|
|
||||||
github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I=
|
|
||||||
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
||||||
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
|
||||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
|
||||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
|
||||||
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
|
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
|
||||||
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
|
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
|
||||||
github.com/bluekeyes/go-gitdiff v0.7.1 h1:graP4ElLRshr8ecu0UtqfNTCHrtSyZd3DABQm/DWesQ=
|
github.com/bluekeyes/go-gitdiff v0.7.1 h1:graP4ElLRshr8ecu0UtqfNTCHrtSyZd3DABQm/DWesQ=
|
||||||
@@ -55,20 +49,6 @@ github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
|||||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
|
|
||||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
|
|
||||||
github.com/charmbracelet/glamour v0.10.0 h1:MtZvfwsYCx8jEPFJm3rIBFIMZUfUJ765oX8V6kXldcY=
|
|
||||||
github.com/charmbracelet/glamour v0.10.0/go.mod h1:f+uf+I/ChNmqo087elLnVdCiVgjSKWuXa/l6NU2ndYk=
|
|
||||||
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 h1:ZR7e0ro+SZZiIZD7msJyA+NjkCNNavuiPBLgerbOziE=
|
|
||||||
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834/go.mod h1:aKC/t2arECF6rNOnaKaVU6y4t4ZeHQzqfxedE/VkVhA=
|
|
||||||
github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE=
|
|
||||||
github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q=
|
|
||||||
github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k=
|
|
||||||
github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
|
|
||||||
github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf h1:rLG0Yb6MQSDKdB52aGX55JT1oi0P0Kuaj7wi1bLUpnI=
|
|
||||||
github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf/go.mod h1:B3UgsnsBZS/eX42BlaNiJkD1pPOUa+oF1IYC6Yd2CEU=
|
|
||||||
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
|
|
||||||
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
|
|
||||||
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
|
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
|
||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
|
||||||
@@ -94,8 +74,6 @@ github.com/dgraph-io/ristretto/v2 v2.3.0 h1:qTQ38m7oIyd4GAed/QkUZyPFNMnvVWyazGXR
|
|||||||
github.com/dgraph-io/ristretto/v2 v2.3.0/go.mod h1:gpoRV3VzrEY1a9dWAYV6T1U7YzfgttXdd/ZzL1s9OZM=
|
github.com/dgraph-io/ristretto/v2 v2.3.0/go.mod h1:gpoRV3VzrEY1a9dWAYV6T1U7YzfgttXdd/ZzL1s9OZM=
|
||||||
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38=
|
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38=
|
||||||
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||||
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
|
|
||||||
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
|
||||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
github.com/dvyukov/go-fuzz v0.0.0-20200318091601-be3528f3a813/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
|
github.com/dvyukov/go-fuzz v0.0.0-20200318091601-be3528f3a813/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
|
||||||
@@ -129,8 +107,6 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX
|
|||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
|
|
||||||
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
|
|
||||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/hablullah/go-hijri v1.0.2 h1:drT/MZpSZJQXo7jftf5fthArShcaMtsal0Zf/dnmp6k=
|
github.com/hablullah/go-hijri v1.0.2 h1:drT/MZpSZJQXo7jftf5fthArShcaMtsal0Zf/dnmp6k=
|
||||||
github.com/hablullah/go-hijri v1.0.2/go.mod h1:OS5qyYLDjORXzK4O1adFw9Q5WfhOcMdAKglDkcTxgWQ=
|
github.com/hablullah/go-hijri v1.0.2/go.mod h1:OS5qyYLDjORXzK4O1adFw9Q5WfhOcMdAKglDkcTxgWQ=
|
||||||
@@ -163,8 +139,6 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
|
|||||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||||
github.com/liamg/magic v0.0.1 h1:Ru22ElY+sCh6RvRTWjQzKKCxsEco8hE0co8n1qe7TBM=
|
github.com/liamg/magic v0.0.1 h1:Ru22ElY+sCh6RvRTWjQzKKCxsEco8hE0co8n1qe7TBM=
|
||||||
github.com/liamg/magic v0.0.1/go.mod h1:yQkOmZZI52EA+SQ2xyHpVw8fNvTBruF873Y+Vt6S+fk=
|
github.com/liamg/magic v0.0.1/go.mod h1:yQkOmZZI52EA+SQ2xyHpVw8fNvTBruF873Y+Vt6S+fk=
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
|
||||||
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=
|
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=
|
||||||
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
||||||
github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8=
|
github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8=
|
||||||
@@ -179,17 +153,12 @@ github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stg
|
|||||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
|
||||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
|
||||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
|
||||||
github.com/mattn/go-tty v0.0.7 h1:KJ486B6qI8+wBO7kQxYgmmEFDaFEE96JMBQ7h400N8Q=
|
github.com/mattn/go-tty v0.0.7 h1:KJ486B6qI8+wBO7kQxYgmmEFDaFEE96JMBQ7h400N8Q=
|
||||||
github.com/mattn/go-tty v0.0.7/go.mod h1:f2i5ZOvXBU/tCABmLmOfzLz9azMo5wdAaElRNnJKr+k=
|
github.com/mattn/go-tty v0.0.7/go.mod h1:f2i5ZOvXBU/tCABmLmOfzLz9azMo5wdAaElRNnJKr+k=
|
||||||
github.com/mdp/qrterminal/v3 v3.2.1 h1:6+yQjiiOsSuXT5n9/m60E54vdgFsw0zhADHhHLrFet4=
|
github.com/mdp/qrterminal/v3 v3.2.1 h1:6+yQjiiOsSuXT5n9/m60E54vdgFsw0zhADHhHLrFet4=
|
||||||
github.com/mdp/qrterminal/v3 v3.2.1/go.mod h1:jOTmXvnBsMy5xqLniO0R++Jmjs2sTm9dFSuQ5kpz/SU=
|
github.com/mdp/qrterminal/v3 v3.2.1/go.mod h1:jOTmXvnBsMy5xqLniO0R++Jmjs2sTm9dFSuQ5kpz/SU=
|
||||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
|
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
|
||||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||||
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
|
|
||||||
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
|
|
||||||
github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78=
|
github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78=
|
||||||
github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI=
|
github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
@@ -197,10 +166,6 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
|
|||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
|
|
||||||
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
|
||||||
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
|
|
||||||
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
|
|
||||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
@@ -216,10 +181,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
|||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg=
|
github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg=
|
||||||
github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
|
||||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
|
||||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
|
||||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||||
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
|
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
|
||||||
@@ -261,16 +222,9 @@ github.com/wasilibs/nottinygc v0.4.0 h1:h1TJMihMC4neN6Zq+WKpLxgd9xCFMw7O9ETLwY2e
|
|||||||
github.com/wasilibs/nottinygc v0.4.0/go.mod h1:oDcIotskuYNMpqMF23l7Z8uzD4TC0WXHK8jetlB3HIo=
|
github.com/wasilibs/nottinygc v0.4.0/go.mod h1:oDcIotskuYNMpqMF23l7Z8uzD4TC0WXHK8jetlB3HIo=
|
||||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
|
||||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
|
||||||
github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
|
|
||||||
github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
|
||||||
github.com/yuin/goldmark-emoji v1.0.5 h1:EMVWyCGPlXJfUXBXpuMu+ii3TIaxbVBnEX9uaDC4cIk=
|
|
||||||
github.com/yuin/goldmark-emoji v1.0.5/go.mod h1:tTkZEbwu5wkPmgTcitqddVxY9osFZiavD+r4AzQrh1U=
|
|
||||||
go.etcd.io/bbolt v1.4.2 h1:IrUHp260R8c+zYx/Tm8QZr04CX+qWS5PGfPdevhdm1I=
|
go.etcd.io/bbolt v1.4.2 h1:IrUHp260R8c+zYx/Tm8QZr04CX+qWS5PGfPdevhdm1I=
|
||||||
go.etcd.io/bbolt v1.4.2/go.mod h1:Is8rSHO/b4f3XigBC0lL0+4FwAQv3HXEEIgFMuKHceM=
|
go.etcd.io/bbolt v1.4.2/go.mod h1:Is8rSHO/b4f3XigBC0lL0+4FwAQv3HXEEIgFMuKHceM=
|
||||||
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
|
|||||||
1
main.go
1
main.go
@@ -49,7 +49,6 @@ var app = &cli.Command{
|
|||||||
fsCmd,
|
fsCmd,
|
||||||
publish,
|
publish,
|
||||||
git,
|
git,
|
||||||
nip,
|
|
||||||
},
|
},
|
||||||
Version: version,
|
Version: version,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
|
|||||||
201
nip.go
201
nip.go
@@ -1,201 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"os/exec"
|
|
||||||
"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: "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: "open",
|
|
||||||
Usage: "open the NIP page in the browser",
|
|
||||||
Action: func(ctx context.Context, c *cli.Command) error {
|
|
||||||
reqNum := c.Args().First()
|
|
||||||
if reqNum == "" {
|
|
||||||
return fmt.Errorf("missing NIP number")
|
|
||||||
}
|
|
||||||
|
|
||||||
normalize := func(s string) string {
|
|
||||||
s = strings.ToLower(s)
|
|
||||||
s = strings.TrimPrefix(s, "nip-")
|
|
||||||
s = strings.TrimLeft(s, "0")
|
|
||||||
if s == "" {
|
|
||||||
s = "0"
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
reqNum = normalize(reqNum)
|
|
||||||
|
|
||||||
foundLink := ""
|
|
||||||
for info := range listnips() {
|
|
||||||
nipNum := normalize(info.nip)
|
|
||||||
if nipNum == reqNum {
|
|
||||||
foundLink = info.link
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if foundLink == "" {
|
|
||||||
return fmt.Errorf("NIP-%s not found", strings.ToUpper(reqNum))
|
|
||||||
}
|
|
||||||
|
|
||||||
url := "https://github.com/nostr-protocol/nips/blob/master/" + foundLink
|
|
||||||
fmt.Println("Opening " + url)
|
|
||||||
|
|
||||||
var cmd *exec.Cmd
|
|
||||||
switch runtime.GOOS {
|
|
||||||
case "darwin":
|
|
||||||
cmd = exec.Command("open", url)
|
|
||||||
case "windows":
|
|
||||||
cmd = exec.Command("cmd", "/c", "start", url)
|
|
||||||
default:
|
|
||||||
cmd = exec.Command("xdg-open", url)
|
|
||||||
}
|
|
||||||
|
|
||||||
return cmd.Start()
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Action: func(ctx context.Context, c *cli.Command) error {
|
|
||||||
reqNum := c.Args().First()
|
|
||||||
if reqNum == "" {
|
|
||||||
// list all NIPs
|
|
||||||
for info := range listnips() {
|
|
||||||
stdout(info.nip + ": " + info.desc)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
normalize := func(s string) string {
|
|
||||||
s = strings.ToLower(s)
|
|
||||||
s = strings.TrimPrefix(s, "nip-")
|
|
||||||
s = strings.TrimLeft(s, "0")
|
|
||||||
if s == "" {
|
|
||||||
s = "0"
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
reqNum = normalize(reqNum)
|
|
||||||
|
|
||||||
var foundLink string
|
|
||||||
for info := range listnips() {
|
|
||||||
nipNum := normalize(info.nip)
|
|
||||||
|
|
||||||
if nipNum == reqNum {
|
|
||||||
foundLink = info.link
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 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
|
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
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}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return ch
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user