Skip to content

Commit

Permalink
feat(snippet): Add command for creating snippets
Browse files Browse the repository at this point in the history
This adds a simple snippet creation command. This currently just allows
for creating snippets from STDIN like so:

`echo "this is a script" | glab snippet create --title "snippet" --filename "example.py"`

There are few things that are missing from this, like ability to add
more then one file to the snippet, but that is not supported by the "github.com/xanzy/go-gitlab" yet.

Issue is opened for that xanzy/go-gitlab#1372

Issue profclems#872
  • Loading branch information
zemzale committed Feb 10, 2022
1 parent 6e92e27 commit 73e5888
Show file tree
Hide file tree
Showing 4 changed files with 193 additions and 0 deletions.
2 changes: 2 additions & 0 deletions commands/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
mrCmd "github.com/profclems/glab/commands/mr"
projectCmd "github.com/profclems/glab/commands/project"
releaseCmd "github.com/profclems/glab/commands/release"
snippetCmd "github.com/profclems/glab/commands/snippet"
sshCmd "github.com/profclems/glab/commands/ssh-key"
updateCmd "github.com/profclems/glab/commands/update"
userCmd "github.com/profclems/glab/commands/user"
Expand Down Expand Up @@ -113,6 +114,7 @@ func NewCmdRoot(f *cmdutils.Factory, version, buildDate string) *cobra.Command {
rootCmd.AddCommand(userCmd.NewCmdUser(f))
rootCmd.AddCommand(variableCmd.NewVariableCmd(f))
rootCmd.AddCommand(apiCmd.NewCmdApi(f, nil))
rootCmd.AddCommand(snippetCmd.NewCmdSnippet(f))

rootCmd.Flags().BoolP("version", "v", false, "show glab version information")
return rootCmd
Expand Down
120 changes: 120 additions & 0 deletions commands/snippet/create/create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package create

import (
"errors"
"fmt"
"io"

"github.com/MakeNowJust/heredoc"
"github.com/profclems/glab/api"
"github.com/profclems/glab/commands/cmdutils"
"github.com/profclems/glab/internal/glrepo"
"github.com/profclems/glab/pkg/iostreams"
"github.com/spf13/cobra"
"github.com/xanzy/go-gitlab"
)

type CreateOpts struct {
Title string
Description string
Filename string
Visibility string

ForUser bool

IO *iostreams.IOStreams
Lab func() (*gitlab.Client, error)
BaseRepo func() (glrepo.Interface, error)
}

func NewCmdCreate(f *cmdutils.Factory) *cobra.Command {
opts := &CreateOpts{}
var snippetCreateCmd = &cobra.Command{
Use: "create",
Short: `Create new snippet`,
Long: ``,
Aliases: []string{"new"},
Example: heredoc.Doc(`
$ glab snippet new --title "Title of the snippet" --filename "main.go"
$ glab snippet create --title "Title of the snippet" --filename "main.go"
$ glab snippet create -t Title -f "main.go" -d Description
`),
Args: cobra.ExactArgs(0),
PreRunE: func(cmd *cobra.Command, args []string) error {
opts.IO = f.IO
opts.BaseRepo = f.BaseRepo
opts.Lab = f.HttpClient
if opts.Title == "" {
return &cmdutils.FlagError{
Err: errors.New("--title required for snipets"),
}
}
if opts.Filename == "" {
return &cmdutils.FlagError{
Err: errors.New("--filename required for snipets"),
}
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
client, err := opts.Lab()
if err != nil {
return err
}
repo, err := opts.BaseRepo()
if err != nil {
return err
}

return runCreate(client, repo, opts)
},
}

snippetCreateCmd.Flags().StringVarP(&opts.Title, "title", "t", "", "Title of the snippet")
snippetCreateCmd.Flags().StringVarP(&opts.Filename, "filename", "f", "", "Filename of the snippet")
snippetCreateCmd.Flags().StringVarP(&opts.Description, "description", "d", "", "Description of the snippet")
snippetCreateCmd.Flags().StringVarP(&opts.Visibility, "visibility", "v", "private", "Limit by visibility {public, internal, or private}")

return snippetCreateCmd
}

func runCreate(client *gitlab.Client, repo glrepo.Interface, opts *CreateOpts) error {
content, err := readSnippetsContent(opts)
if err != nil {
return err
}
fmt.Fprintln(opts.IO.StdErr, "- Creating snippet in", repo.FullName())
snippet, err := api.CreateProjectSnippet(client, repo.FullName(), &gitlab.CreateProjectSnippetOptions{
Title: &opts.Title,
Description: &opts.Description,
Content: gitlab.String(string(content)),
FileName: &opts.Filename,
Visibility: gitlab.Visibility(gitlab.VisibilityValue(opts.Visibility)),
})
if err != nil {
return fmt.Errorf("failed to create snippet. %w", err)
}
snippetID := opts.IO.Color().Green(fmt.Sprintf("$%d", snippet.ID))
if opts.IO.IsaTTY {
fmt.Fprintf(opts.IO.StdOut, "%s %s (%s)\n %s\n", snippetID, snippet.Title, snippet.FileName, snippet.WebURL)
} else {
fmt.Fprintln(opts.IO.StdOut, snippet.WebURL)
}

return nil
}

// FIXME: Add a flag that would allow reading the snippets from a file instead of just STDIN.
// Adding more then one file can't be done right now because the GitLab API library
// Doesn't support it yet.
//
// See for the API reference: https://docs.gitlab.com/ee/api/snippets.html#create-new-snippet
// See for the library docs : https://pkg.go.dev/github.com/xanzy/go-gitlab#CreateSnippetOptions
// See for GitHub issue : https://github.com/xanzy/go-gitlab/issues/1372
func readSnippetsContent(opts *CreateOpts) (string, error) {
content, err := io.ReadAll(opts.IO.In)
if err != nil {
return "", fmt.Errorf("Failed to read snippet from STDIN. %w", err)
}
return string(content), nil
}
30 changes: 30 additions & 0 deletions commands/snippet/snippet.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package snippet

import (
"github.com/MakeNowJust/heredoc"
"github.com/profclems/glab/commands/cmdutils"
"github.com/profclems/glab/commands/snippet/create"
"github.com/spf13/cobra"
)

func NewCmdSnippet(f *cmdutils.Factory) *cobra.Command {
var snippetCmd = &cobra.Command{
Use: "snippet <command> [flags]",
Short: `Create, view and manage snippets`,
Long: ``,
Example: heredoc.Doc(`
$ glab snippet create --title "Title of the snippet" --filename "main.go"
`),
Annotations: map[string]string{
"help:arguments": heredoc.Doc(`
A snippet can be supplied as argument in the following format:
- by number, e.g. "123"
`),
},
}

cmdutils.EnableRepoOverride(snippetCmd, f)

snippetCmd.AddCommand(create.NewCmdCreate(f))
return snippetCmd
}
41 changes: 41 additions & 0 deletions commands/snippet/snippet_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package snippet

import (
"bytes"
"io"
"os"
"testing"

"github.com/alecthomas/assert"
"github.com/profclems/glab/commands/cmdtest"
"github.com/profclems/glab/commands/cmdutils"
)

func TestMain(m *testing.M) {
cmdtest.InitTest(m, "mr_cmd_test")
cmdtest.InitTest(m, "mr_cmd_autofill")
}

func TestCmdSnippet_noARgs(t *testing.T) {
old := os.Stdout // keep backup of the real stdout
r, w, _ := os.Pipe()
os.Stdout = w

assert.Nil(t, NewCmdSnippet(&cmdutils.Factory{}).Execute())

outC := make(chan string)
// copy the output in a separate goroutine so printing can't block indefinitely
go func() {
var buf bytes.Buffer
_, _ = io.Copy(&buf, r)
outC <- buf.String()
}()

// back to normal state
w.Close()
os.Stdout = old // restoring the real stdout
out := <-outC

assert.Contains(t, out, "Use \"snippet [command] --help\" for more information about a command.\n")

}

0 comments on commit 73e5888

Please sign in to comment.