refactor(ninja): extract popular-tags stubs into named helpers

This commit is contained in:
Alex Dunmow 2026-06-07 15:38:24 +08:00
parent 5d368da839
commit bb3ddfe1bd
2 changed files with 48 additions and 50 deletions

View File

@ -208,14 +208,14 @@ func parsePrivateCoord(s string) (string, error) {
return s, nil
}
rest := strings.TrimPrefix(s, "@")
slash := strings.IndexByte(rest, '/')
if slash < 0 {
before, after, ok := strings.Cut(rest, "/")
if !ok {
return "", fmt.Errorf("expected @%s/<name>, got %q", core.PrivateScopeSlug, s)
}
if rest[:slash] != core.PrivateScopeSlug {
return "", fmt.Errorf("only @%s scope is supported for delete; got @%s", core.PrivateScopeSlug, rest[:slash])
if before != core.PrivateScopeSlug {
return "", fmt.Errorf("only @%s scope is supported for delete; got @%s", core.PrivateScopeSlug, before)
}
return rest[slash+1:], nil
return after, nil
}
func printPrivateSection(ctx context.Context, cli *orchclient.Client, hc creds.HostCreds) {
@ -605,26 +605,28 @@ func promptCategoriesWithDefault(ctx context.Context, cli *orchclient.Client, sc
return out, nil
}
// fetchPopularTagsForPrompt returns a comma-joined "tag (count)" string of the
// top tags for the given kind, or "" if the orchestrator lookup fails. The
// current implementation always returns "" because core's copy of the
// orchestrator proto bindings does not yet expose ListTags; once the bindings
// are regenerated this body becomes a best-effort PluginRegistryService.ListTags
// call. Callers must already tolerate an empty return.
func fetchPopularTagsForPrompt(ctx context.Context, cli *orchclient.Client, kind string) string {
_ = ctx
_ = cli
_ = kind
return ""
}
// promptTagsWithDefault prompts the user for free-form tags. Best-effort fetches
// the top-20 most-used tags via ListTags(kind) to surface popular suggestions;
// if that call fails (offline, etc.) it falls back to a plain prompt with a
// one-line warning. Empty input keeps `current`. Validates with NormalizeTags;
// on error, prints the issue and reprompts (one re-try, then bails).
func promptTagsWithDefault(ctx context.Context, cli *orchclient.Client, scanner *bufio.Scanner, kind string, current []string) ([]string, error) {
// Best-effort popular-tags lookup.
var popular string
// TODO(tags): re-enable once core regenerates proto bindings to expose v1.ListTagsRequest (plan Task 12).
// if resp, err := cli.Reg.ListTags(ctx, connect.NewRequest(&v1.ListTagsRequest{Kind: kind, Limit: 20})); err == nil {
// parts := make([]string, 0, len(resp.Msg.Tags))
// for _, t := range resp.Msg.Tags {
// parts = append(parts, fmt.Sprintf("%s (%d)", t.Tag, t.Count))
// }
// popular = strings.Join(parts, ", ")
// }
_ = ctx
_ = cli
popular := fetchPopularTagsForPrompt(ctx, cli, kind)
for attempt := 0; attempt < 2; attempt++ {
for attempt := range 2 {
if len(current) > 0 {
fmt.Printf("Tags (current: %s)\n", strings.Join(current, ", "))
} else {
@ -1021,7 +1023,7 @@ func submodulePaths(repoDir string) []string {
return nil
}
var paths []string
for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") {
for line := range strings.SplitSeq(strings.TrimSpace(string(out)), "\n") {
// each line is "submodule.<name>.path <path>"
fields := strings.Fields(line)
if len(fields) >= 2 {
@ -1055,43 +1057,43 @@ func upsertPluginMod(scope, name, displayName, description, kind string, categor
func writeMod(path string, m *core.ModFile) error {
var b strings.Builder
b.WriteString("[plugin]\n")
b.WriteString(fmt.Sprintf("name = %q\n", m.Plugin.Name))
fmt.Fprintf(&b, "name = %q\n", m.Plugin.Name)
if m.Plugin.DisplayName != "" {
b.WriteString(fmt.Sprintf("display_name = %q\n", m.Plugin.DisplayName))
fmt.Fprintf(&b, "display_name = %q\n", m.Plugin.DisplayName)
}
b.WriteString(fmt.Sprintf("scope = %q\n", m.Plugin.Scope))
b.WriteString(fmt.Sprintf("version = %q\n", m.Plugin.Version))
fmt.Fprintf(&b, "scope = %q\n", m.Plugin.Scope)
fmt.Fprintf(&b, "version = %q\n", m.Plugin.Version)
if m.Plugin.Description != "" {
b.WriteString(fmt.Sprintf("description = %q\n", m.Plugin.Description))
fmt.Fprintf(&b, "description = %q\n", m.Plugin.Description)
}
if m.Plugin.Kind != "" {
b.WriteString(fmt.Sprintf("kind = %q\n", m.Plugin.Kind))
fmt.Fprintf(&b, "kind = %q\n", m.Plugin.Kind)
}
if len(m.Plugin.Categories) > 0 {
quoted := make([]string, len(m.Plugin.Categories))
for i, c := range m.Plugin.Categories {
quoted[i] = fmt.Sprintf("%q", c)
}
b.WriteString(fmt.Sprintf("categories = [%s]\n", strings.Join(quoted, ", ")))
fmt.Fprintf(&b, "categories = [%s]\n", strings.Join(quoted, ", "))
}
if len(m.Plugin.Tags) > 0 {
quoted := make([]string, len(m.Plugin.Tags))
for i, t := range m.Plugin.Tags {
quoted[i] = fmt.Sprintf("%q", t)
}
b.WriteString(fmt.Sprintf("tags = [%s]\n", strings.Join(quoted, ", ")))
fmt.Fprintf(&b, "tags = [%s]\n", strings.Join(quoted, ", "))
}
if m.Plugin.Private {
b.WriteString("private = true\n")
}
if m.Compatibility != nil {
b.WriteString("\n[compatibility]\n")
b.WriteString(fmt.Sprintf("block_core = %q\n", m.Compatibility.BlockCore))
fmt.Fprintf(&b, "block_core = %q\n", m.Compatibility.BlockCore)
}
for _, r := range m.Requires {
b.WriteString("\n[[requires]]\n")
b.WriteString(fmt.Sprintf("name = %q\n", r.Name))
b.WriteString(fmt.Sprintf("version = %q\n", r.Version))
fmt.Fprintf(&b, "name = %q\n", r.Name)
fmt.Fprintf(&b, "version = %q\n", r.Version)
}
return os.WriteFile(path, []byte(b.String()), 0o644)
}
@ -1108,7 +1110,7 @@ func gitignoredTrackedWarning(repoDir string, w io.Writer) {
return
}
fmt.Fprintln(w, "warning: these tracked files match .gitignore and will still be shipped:")
for _, n := range strings.Split(names, "\n") {
for n := range strings.SplitSeq(names, "\n") {
fmt.Fprintln(w, " "+n)
}
fmt.Fprintln(w, " (run `git rm --cached <file>` to drop)")
@ -1127,7 +1129,7 @@ func untrackedFilesWarning(repoDir string, w io.Writer) {
return
}
fmt.Fprintln(w, "warning: these untracked files will NOT be in the archive:")
for _, n := range strings.Split(names, "\n") {
for n := range strings.SplitSeq(names, "\n") {
fmt.Fprintln(w, " "+n)
}
fmt.Fprintln(w, " (run `git add <file>` if they should be shipped)")

View File

@ -39,24 +39,8 @@ func newPluginTagsCmd() *cobra.Command {
return nil
}
cli := orchclient.New(resolvedHost, hc.Token)
_ = cli
// TODO(tags): re-enable once core regenerates proto bindings to expose v1.ListTagsRequest (plan Task 12).
// resp, err := cli.Reg.ListTags(context.Background(), connect.NewRequest(&v1.ListTagsRequest{Kind: mod.Plugin.Kind, Limit: 20}))
// if err != nil {
// fmt.Println("(could not fetch popular tags — orchestrator unreachable)")
// return nil
// }
// if len(resp.Msg.Tags) == 0 {
// fmt.Println("Popular tags: (none yet)")
// return nil
// }
// parts := make([]string, len(resp.Msg.Tags))
// for i, t := range resp.Msg.Tags {
// parts[i] = fmt.Sprintf("%s (%d)", t.Tag, t.Count)
// }
// fmt.Printf("Popular: %s\n", strings.Join(parts, ", "))
fmt.Println("(popular tags unavailable — orchestrator bindings not yet regenerated)")
line := fetchPopularTagsForList(cli, mod.Plugin.Kind)
fmt.Println(line)
return nil
},
}
@ -167,3 +151,15 @@ func writeLocalModTags(mod *core.ModFile, tags []string) error {
)
}
// fetchPopularTagsForList returns a single user-facing line listing the most-used
// tags for the given kind. The current body returns an unavailable-notice
// because core's copy of the orchestrator proto bindings does not yet expose
// ListTags; once bindings are regenerated this body becomes a real
// PluginRegistryService.ListTags call rendering "Popular: tag (count), ..." or
// "Popular tags: (none yet)".
func fetchPopularTagsForList(cli *orchclient.Client, kind string) string {
_ = cli
_ = kind
return "(popular tags unavailable — orchestrator bindings not yet regenerated)"
}