commands/project/view/project_view.go (216 lines of code) (raw):

package view import ( "encoding/base64" "encoding/json" "fmt" "net/url" "strings" "github.com/MakeNowJust/heredoc/v2" "github.com/spf13/cobra" gitlab "gitlab.com/gitlab-org/api/client-go" "gitlab.com/gitlab-org/cli/api" "gitlab.com/gitlab-org/cli/commands/cmdutils" "gitlab.com/gitlab-org/cli/internal/glrepo" "gitlab.com/gitlab-org/cli/pkg/iostreams" "gitlab.com/gitlab-org/cli/pkg/utils" ) type ViewOptions struct { ProjectID string APIClient *gitlab.Client Web bool OutputFormat string Branch string Browser string GlamourStyle string IO *iostreams.IOStreams Repo glrepo.Interface } func NewCmdView(f *cmdutils.Factory) *cobra.Command { opts := ViewOptions{ IO: f.IO, } projectViewCmd := &cobra.Command{ Use: "view [repository] [flags]", Short: "View a project or repository.", Long: heredoc.Doc(`Display the description and README of a project, or open it in the browser. `), Args: cobra.MaximumNArgs(1), Example: heredoc.Doc(` # View project information for the current directory. # Must be a Git repository. $ glab repo view # View project information of specified name. # glab repo view my-project $ glab repo view user/repo $ glab repo view group/namespace/repo # Specify repository by full [Git] URL. $ glab repo view git@gitlab.com:user/repo.git $ glab repo view https://gitlab.company.org/user/repo $ glab repo view https://gitlab.company.org/user/repo.git `), RunE: func(cmd *cobra.Command, args []string) error { var err error cfg, err := f.Config() if err != nil { return err } opts.Branch = strings.TrimSpace(opts.Branch) if len(args) == 1 { opts.ProjectID = args[0] } // No project argument - use current repository if opts.ProjectID == "" { opts.Repo, err = f.BaseRepo() if err != nil { return cmdutils.WrapError(err, "`repository` is required when not running in a Git repository.") } // Configure client to have host of current repository client, err := api.NewClientWithCfg(opts.Repo.RepoHost(), cfg, false) if err != nil { return err } opts.APIClient = client.Lab() if opts.Branch == "" { opts.Branch, _ = f.Branch() } } else { // If the ProjectID is a single token, use current user's namespace if !strings.Contains(opts.ProjectID, "/") { apiClient, err := f.HttpClient() if err != nil { return err } currentUser, err := api.CurrentUser(apiClient) if err != nil { return cmdutils.WrapError(err, "Failed to retrieve your current user.") } opts.ProjectID = currentUser.Username + "/" + opts.ProjectID } // Get the repo full name from the ProjectID which can be a full URL or a group/repo format opts.Repo, err = glrepo.FromFullName(opts.ProjectID) if err != nil { return err } client, err := api.NewClientWithCfg(opts.Repo.RepoHost(), cfg, false) if err != nil { return err } opts.APIClient = client.Lab() } browser, _ := cfg.Get(opts.Repo.RepoHost(), "browser") opts.Browser = browser opts.GlamourStyle, _ = cfg.Get(opts.Repo.RepoHost(), "glamour_style") return runViewProject(&opts) }, } projectViewCmd.Flags().BoolVarP(&opts.Web, "web", "w", false, "Open a project in the browser.") projectViewCmd.Flags().StringVarP(&opts.OutputFormat, "output", "F", "text", "Format output as: text, json.") projectViewCmd.Flags().StringVarP(&opts.Branch, "branch", "b", "", "View a specific branch of the repository.") return projectViewCmd } func runViewProject(opts *ViewOptions) error { project, err := opts.Repo.Project(opts.APIClient) if err != nil { return cmdutils.WrapError(err, "Failed to retrieve project information.") } if opts.Web { projectURL := project.WebURL if opts.IO.IsaTTY { fmt.Fprintf( opts.IO.StdOut, "Opening %s in your browser.\n", generateProjectOpenURL(utils.DisplayURL(projectURL), project.DefaultBranch, opts.Branch), ) } return utils.OpenInBrowser( generateProjectOpenURL(projectURL, project.DefaultBranch, opts.Branch), opts.Browser, ) } else if opts.OutputFormat == "json" { printProjectContentJSON(opts, project) } else { readmeFile, err := getReadmeFile(opts, project) if err != nil { return err } if opts.IO.IsaTTY { if err := opts.IO.StartPager(); err != nil { return err } defer opts.IO.StopPager() printProjectContentTTY(opts, project, readmeFile) } else { printProjectContentRaw(opts, project, readmeFile) } } return nil } func getReadmeFile(opts *ViewOptions, project *gitlab.Project) (*gitlab.File, error) { if project.ReadmeURL == "" { return nil, nil } readmePath := strings.Replace(project.ReadmeURL, project.WebURL+"/-/blob/", "", 1) readmePathComponents := strings.Split(readmePath, "/") readmeRef := readmePathComponents[0] readmeFileName := readmePathComponents[1] if opts.Branch == "" { opts.Branch = readmeRef } readmeFile, err := api.GetFile(opts.APIClient, project.PathWithNamespace, readmeFileName, opts.Branch) if err != nil { return nil, cmdutils.WrapError(err, fmt.Sprintf("Failed to retrieve README file on the %s branch.", opts.Branch)) } decoded, err := base64.StdEncoding.DecodeString(readmeFile.Content) if err != nil { return nil, cmdutils.WrapError(err, "Failed to decode README file.") } readmeFile.Content = string(decoded) return readmeFile, nil } func generateProjectOpenURL(projectWebURL string, defaultBranch string, branch string) string { if branch != "" && defaultBranch != branch { return projectWebURL + "/-/tree/" + url.PathEscape(branch) } return projectWebURL } func printProjectContentTTY(opts *ViewOptions, project *gitlab.Project, readme *gitlab.File) { var description string var readmeContent string var err error fullName := project.NameWithNamespace if project.Description != "" { description, err = utils.RenderMarkdownWithoutIndentations(project.Description, opts.GlamourStyle) if err != nil { description = project.Description } } else { description = "\n(No description provided)\n\n" } if readme != nil { readmeContent, err = utils.RenderMarkdown(readme.Content, opts.GlamourStyle) if err != nil { readmeContent = readme.Content } } c := opts.IO.Color() // Header fmt.Fprint(opts.IO.StdOut, c.Bold(fullName)) fmt.Fprint(opts.IO.StdOut, c.Gray(description)) // Readme if readme != nil { fmt.Fprint(opts.IO.StdOut, readmeContent) } else { fmt.Fprintln(opts.IO.StdOut, c.Gray("(This repository does not have a README file.)")) } fmt.Fprintln(opts.IO.StdOut) fmt.Fprintf(opts.IO.StdOut, c.Gray("View this project on GitLab: %s\n"), project.WebURL) } func printProjectContentRaw(opts *ViewOptions, project *gitlab.Project, readme *gitlab.File) { fullName := project.NameWithNamespace description := project.Description fmt.Fprintf(opts.IO.StdOut, "name:\t%s\n", fullName) fmt.Fprintf(opts.IO.StdOut, "description:\t%s\n", description) if readme != nil { fmt.Fprintln(opts.IO.StdOut, "---") fmt.Fprint(opts.IO.StdOut, readme.Content) fmt.Fprintln(opts.IO.StdOut) } } func printProjectContentJSON(opts *ViewOptions, project *gitlab.Project) { projectJSON, _ := json.Marshal(project) fmt.Fprintln(opts.IO.StdOut, string(projectJSON)) }