package cmd import ( "fmt" "strings" "github.com/spf13/cobra" ) var ( milestoneRepo string milestoneState string milestoneTitle string milestoneDue string milestoneDescription string ) func init() { msCmd := &cobra.Command{ Use: "milestone", Aliases: []string{"milestones"}, Short: "Work with milestones", } msCmd.PersistentFlags().StringVarP(&milestoneRepo, "repo", "R", "", "target repo as owner/repo (default: current git remote)") listCmd := &cobra.Command{ Use: "list", Short: "List milestones", Args: cobra.NoArgs, RunE: runMilestoneList, } listCmd.Flags().StringVar(&milestoneState, "state", "open", "open | closed | all") createCmd := &cobra.Command{ Use: "create", Short: "Create a milestone", Args: cobra.NoArgs, RunE: runMilestoneCreate, } createCmd.Flags().StringVarP(&milestoneTitle, "title", "t", "", "title (required)") createCmd.Flags().StringVarP(&milestoneDescription, "description", "d", "", "description") createCmd.Flags().StringVar(&milestoneDue, "due", "", "due date, YYYY-MM-DD") closeCmd := &cobra.Command{ Use: "close ", Short: "Close a milestone (by id; see `milestone list`)", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { return runMilestoneSetState(cmd, args, "closed") }, } reopenCmd := &cobra.Command{ Use: "reopen ", Short: "Reopen a closed milestone", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { return runMilestoneSetState(cmd, args, "open") }, } deleteCmd := &cobra.Command{ Use: "delete ", Short: "Delete a milestone (issues keep going, milestone cleared)", Args: cobra.ExactArgs(1), RunE: runMilestoneDelete, } msCmd.AddCommand(listCmd, createCmd, closeCmd, reopenCmd, deleteCmd) rootCmd.AddCommand(msCmd) } func runMilestoneList(cmd *cobra.Command, _ []string) error { client, err := newClient() if err != nil { return err } owner, repo, err := resolveRepo(milestoneRepo) if err != nil { return err } mses, err := client.ListMilestones(cmd.Context(), owner, repo, milestoneState) if err != nil { return err } if flagJSON { return printJSON(cmd.OutOrStdout(), mses) } if len(mses) == 0 { fmt.Fprintln(cmd.OutOrStdout(), "No milestones.") return nil } tw := newTabw(cmd.OutOrStdout()) fmt.Fprintln(tw, "ID\tSTATE\tDUE\tPROGRESS\tTITLE") for _, m := range mses { progress := fmt.Sprintf("%d/%d", m.ClosedCount, m.OpenCount+m.ClosedCount) fmt.Fprintf(tw, "%s\t%s\t%s\t%s\t%s\n", m.ID, m.State, dash(deref(m.DueOn)), progress, m.Title) } tw.Flush() return nil } func deref(s *string) string { if s == nil { return "" } return *s } func runMilestoneCreate(cmd *cobra.Command, _ []string) error { if strings.TrimSpace(milestoneTitle) == "" { return fmt.Errorf("--title is required") } client, err := newClient() if err != nil { return err } owner, repo, err := resolveRepo(milestoneRepo) if err != nil { return err } m, err := client.CreateMilestone(cmd.Context(), owner, repo, milestoneTitle, milestoneDescription, milestoneDue) if err != nil { return err } if flagJSON { return printJSON(cmd.OutOrStdout(), m) } fmt.Fprintf(cmd.OutOrStdout(), "Created milestone %s (%s)\n", m.Title, m.ID) return nil } func runMilestoneSetState(cmd *cobra.Command, args []string, state string) error { client, err := newClient() if err != nil { return err } owner, repo, err := resolveRepo(milestoneRepo) if err != nil { return err } m, err := client.UpdateMilestone(cmd.Context(), owner, repo, args[0], map[string]any{"state": state}) if err != nil { return err } if flagJSON { return printJSON(cmd.OutOrStdout(), m) } fmt.Fprintf(cmd.OutOrStdout(), "Milestone %s is now %s.\n", m.Title, m.State) return nil } func runMilestoneDelete(cmd *cobra.Command, args []string) error { client, err := newClient() if err != nil { return err } owner, repo, err := resolveRepo(milestoneRepo) if err != nil { return err } if err := client.DeleteMilestone(cmd.Context(), owner, repo, args[0]); err != nil { return err } fmt.Fprintf(cmd.OutOrStdout(), "Deleted milestone %s.\n", args[0]) return nil }