147 lines
4.1 KiB
Go
147 lines
4.1 KiB
Go
|
|
package cmd
|
||
|
|
|
||
|
|
import (
|
||
|
|
"fmt"
|
||
|
|
|
||
|
|
"code.gitea.io/sdk/gitea"
|
||
|
|
"github.com/spf13/cobra"
|
||
|
|
)
|
||
|
|
|
||
|
|
var repoSearchCmd = &cobra.Command{
|
||
|
|
Use: "search [query]",
|
||
|
|
Aliases: []string{"s"},
|
||
|
|
Short: "Search repositories on the current host",
|
||
|
|
Long: `Search repositories using the host's search index.
|
||
|
|
|
||
|
|
The query is matched against name by default. Pass --topic to match against
|
||
|
|
topics only, or --description to include descriptions. --type limits results
|
||
|
|
to "source" (non-fork, non-mirror), "fork", or "mirror" repositories.`,
|
||
|
|
Example: ` # Search by name substring
|
||
|
|
fgj repo search tea
|
||
|
|
|
||
|
|
# Search by topic
|
||
|
|
fgj repo search ci --topic
|
||
|
|
|
||
|
|
# Find only forks
|
||
|
|
fgj repo search go --type fork
|
||
|
|
|
||
|
|
# List private repos owned by a user (no query)
|
||
|
|
fgj repo search --owner alice --private --limit 50
|
||
|
|
|
||
|
|
# Output as JSON
|
||
|
|
fgj repo search platform --json`,
|
||
|
|
Args: cobra.MaximumNArgs(1),
|
||
|
|
RunE: runRepoSearch,
|
||
|
|
}
|
||
|
|
|
||
|
|
func init() {
|
||
|
|
repoCmd.AddCommand(repoSearchCmd)
|
||
|
|
|
||
|
|
repoSearchCmd.Flags().Bool("topic", false, "Match query against topics only")
|
||
|
|
repoSearchCmd.Flags().Bool("description", false, "Include descriptions in the search")
|
||
|
|
repoSearchCmd.Flags().Bool("private", false, "Limit to private repositories")
|
||
|
|
repoSearchCmd.Flags().Bool("archived", false, "Include archived repositories")
|
||
|
|
repoSearchCmd.Flags().Bool("exclude-templates", false, "Exclude template repositories")
|
||
|
|
repoSearchCmd.Flags().String("type", "", "Filter by repo type: source, fork, mirror")
|
||
|
|
repoSearchCmd.Flags().String("owner", "", "Limit to repos owned by this user or org")
|
||
|
|
repoSearchCmd.Flags().String("sort", "", "Sort by: alpha, created, updated, size, id")
|
||
|
|
repoSearchCmd.Flags().String("order", "", "Order: asc or desc")
|
||
|
|
repoSearchCmd.Flags().IntP("limit", "L", 30, "Maximum number of results")
|
||
|
|
|
||
|
|
addJSONFlags(repoSearchCmd, "Output as JSON")
|
||
|
|
}
|
||
|
|
|
||
|
|
func runRepoSearch(cmd *cobra.Command, args []string) error {
|
||
|
|
client, err := loadClient()
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
query := ""
|
||
|
|
if len(args) == 1 {
|
||
|
|
query = args[0]
|
||
|
|
}
|
||
|
|
|
||
|
|
topic, _ := cmd.Flags().GetBool("topic")
|
||
|
|
desc, _ := cmd.Flags().GetBool("description")
|
||
|
|
private, _ := cmd.Flags().GetBool("private")
|
||
|
|
archived, _ := cmd.Flags().GetBool("archived")
|
||
|
|
excludeTemplates, _ := cmd.Flags().GetBool("exclude-templates")
|
||
|
|
sort, _ := cmd.Flags().GetString("sort")
|
||
|
|
order, _ := cmd.Flags().GetString("order")
|
||
|
|
typeFlag, _ := cmd.Flags().GetString("type")
|
||
|
|
limit, _ := cmd.Flags().GetInt("limit")
|
||
|
|
if limit <= 0 {
|
||
|
|
limit = 30
|
||
|
|
}
|
||
|
|
|
||
|
|
var repoType gitea.RepoType
|
||
|
|
switch typeFlag {
|
||
|
|
case "":
|
||
|
|
repoType = gitea.RepoTypeNone
|
||
|
|
case "source":
|
||
|
|
repoType = gitea.RepoTypeSource
|
||
|
|
case "fork":
|
||
|
|
repoType = gitea.RepoTypeFork
|
||
|
|
case "mirror":
|
||
|
|
repoType = gitea.RepoTypeMirror
|
||
|
|
default:
|
||
|
|
return fmt.Errorf("invalid --type %q (must be source, fork, or mirror)", typeFlag)
|
||
|
|
}
|
||
|
|
|
||
|
|
opt := gitea.SearchRepoOptions{
|
||
|
|
ListOptions: gitea.ListOptions{PageSize: limit},
|
||
|
|
Keyword: query,
|
||
|
|
KeywordIsTopic: topic,
|
||
|
|
KeywordInDescription: desc,
|
||
|
|
IsPrivate: optionalBool(private),
|
||
|
|
IsArchived: optionalBool(archived),
|
||
|
|
ExcludeTemplate: excludeTemplates,
|
||
|
|
Type: repoType,
|
||
|
|
Sort: sort,
|
||
|
|
Order: order,
|
||
|
|
}
|
||
|
|
|
||
|
|
if o, _ := cmd.Flags().GetString("owner"); o != "" {
|
||
|
|
u, _, err := client.GetUserInfo(o)
|
||
|
|
if err != nil {
|
||
|
|
return fmt.Errorf("failed to resolve owner %q: %w", o, err)
|
||
|
|
}
|
||
|
|
opt.OwnerID = u.ID
|
||
|
|
}
|
||
|
|
|
||
|
|
repos, _, err := client.SearchRepos(opt)
|
||
|
|
if err != nil {
|
||
|
|
return fmt.Errorf("search failed: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
if wantJSON(cmd) {
|
||
|
|
return outputJSON(cmd, repos)
|
||
|
|
}
|
||
|
|
|
||
|
|
if len(repos) == 0 {
|
||
|
|
fmt.Fprintln(ios.Out, "No repositories match.")
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
tp := ios.NewTablePrinter()
|
||
|
|
tp.AddHeader("FULL NAME", "VISIBILITY", "DESCRIPTION", "STARS")
|
||
|
|
for _, r := range repos {
|
||
|
|
visibility := "public"
|
||
|
|
if r.Private {
|
||
|
|
visibility = "private"
|
||
|
|
}
|
||
|
|
tp.AddRow(r.FullName, visibility, r.Description, fmt.Sprintf("%d", r.Stars))
|
||
|
|
}
|
||
|
|
return tp.Render()
|
||
|
|
}
|
||
|
|
|
||
|
|
// optionalBool returns a pointer when the user explicitly wants the positive
|
||
|
|
// filter (IsPrivate/IsArchived); nil means "no filter" to the SDK.
|
||
|
|
func optionalBool(v bool) *bool {
|
||
|
|
if !v {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
return &v
|
||
|
|
}
|