gisty

package
v0.0.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 6, 2024 License: MIT Imports: 25 Imported by: 0

Documentation

Overview

Package gisty provides a similar functionality of `gh gist` command.

It aims to provide a simple and easy to use interface to interact with GitHub Gists in the Go applications.

- Note

In this package, the environment variable "GH_TOKEN" or "GITHUB_TOKEN" must be set to the "personal access token" of the GitHub API. (`gist` scope required)

For GitHub Enterprise users, the environment variable "GH_ENTERPRISE_TOKEN" or "GITHUB_ENTERPRISE_TOKEN" must also be set with the GitHub API "authentication token".

- Tips and info to implement Gisty commands

The basic of Gisty is the wrapper of the sub command `gist` of `gh` application which is a Cobra instance from the `cli/cli` package. Gisty object receives the command arguments and passes them to Cobra command.

Commands that are not supported by `gh`, `Stargazer` for example, are implemented via `api` sub command of `cli/cli`("github.com/cli/cli/v2/pkg/cmd/api"). This api package supports GitHub GraphQL API (v4) which supports more features than the REST API (v3) that `gh` uses.

- GraphQL API documentation: https://docs.github.com/en/graphql - GraphQL Explorer: https://docs.github.com/ja/graphql/overview/explorer

Index

Examples

Constants

View Source
const DummyID = "42f5f23053ab59ca480f480b8d01e1fd"

DummyID is the ID of the dummy gist used in the example of the test.

View Source
const MaxCommentDefault = 100

Variables

View Source
var AppendErrPos = true

AppendErrPos is a flag to disable the file name and line number of the caller from the error message. If set to false, it will not be appended.

View Source
var DummyComment = Comment{
	Author: Author{
		AvatarURL: "https://avatars.githubusercontent.com/u/11840938?u=e915b35bd36abfdcbbaaa6fbe5ea0c6e8ee51e70&v=4",
		Login:     "KEINOS",
	},
	ID:                "GC_lADOALStqtoAIDQyZjVmMjMwNTNhYjU5Y2E0ODBmNDgwYjhkMDFlMWZkzgBF6l4",
	AuthorAssociation: "OWNER",
	BodyRaw:           "1st example comment @ 20230528.\r\n\r\n- This line was added by edit.",
	BodyHTML:          "<p dir=\"auto\">1st example comment @ 20230528.</p>\n<ul dir=\"auto\">\n<li>This line was added by edit.</li>\n</ul>",
	BodyText:          "1st example comment @ 20230528.\n\nThis line was added by edit.",
	CreatedAt:         "2023-05-28T08:36:32Z",
	PublishedAt:       "2023-05-28T08:36:32Z",
	LastEditedAt:      "2023-05-28T08:44:10Z",
	IsMinimized:       false,
	MinimizedReason:   "",
}

DummyComment is the dummy comment used in the example of the test.

Functions

func ChDir

func ChDir(path string) (string, error)

ChDir changes the current working directory to the given path and returns the previous working directory.

It is the callers choice to change the working directory back to the previous working directory.

func NewErr

func NewErr(msgs ...any) error

NewErr returns a new error object with the given message appending the file name and line number of the caller.

It is a wrapper of errors.New() and errors.Errorf(). Which is the alternative of deprecated github.com/pkg/errors.

Example
package main

import (
	"fmt"

	"github.com/KEINOS/go-gisty/gisty"
)

func main() {
	if err := gisty.NewErr(); err == nil {
		fmt.Println("empty args returns nil")
	}

	// Note the output contains the file name and line number of the caller.
	if err := gisty.NewErr("simple error message"); err != nil {
		fmt.Println(err)
	}

	if err := gisty.NewErr("%v error message", "formatted"); err != nil {
		fmt.Println(err)
	}

	if err := gisty.NewErr("%v error message(s)", 3); err != nil {
		fmt.Println(err)
	}

	if err := gisty.NewErr(1, 2, 3); err != nil {
		fmt.Println(err)
	}

}
Output:

empty args returns nil
simple error message (file: new_err_test.go, line: 21)
formatted error message (file: new_err_test.go, line: 25)
3 error message(s) (file: new_err_test.go, line: 29)
1 2 3 (file: new_err_test.go, line: 33)

func SanitizeGistID

func SanitizeGistID(gistID string) string

SanitizeGistID removes non-alphanumeric characters from gistID.

func WrapIfErr

func WrapIfErr(err error, msgs ...any) error

WrapIfErr returns nil if err is nil.

Otherwise, it returns an error annotating err with a stack trace at the point WrapIfErr is called. The supplied message contains the file name and line number of the caller.

Note that if the "msgs" arg is more than one, the first arg is used as a format string and the rest are used as arguments.

E.g.

WrapIfErr(nil, "it wil do nothing")
WrapIfErr(err)                                 // returns err as is
WrapIfErr(err, "failed to do something")       // eq to errors.Wrap
WrapIfErr(err, "failed to do %s", "something") // eq to errors.Wrapf

It is a wrapper of errors.Wrap() and errors.Wrapf(). Which is the alternative of deprecated github.com/pkg/errors.

Example
package main

import (
	"fmt"

	"github.com/KEINOS/go-gisty/gisty"
)

func main() {
	var err error

	// WrapIfErr returns nil if err is nil
	fmt.Println("err is nil:", gisty.WrapIfErr(err, "error at line 18"))

	// Cause err to be non-nil
	err = gisty.NewErr("error occurred at line 21")
	// Wrap with no additional message
	fmt.Println("err is non-nil:\n", gisty.WrapIfErr(err))
	// Wrap with additional message
	fmt.Println("err is non-nil:\n", gisty.WrapIfErr(err, "wrapped at line 25"))
}
Output:

err is nil: <nil>
err is non-nil:
 error occurred at line 21 (file: wrap_if_err_test.go, line: 21)
err is non-nil:
 wrapped at line 25 (file: wrap_if_err_test.go, line: 25): error occurred at line 21 (file: wrap_if_err_test.go, line: 21)
Example (Disable_error_position)
package main

import (
	"fmt"

	"github.com/KEINOS/go-gisty/gisty"
)

func main() {
	// Backup and defer restore the original value of AppendErrPos
	oldAppendErrPos := gisty.AppendErrPos
	defer func() {
		gisty.AppendErrPos = oldAppendErrPos
	}()

	{
		gisty.AppendErrPos = false // Disable appending the error position

		err := gisty.NewErr("error occurred at line 45")
		fmt.Println(gisty.WrapIfErr(err, "wrapped at line 46"))
	}
	{
		gisty.AppendErrPos = true // Enable appending the error position (default)

		err := gisty.NewErr("error occurred at line 51")
		fmt.Println(gisty.WrapIfErr(err, "wrapped at line 52"))
	}
}
Output:

wrapped at line 46: error occurred at line 45
wrapped at line 52 (file: wrap_if_err_test.go, line: 52): error occurred at line 51 (file: wrap_if_err_test.go, line: 51)

Types

type AltFunc

type AltFunc struct {
	Clone     func(*clone.CloneOptions) error
	Comments  func(*api.ApiOptions) error
	Create    func(*create.CreateOptions) error
	Delete    func(*delete.DeleteOptions) error
	List      func(*list.ListOptions) error
	Read      func(*view.ViewOptions) error
	Stargazer func(*api.ApiOptions) error
	Update    func(*sync.SyncOptions) error
}

AltFunc is a set of alternative functions to be used in the commands.

Even though it is mostly used for dependency-injection purposes during testing, it can be used to overrride the default behavior of the commands.

type Author added in v0.0.2

type Author struct {
	AvatarURL string `json:"avatarUrl"`
	Login     string `json:"login"`
}

type Comment added in v0.0.2

type Comment struct {
	Author            Author `json:"author"`
	ID                string `json:"id"`
	AuthorAssociation string `json:"authorAssociation"`
	BodyRaw           string `json:"body"`
	BodyHTML          string `json:"bodyHTML"`
	BodyText          string `json:"bodyText"`
	CreatedAt         string `json:"createdAt"`
	PublishedAt       string `json:"publishedAt"`
	LastEditedAt      string `json:"lastEditedAt"`
	MinimizedReason   string `json:"minimizedReason"`
	IsMinimized       bool   `json:"isMinimized"`
}

Comment is a struct for the comment node in the GraphQL response.

type CreateArgs

type CreateArgs struct {
	// Description for this gist
	Description string
	// FilePaths to contain in this gist
	FilePaths []string
	// AsPublic indicates whether this gist should be public or secret.
	// By default, it is secret.
	AsPublic bool
}

CreateArgs are the arguments for the Create function.

type GistInfo

type GistInfo struct {
	UpdatedAt   time.Time // UpdatedAt is the time when the gist was last updated.
	GistID      string    // GistID is the ID of the gist.
	Description string    // Description is the description of the gist.
	Files       int       // Files is the number of files in the gist.
	IsPublic    bool      // IsPublic is true if the gist is public.
}

GistInfo holds information about a gist.

func NewGistInfo

func NewGistInfo(line string) (GistInfo, error)

NewGistInfo creates a new GistInfo instance from the given input.

The input line must be tab-separated and have the following format:

	[gistID]\t[description]\t[n] <file|files>\t<public|secret>\t[updatedAt]
 e.g.
	`1234567890abcdef	my gist	2 files	public	2018-01-01T00:00:00Z`
Example
package main

import (
	"fmt"
	"log"

	"github.com/KEINOS/go-gisty/gisty"
)

func main() {
	// "line" is one of the lines returned by the `gist list` command as an
	// example. Each line is tab separated and must contain the following
	// information in that order:
	//   1. Gist ID
	//   2. Description
	//   3. Number of files
	//   4. Public or private
	//   5. Updated at (RFC3339/ISO-8601 format)
	line := "7101f542be23e5048198e2a27c3cfda8	Title of gist item1	1 file	public	2022-09-18T18:56:10Z"

	// NewGistInfo parses the line and returns a GistInfo object.
	item, err := gisty.NewGistInfo(line)
	if err != nil {
		log.Fatal(err)
	}

	// Print the parsed information.
	fmt.Printf("%T: %v\n", item.GistID, item.GistID)
	fmt.Printf("%T: %v\n", item.Description, item.Description)
	fmt.Printf("%T: %v\n", item.IsPublic, item.IsPublic)
	fmt.Printf("%T: %v\n", item.UpdatedAt, item.UpdatedAt)
}
Output:

string: 7101f542be23e5048198e2a27c3cfda8
string: Title of gist item1
bool: true
time.Time: 2022-09-18 18:56:10 +0000 UTC

type Gisty

type Gisty struct {
	// AltFunctions is a set of alternative functions to be used in the
	// commands. If nil is set, the default function is used.
	AltFunctions AltFunc
	// Factory holds the I/O streams, http client, and other common
	// dependencies to request GitHub API.
	Factory *cmdutil.Factory
	// Stdin is the standard input stream which each command reads from.
	Stdin *bytes.Buffer
	// Stdout is the standard output stream which each command writes to.
	Stdout *bytes.Buffer
	// Stderr is the standard error stream which each command writes to.
	Stderr *bytes.Buffer
	// BuildDate is the date when the binary was built.
	BuildDate string
	// BuildVersion is the version of the binary.
	BuildVersion string
	// MaxComment is the max number of comments in a gist to be fetched.
	MaxComment int
}

Gisty is the main struct of this package.

func NewGisty

func NewGisty() *Gisty

NewGisty returns a new instance of Gisty.

func (*Gisty) Clone

func (g *Gisty) Clone(args []string) error

Clone clones a gist with the given args.

[]string{
  <gist>,             // required
  [<directory>],      // optional
  [-- <gitflags>...], // optional
}

<gist> is a gist ID or URL and <directory> is the directory to clone the gist into. <gitflags> listed after '--' are the additional flags to pass directly as a 'git clone' flags.

func (*Gisty) Comments added in v0.0.2

func (g *Gisty) Comments(gistID string) ([]Comment, error)

Comments returns the comments in the gist.

Example
package main

import (
	"fmt"
	"log"
	"strings"

	"github.com/KEINOS/go-gisty/gisty"
)

func main() {
	// Instantiate a new Gisty object.
	obj := gisty.NewGisty()

	// Get the comments in the gist.
	const gistID = "42f5f23053ab59ca480f480b8d01e1fd"

	comments, err := obj.Comments(gistID)
	if err != nil {
		log.Fatal(err)
	}

	if len(comments) == 0 {
		log.Fatal("No comments found.")
	}

	firstComment := comments[0]

	// Print available fields in the Comment struct.
	for _, field := range []struct {
		nameField string
		value     interface{}
	}{
		// List of fields in the Comment struct.
		{"Name", firstComment.Author.Login},
		{"Icon", firstComment.Author.AvatarURL},
		{"Association", firstComment.AuthorAssociation},
		{"Comment ID", firstComment.ID},
		{"Comment body(Raw)", firstComment.BodyRaw},   // Note that raw body contains "\r\n" for line breaks.
		{"Comment body(HTML)", firstComment.BodyHTML}, // Note that html body contains "\n" for line breaks.
		{"Comment body(Text)", firstComment.BodyText}, // Note that text body contains "\n" for line breaks.
		{"Created at", firstComment.CreatedAt},
		{"Published at", firstComment.PublishedAt},
		{"Updated at", firstComment.LastEditedAt},
		{"Is minimized", firstComment.IsMinimized},
		{"Minimized reason", firstComment.MinimizedReason},
	} {
		val, ok := field.value.(string)
		if ok {
			fmt.Printf("%s: %#v\n", field.nameField, strings.TrimSpace(val))
		} else {
			fmt.Printf("%s: %v\n", field.nameField, field.value)
		}
	}
}
Output:

Name: "KEINOS"
Icon: "https://avatars.githubusercontent.com/u/11840938?u=e915b35bd36abfdcbbaaa6fbe5ea0c6e8ee51e70&v=4"
Association: "OWNER"
Comment ID: "GC_lADOALStqtoAIDQyZjVmMjMwNTNhYjU5Y2E0ODBmNDgwYjhkMDFlMWZkzgBF6l4"
Comment body(Raw): "1st example comment @ 20230528.\r\n\r\n- This line was added by edit."
Comment body(HTML): "<p dir=\"auto\">1st example comment @ 20230528.</p>\n<ul dir=\"auto\">\n<li>This line was added by edit.</li>\n</ul>"
Comment body(Text): "1st example comment @ 20230528.\n\nThis line was added by edit."
Created at: "2023-05-28T08:36:32Z"
Published at: "2023-05-28T08:36:32Z"
Updated at: "2023-05-28T08:44:10Z"
Is minimized: false
Minimized reason: ""

func (*Gisty) Create

func (g *Gisty) Create(args CreateArgs) (*url.URL, error)

Create creates a new gist with the given args and returns the URL of the gist.

func (*Gisty) Delete

func (g *Gisty) Delete(gist string) error

Delete deletes a gist for a given gist ID or URL.

Note that it will remove the gist right away, without any confirmation.

func (*Gisty) List

func (g *Gisty) List(args ListArgs) ([]GistInfo, error)

List returns a list of GistInfo objects. The returned list depends on the arguments passed to the function.

Example (Dummy_api_call)

Example to retrieve the list of gists but WITHOUT calling the actual GitHub API.

package main

import (
	"fmt"
	"log"

	"github.com/KEINOS/go-gisty/gisty"
	"github.com/cli/cli/v2/pkg/cmd/gist/list"
)

func main() {
	obj := gisty.NewGisty()

	// Dummy function to avoid calling the actual GitHub API during test/example.
	// Usually, you do not need to set this.
	obj.AltFunctions.List = func(*list.ListOptions) error {
		// Mock the GitHub API response.
		fmt.Fprint(
			obj.Stdout,
			"d5b9800c636dd78defa4f15894d54d29	Title of gist item2	6 files	secret	2022-04-16T06:08:46Z",
		)

		return nil
	}

	// Retrieve the list of gists.
	gistInfos, err := obj.List(gisty.ListArgs{
		Limit:      1000,  // Maximum number of gists to be obtained.
		OnlyPublic: true,  // Get only public gists.
		OnlySecret: false, // Get only secret gists. If true, then prior than OnlyPublic.
	})
	if err != nil {
		log.Fatal(err)
	}

	// Loop through the obtained gist information. In this example, only one
	// gist is obtained.
	for numItem, gistInfo := range gistInfos {
		fmt.Printf("#%d GistID: %v\n", numItem+1, gistInfo.GistID)
		fmt.Printf("#%d Description: %v\n", numItem+1, gistInfo.Description)
		fmt.Printf("#%d Num files in a gist: %v\n", numItem+1, gistInfo.Files)
		fmt.Printf("#%d IsPublic: %v\n", numItem+1, gistInfo.IsPublic)
		fmt.Printf("#%d UpdatedAt: %v\n", numItem+1, gistInfo.UpdatedAt)
	}
}
Output:

#1 GistID: d5b9800c636dd78defa4f15894d54d29
#1 Description: Title of gist item2
#1 Num files in a gist: 6
#1 IsPublic: false
#1 UpdatedAt: 2022-04-16 06:08:46 +0000 UTC

func (*Gisty) Read

func (g *Gisty) Read(gist string) (*shared.Gist, error)

Read returns a list of GistInfo objects. The returned list depends on the arguments passed to the function.

func (*Gisty) Stargazer

func (g *Gisty) Stargazer(gistID string) (int, error)

Stargazer returns the number of stars in the gist for a given gist ID.

Note that gistID should not be the gist URL.

Example

Example to get the number of stars in the gist.

package main

import (
	"fmt"
	"log"

	"github.com/KEINOS/go-gisty/gisty"
	"github.com/cli/cli/v2/pkg/cmd/api"
)

func main() {
	obj := gisty.NewGisty()

	// Dummy function to avoid calling the actual GitHub API during test/example.
	// Usually, you do not need to set this.
	obj.AltFunctions.Stargazer = func(*api.ApiOptions) error {
		numStarsDummy := 10

		// Mock the GitHub API response.
		fmt.Fprintf(obj.Stdout, "'%d'", numStarsDummy)

		return nil
	}

	// Target gist ID to obtain the number of stars.
	gistID := "5b10b34f87955dfc86d310cd623a61d1"

	count, err := obj.Stargazer(gistID)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(count)
}
Output:

10

func (*Gisty) Update

func (g *Gisty) Update(args UpdateArgs) (msg string, err error)

Update syncs the gist repository with the given args. The returned string is the output of the command on success.

type ListArgs

type ListArgs struct {
	Limit      int  // Limit is the maximum number of gists to fetch (default 10)
	OnlyPublic bool // Show only public gists. Ignored if OnlySecret is true.
	OnlySecret bool // Show only secret gists. Prior than OnlyPublic.
}

ListArgs are the arguments/options to the List function.

type UpdateArgs

type UpdateArgs struct {
	PathDirRepo string // path to the local repository to sync.
	Branch      string // if set, syncs local repo from remote parent on specific branch.
	Destination string // If set, syncs as a destination repo. If not set, syncs to remote parent.
	Source      string // if set, syncs remote repo from another remote source repo.
	Force       bool   // if true, syncs using a hard reset. fast forward update if false.
}

UpdateArgs are the arguments for the Update function.

func NewUpdateArgs

func NewUpdateArgs(pathDirRepo string) UpdateArgs

Directories

Path Synopsis
Package buildinfo provides version information about the current build.
Package buildinfo provides version information about the current build.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL