mirror of
https://gitea.com/gitea/tea.git
synced 2026-10-26 11:01:25 +01:00
services.IsNotFound replaces the hand-rolled 404 checks, convertPage the copied page conversions and lastPathNumber the comment's own number parser. The List wrappers around ListPage of releases, branches, wiki pages, secrets and variables go, pull request filters resolve milestones through Milestones.FindOne like issues, and localFlagsChanged, boolOrder, Cell.Render, stateBadge and dueText replace their copies. Signed-off-by: bircni <bircni@icloud.com>
323 lines
12 KiB
Go
323 lines
12 KiB
Go
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package app
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"math"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"gitea.dev/tea/internal/clierr"
|
|
"gitea.dev/tea/internal/services"
|
|
"gitea.dev/tea/internal/terminal"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
func (a *application) wikiCommand() *cobra.Command {
|
|
group := &cobra.Command{
|
|
Use: "wiki",
|
|
Short: "Manage repository wiki pages",
|
|
Long: "List, view, create, edit and delete the pages of a repository's wiki, and list their revisions.",
|
|
}
|
|
group.AddCommand(a.wikiListCommand(), a.wikiViewCommand(), a.wikiRevisionsCommand(), a.wikiCreateCommand(), a.wikiEditCommand(), a.wikiDeleteCommand())
|
|
return group
|
|
}
|
|
|
|
// completeWikiPages completes the titles of the repository's wiki pages.
|
|
func (a *application) completeWikiPages() cobra.CompletionFunc {
|
|
return a.apiCompletion(true, func(ctx context.Context, t target, _ string) ([]cobra.Completion, error) {
|
|
pages, err := services.Wiki{Client: t.client, Repository: t.Repo}.ListPage(ctx, completionLimit, 1)
|
|
return describe(pages.Items, func(page services.WikiPage) (string, string) { return page.Title, "" }), err
|
|
})
|
|
}
|
|
|
|
func (a *application) wikiListCommand() *cobra.Command {
|
|
var limit, page int
|
|
command := &cobra.Command{
|
|
Use: "list",
|
|
Short: "List wiki pages",
|
|
Example: "$ tea wiki list\n$ tea wiki list -R alice/project --json title,url",
|
|
Args: argsExact(0),
|
|
RunE: func(cmd *cobra.Command, _ []string) error {
|
|
target, err := a.resolve(cmd.Context(), true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
result, err := (services.Wiki{Client: target.client, Repository: target.Repo}).ListPage(cmd.Context(), limit, page)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
spec := listSpec[services.WikiPage]{
|
|
Columns: []terminal.Column{{Header: "Title", Flex: true}, {Header: "Author"}, {Header: "Updated"}, {Header: "URL", PipedOnly: true}},
|
|
Row: func(item services.WikiPage) []terminal.Cell {
|
|
return []terminal.Cell{{Text: item.Title, Link: item.URL}, {Text: item.Author}, {Time: terminal.ParseTime(item.UpdatedAt)}, {Text: item.URL}}
|
|
},
|
|
Context: "wiki pages in " + target.Repo, Empty: "no wiki pages in " + target.Repo,
|
|
}
|
|
return writeTable(a, cmd, result.Items, spec, servicePage(result, page))
|
|
},
|
|
}
|
|
pageFlags(command, &limit, &page)
|
|
return command
|
|
}
|
|
|
|
func (a *application) wikiViewCommand() *cobra.Command {
|
|
var web bool
|
|
command := &cobra.Command{
|
|
Use: "view <page>",
|
|
Short: "View a wiki page",
|
|
Long: "View a wiki page, rendered on a terminal.",
|
|
Example: "$ tea wiki view Home\n$ tea wiki view \"Getting Started\" --web",
|
|
Args: argsExact(1),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
target, err := a.resolve(cmd.Context(), true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
item, err := (services.Wiki{Client: target.client, Repository: target.Repo}).View(cmd.Context(), args[0])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if web {
|
|
return a.openBrowser(cmd, item.URL)
|
|
}
|
|
return writeView(a, cmd, item, a.wikiView)
|
|
},
|
|
}
|
|
command.Flags().BoolVarP(&web, "web", "w", false, "Open the page in the browser")
|
|
command.ValidArgsFunction = a.completeWikiPages()
|
|
return command
|
|
}
|
|
|
|
// wikiView shows a wiki page with its content as the body.
|
|
func (a *application) wikiView(item services.WikiPage) terminal.View {
|
|
var meta []string
|
|
if item.Author != "" {
|
|
meta = append(meta, item.Author)
|
|
}
|
|
if updated := terminal.ParseTime(item.UpdatedAt); !updated.IsZero() {
|
|
meta = append(meta, "updated "+terminal.RelativeTime(a.deps.Now(), updated))
|
|
}
|
|
if item.CommitCount > 0 {
|
|
meta = append(meta, terminal.Plural(int(item.CommitCount), "revision"))
|
|
}
|
|
return terminal.View{
|
|
Title: item.Title, Meta: strings.Join(meta, " · "), Body: item.Body, Kind: "wiki page", URL: item.URL,
|
|
Fields: []terminal.Field{{Label: "Author", Value: item.Author, PipedOnly: true}, {Label: "Updated", Value: item.UpdatedAt, PipedOnly: true}},
|
|
}
|
|
}
|
|
|
|
func (a *application) wikiRevisionsCommand() *cobra.Command {
|
|
var page int
|
|
command := &cobra.Command{
|
|
Use: "revisions <page>",
|
|
Short: "List revisions of a wiki page",
|
|
Example: "$ tea wiki revisions Home",
|
|
Args: argsExact(1),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
target, err := a.resolve(cmd.Context(), true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
items, err := (services.Wiki{Client: target.client, Repository: target.Repo}).Revisions(cmd.Context(), args[0], page)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
spec := listSpec[services.WikiRevision]{
|
|
Columns: []terminal.Column{{Header: "Commit"}, {Header: "Author"}, {Header: "Date"}, {Header: "Message", Flex: true}},
|
|
Row: func(item services.WikiRevision) []terminal.Cell {
|
|
message, _, _ := strings.Cut(item.Message, "\n")
|
|
sha := item.SHA
|
|
if a.deps.Streams.OutTTY {
|
|
sha = shortSHA(sha)
|
|
}
|
|
return []terminal.Cell{{Text: sha, Muted: true}, {Text: item.Author}, {Time: terminal.ParseTime(item.CreatedAt)}, {Text: message}}
|
|
},
|
|
Context: "revisions of wiki page " + strconv.Quote(args[0]), Empty: "no revisions of wiki page " + strconv.Quote(args[0]),
|
|
}
|
|
return writeTable(a, cmd, items, spec, listPage{Number: page, Counted: false, More: false})
|
|
},
|
|
}
|
|
positiveIntVar(command, &page, "page", 1, math.MaxInt, "Revision page (server controls page size)")
|
|
command.ValidArgsFunction = a.completeWikiPages()
|
|
return command
|
|
}
|
|
|
|
// wikiContent are the flags of wiki create and wiki edit.
|
|
type wikiContent struct {
|
|
title, body, file, message string
|
|
editor bool
|
|
}
|
|
|
|
func (c *wikiContent) register(cmd *cobra.Command) {
|
|
flags := cmd.Flags()
|
|
flags.StringVarP(&c.title, "title", "t", "", "Page title")
|
|
flags.BoolVarP(&c.editor, "editor", "e", false, "Write the page content in an editor")
|
|
flags.StringVarP(&c.body, "body", "b", "", "Page content")
|
|
flags.StringVarP(&c.file, "body-file", "F", "", "Read the page content from a file, or - for standard input")
|
|
flags.StringVarP(&c.message, "message", "m", "", "Commit message of the change")
|
|
}
|
|
|
|
func (a *application) wikiCreateCommand() *cobra.Command {
|
|
var content wikiContent
|
|
command := &cobra.Command{
|
|
Use: "create [<title>]",
|
|
Short: "Create a wiki page",
|
|
Long: "Create a wiki page. Without --body or --body-file, the content is written in an editor or typed on the terminal.",
|
|
Example: `$ tea wiki create "Getting Started" --body-file docs/start.md
|
|
$ tea wiki create Home --editor`,
|
|
Args: usageArgs(cobra.MaximumNArgs(1)),
|
|
RunE: func(cmd *cobra.Command, args []string) (runErr error) {
|
|
var draft terminal.Draft
|
|
defer finishDraft(&draft, &runErr)
|
|
if len(args) > 0 {
|
|
if cmd.Flags().Changed("title") {
|
|
return clierr.Wrap(clierr.Usage, errors.New("supply the title as an argument or --title, not both"))
|
|
}
|
|
content.title = args[0]
|
|
}
|
|
if err := validateBodySources(cmd); err != nil {
|
|
return err
|
|
}
|
|
if content.file == "-" && content.title == "" {
|
|
return clierr.Wrap(clierr.Usage, errors.New("--body-file - requires a title; stdin cannot also supply a prompt"))
|
|
}
|
|
body, err := a.readBody(cmd, content.body, content.file)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if _, err := a.requireInput(cmd, &content.title, "title", "Page title", nil); err != nil {
|
|
return err
|
|
}
|
|
if strings.TrimSpace(content.title) == "" {
|
|
return clierr.Wrap(clierr.Usage, errors.New("wiki title cannot be empty"))
|
|
}
|
|
target, err := a.resolve(cmd.Context(), true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !cmd.Flags().Changed("body") && !cmd.Flags().Changed("body-file") {
|
|
if draft, err = a.wikiBody(cmd, content.editor, ""); err != nil {
|
|
return err
|
|
}
|
|
body = draft.Body
|
|
}
|
|
if strings.TrimSpace(body) == "" {
|
|
return clierr.Wrap(clierr.Usage, errors.New("wiki content cannot be empty"))
|
|
}
|
|
item, err := (services.Wiki{Client: target.client, Repository: target.Repo}).Create(cmd.Context(), services.WikiOptions{Title: content.title, Body: body, Message: content.message})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
a.done(cmd, "Created wiki page %q in %s", item.Title, target.Repo)
|
|
return a.created(cmd, item, item.URL)
|
|
},
|
|
}
|
|
content.register(command)
|
|
return command
|
|
}
|
|
|
|
// wikiBody collects page content: in the editor with --editor, else in the editor or an inline
|
|
// field on a terminal. Without a terminal it fails, naming --body.
|
|
func (a *application) wikiBody(cmd *cobra.Command, editor bool, initial string) (terminal.Draft, error) {
|
|
if editor {
|
|
return a.editDraft(cmd, initial)
|
|
}
|
|
if !a.canPrompt() {
|
|
return terminal.Draft{}, clierr.Wrap(clierr.Usage, errors.New("missing required input: supply --body, --body-file or --editor"))
|
|
}
|
|
return a.bodyEditor(cmd)(cmd.Context(), initial)
|
|
}
|
|
|
|
func (a *application) wikiEditCommand() *cobra.Command {
|
|
var content wikiContent
|
|
command := &cobra.Command{
|
|
Use: "edit <page>",
|
|
Short: "Edit a wiki page",
|
|
Long: "Change the title or the content of a wiki page. Renaming with --title alone keeps the content.",
|
|
Example: `$ tea wiki edit Home --body-file docs/home.md --message "Rewrite the home page"
|
|
$ tea wiki edit "Old Title" --title "New Title"
|
|
$ tea wiki edit Home --editor`,
|
|
Args: argsExact(1),
|
|
RunE: func(cmd *cobra.Command, args []string) (runErr error) {
|
|
var draft terminal.Draft
|
|
defer finishDraft(&draft, &runErr)
|
|
if err := validateBodySources(cmd); err != nil {
|
|
return err
|
|
}
|
|
changed := cmd.Flags().Changed
|
|
if !changed("title") && !changed("body") && !changed("body-file") && !content.editor {
|
|
return clierr.Wrap(clierr.Usage, errors.New("supply --title, --body, --body-file or --editor"))
|
|
}
|
|
if changed("title") && strings.TrimSpace(content.title) == "" {
|
|
return clierr.Wrap(clierr.Usage, errors.New("wiki title cannot be empty"))
|
|
}
|
|
body, err := a.readBody(cmd, content.body, content.file)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if (changed("body") || changed("body-file")) && strings.TrimSpace(body) == "" {
|
|
return clierr.Wrap(clierr.Usage, errors.New("wiki content cannot be empty"))
|
|
}
|
|
target, err := a.resolve(cmd.Context(), true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
service := services.Wiki{Client: target.client, Repository: target.Repo}
|
|
if content.editor || (!changed("body") && !changed("body-file")) {
|
|
current, err := service.View(cmd.Context(), args[0])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
body = current.Body
|
|
if content.editor {
|
|
if draft, err = a.editDraft(cmd, current.Body); err != nil {
|
|
return err
|
|
}
|
|
if body = draft.Body; strings.TrimSpace(body) == "" {
|
|
return clierr.Wrap(clierr.Usage, errors.New("wiki content cannot be empty"))
|
|
}
|
|
}
|
|
}
|
|
item, err := service.Edit(cmd.Context(), args[0], services.WikiOptions{Title: content.title, Body: body, Message: content.message})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if item.Title != args[0] {
|
|
a.done(cmd, "Renamed wiki page %q to %q in %s", args[0], item.Title, target.Repo)
|
|
} else {
|
|
a.done(cmd, "Edited wiki page %q in %s", item.Title, target.Repo)
|
|
}
|
|
return a.jsonResult(cmd, item)
|
|
},
|
|
}
|
|
content.register(command)
|
|
command.ValidArgsFunction = a.completeWikiPages()
|
|
return command
|
|
}
|
|
|
|
func (a *application) wikiDeleteCommand() *cobra.Command {
|
|
command := &cobra.Command{
|
|
Use: "delete <page>",
|
|
Short: "Delete a wiki page",
|
|
Example: "$ tea wiki delete \"Old Notes\" --yes",
|
|
Annotations: map[string]string{"tea.confirm": "true", "tea.text": "wiki page deletion does not return a resource"},
|
|
Args: argsExact(1),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
target, err := a.resolve(cmd.Context(), true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
subject := "wiki page " + strconv.Quote(args[0]) + " from " + target.Repo
|
|
return a.deleteConfirmed(cmd, subject, func() error {
|
|
return (services.Wiki{Client: target.client, Repository: target.Repo}).Delete(cmd.Context(), args[0])
|
|
})
|
|
},
|
|
}
|
|
command.ValidArgsFunction = a.completeWikiPages()
|
|
return command
|
|
}
|