package cmd import ( "fmt" "os" "strconv" "strings" "text/tabwriter" "code.gitea.io/sdk/gitea" "github.com/spf13/cobra" "codeberg.org/romaintb/fgj/internal/api" "codeberg.org/romaintb/fgj/internal/config" ) var issueCmd = &cobra.Command{ Use: "issue", Short: "Manage issues", Long: "Create, view, list, and manage issues.", } var issueListCmd = &cobra.Command{ Use: "list [flags]", Short: "List issues", Long: "List issues in a repository.", RunE: runIssueList, } var issueViewCmd = &cobra.Command{ Use: "view ", Short: "View an issue", Long: "Display detailed information about an issue.", Args: cobra.ExactArgs(1), RunE: runIssueView, } var issueCreateCmd = &cobra.Command{ Use: "create", Short: "Create an issue", Long: "Create a new issue.", RunE: runIssueCreate, } var issueCommentCmd = &cobra.Command{ Use: "comment ", Short: "Add a comment to an issue", Long: "Add a comment to an existing issue.", Args: cobra.ExactArgs(1), RunE: runIssueComment, } var issueCloseCmd = &cobra.Command{ Use: "close ", Short: "Close an issue", Long: "Close an existing issue.", Args: cobra.ExactArgs(1), RunE: runIssueClose, } func init() { rootCmd.AddCommand(issueCmd) issueCmd.AddCommand(issueListCmd) issueCmd.AddCommand(issueViewCmd) issueCmd.AddCommand(issueCreateCmd) issueCmd.AddCommand(issueCommentCmd) issueCmd.AddCommand(issueCloseCmd) issueListCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format") issueListCmd.Flags().StringP("state", "s", "open", "Filter by state: open, closed, all") issueViewCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format") issueCreateCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format") issueCreateCmd.Flags().StringP("title", "t", "", "Title for the issue") issueCreateCmd.Flags().StringP("body", "b", "", "Body for the issue") issueCommentCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format") issueCommentCmd.Flags().StringP("body", "b", "", "Comment body") issueCloseCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format") } func runIssueList(cmd *cobra.Command, args []string) error { repo, _ := cmd.Flags().GetString("repo") state, _ := cmd.Flags().GetString("state") owner, name, err := parseRepo(repo) if err != nil { return err } cfg, err := config.Load() if err != nil { return err } client, err := api.NewClientFromConfig(cfg, "") if err != nil { return err } var stateType gitea.StateType switch strings.ToLower(state) { case "open": stateType = gitea.StateOpen case "closed": stateType = gitea.StateClosed case "all": stateType = gitea.StateAll default: return fmt.Errorf("invalid state: %s", state) } issues, _, err := client.ListRepoIssues(owner, name, gitea.ListIssueOption{ State: stateType, }) if err != nil { return fmt.Errorf("failed to list issues: %w", err) } if len(issues) == 0 { fmt.Printf("No %s issues in %s/%s\n", state, owner, name) return nil } w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) fmt.Fprintf(w, "NUMBER\tTITLE\tSTATE\n") for _, issue := range issues { if issue.PullRequest == nil { fmt.Fprintf(w, "#%d\t%s\t%s\n", issue.Index, issue.Title, issue.State) } } w.Flush() return nil } func runIssueView(cmd *cobra.Command, args []string) error { repo, _ := cmd.Flags().GetString("repo") issueNumber, err := strconv.ParseInt(args[0], 10, 64) if err != nil { return fmt.Errorf("invalid issue number: %w", err) } owner, name, err := parseRepo(repo) if err != nil { return err } cfg, err := config.Load() if err != nil { return err } client, err := api.NewClientFromConfig(cfg, "") if err != nil { return err } issue, _, err := client.GetIssue(owner, name, issueNumber) if err != nil { return fmt.Errorf("failed to get issue: %w", err) } fmt.Printf("Issue #%d\n", issue.Index) fmt.Printf("Title: %s\n", issue.Title) fmt.Printf("State: %s\n", issue.State) fmt.Printf("Author: %s\n", issue.Poster.UserName) fmt.Printf("Created: %s\n", issue.Created.Format("2006-01-02 15:04:05")) fmt.Printf("Updated: %s\n", issue.Updated.Format("2006-01-02 15:04:05")) if issue.Body != "" { fmt.Printf("\n%s\n", issue.Body) } comments, _, err := client.ListIssueComments(owner, name, issueNumber, gitea.ListIssueCommentOptions{}) if err == nil && len(comments) > 0 { fmt.Printf("\nComments (%d):\n", len(comments)) for _, comment := range comments { fmt.Printf("\n---\n%s (@%s) - %s\n%s\n", comment.Poster.FullName, comment.Poster.UserName, comment.Created.Format("2006-01-02 15:04:05"), comment.Body) } } return nil } func runIssueCreate(cmd *cobra.Command, args []string) error { repo, _ := cmd.Flags().GetString("repo") title, _ := cmd.Flags().GetString("title") body, _ := cmd.Flags().GetString("body") owner, name, err := parseRepo(repo) if err != nil { return err } if title == "" { return fmt.Errorf("title is required") } cfg, err := config.Load() if err != nil { return err } client, err := api.NewClientFromConfig(cfg, "") if err != nil { return err } issue, _, err := client.CreateIssue(owner, name, gitea.CreateIssueOption{ Title: title, Body: body, }) if err != nil { return fmt.Errorf("failed to create issue: %w", err) } fmt.Printf("Issue created: #%d\n", issue.Index) fmt.Printf("View at: %s\n", issue.HTMLURL) return nil } func runIssueComment(cmd *cobra.Command, args []string) error { repo, _ := cmd.Flags().GetString("repo") body, _ := cmd.Flags().GetString("body") issueNumber, err := strconv.ParseInt(args[0], 10, 64) if err != nil { return fmt.Errorf("invalid issue number: %w", err) } owner, name, err := parseRepo(repo) if err != nil { return err } if body == "" { return fmt.Errorf("comment body is required") } cfg, err := config.Load() if err != nil { return err } client, err := api.NewClientFromConfig(cfg, "") if err != nil { return err } comment, _, err := client.CreateIssueComment(owner, name, issueNumber, gitea.CreateIssueCommentOption{ Body: body, }) if err != nil { return fmt.Errorf("failed to create comment: %w", err) } fmt.Printf("Comment added to issue #%d\n", issueNumber) fmt.Printf("View at: %s\n", comment.HTMLURL) return nil } func runIssueClose(cmd *cobra.Command, args []string) error { repo, _ := cmd.Flags().GetString("repo") issueNumber, err := strconv.ParseInt(args[0], 10, 64) if err != nil { return fmt.Errorf("invalid issue number: %w", err) } owner, name, err := parseRepo(repo) if err != nil { return err } cfg, err := config.Load() if err != nil { return err } client, err := api.NewClientFromConfig(cfg, "") if err != nil { return err } stateClosed := gitea.StateClosed _, _, err = client.EditIssue(owner, name, issueNumber, gitea.EditIssueOption{ State: &stateClosed, }) if err != nil { return fmt.Errorf("failed to close issue: %w", err) } fmt.Printf("Issue #%d closed\n", issueNumber) return nil }