Repository: skanehira/github-tui
Branch: main
Commit: f56081431e3c
Files: 39
Total size: 104.3 KB
Directory structure:
gitextract_8momnkvm/
├── LICENSE
├── README.md
├── cmd/
│ └── ght/
│ └── main.go
├── config/
│ └── config.go
├── domain/
│ ├── assignees.go
│ ├── comment.go
│ ├── error.go
│ ├── issue.go
│ ├── item.go
│ ├── label.go
│ ├── milestone.go
│ └── project.go
├── github/
│ ├── client.go
│ ├── mutation_comment.go
│ ├── mutation_issue.go
│ ├── query.go
│ ├── query_assignees.go
│ ├── query_comment.go
│ ├── query_issue.go
│ ├── query_label.go
│ ├── query_milestone.go
│ ├── query_project.go
│ └── query_repository.go
├── go.mod
├── go.sum
├── ui/
│ ├── assignees.go
│ ├── comments.go
│ ├── filter.go
│ ├── issues.go
│ ├── labels.go
│ ├── milestones.go
│ ├── projects.go
│ ├── search.go
│ ├── select.go
│ ├── ui.go
│ └── view.go
└── utils/
├── open.go
├── strings.go
└── utils.go
================================================
FILE CONTENTS
================================================
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2020 skanehira
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# github-tui
This is a TUI Client for GitHub.
**Still Under Development**
If you are using Vim, you can use [gh.vim](https://github.com/skanehira/gh.vim) instead.

## Features
### Implemented
- Issue
- list
- create
- close
- open
- open browser
- preview
- edit
- Issue comment
- list
- preview
- delete
- edit
- add
- quote reply
### Still Under Development
- Issue
- add assignees, labels, projects, milestone
- remove assignees, labels, projects, milestone
- PR
- list
- edit comment
- add comment
- delete comment
- diff
- create
- close
- change base
- merge
- Github Actions
- re-run
- list
- log
- File tree
- preview
- open browser
- preview
- Project
- columns
- open(if type is issue, pr)
- add
- remove
- move
- open browser
- config
- set default editor
- set user keybindings
## Installation
```sh
$ git clone https://github.com/skanehira/github-tui
$ go install ./cmd/ght
```
## Settings
At first, please set personal access [token](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) and email in config.yaml.
```yaml
github:
token: xxxxxxxxxxxxxxxxx
```
The config.yaml path must be in the bellow place.
| OS | place |
|------------|-----------------------------------------------------|
| Window | `%AppData%¥ght¥config.yaml` |
| Mac | `$HOME/Library/Application Support/ght/config.yaml` |
| Linux/Unix | `$HOME/.config/ght/config.yaml` |
## Usage
```sh
# current repository
$ ght
# specified repository
$ ght owner/repo
```
### Keybindings
| UI | Keybinding | Description |
|----------|----------------------|----------------------------------|
| Common | `j`/`down arrow` | Move down by one row. |
| Common | `k`/`up arrow` | Move up by one row. |
| Common | `g`/`home` | Move to the top. |
| Common | `G`/`end` | Move to the bottom. |
| Common | `Ctrl-F`/`page down` | Move down by one page. |
| Common | `Ctrl-B`/`page up` | Move up by one page. |
| Common | `Ctrl-N` | Move next UI. |
| Common | `Ctrl-P` | Move previous UI. |
| Common | `Ctrl-C` | Finish app. |
| Common | `Ctrl-G` | Focus to Issues |
| Common | `Ctrl-T` | Focus to Filters |
| Filters | `Enter` | Search with enter query. |
| Issues | `h`/`left arrow` | Move left by one column. |
| Issues | `l`/`right arrow` | Move right by one column. |
| Issues | `Ctrl-J` | Check issue and move down. |
| Issues | `Ctrl-K` | Check issue and move up. |
| Issues | `e` | Edit and update issue body. |
| Issues | `o` | Open checked issue. |
| Issues | `c` | Close checked issue. |
| Issues | `Ctrl-O` | Open checked issue on browser. |
| Issues | `/` | filter with enter words |
| Issues | `n` | Create new issue. |
| Issues | `f` | Fetch more issue. |
| Comments | `h`/`left arrow` | Move left by one column. |
| Comments | `l`/`right arrow` | Move right by one column. |
| Comments | `Ctrl-J` | Check comment and move down. |
| Comments | `Ctrl-K` | Check comment and move up. |
| Comments | `Ctrl-O` | Open checked comment on browser. |
| Comments | `n` | Add new issue comment. |
| Comments | `e` | Edit and update comment body. |
| Comments | `r` | Quote reply comment. |
| Comments | `/` | filter with enter words |
| Preview | `/` | search with enter words |
| Preview | `n` | move next word |
| Preview | `N` | move previous word |
| Preview | `o` | change to full screen |
### Note
When you creating issue, you can specify multiple labels, projects and assignees with `,`.
For instance, when you specify 2 labels then must input `label1,label2`.

When you edit issue body with `Edit Body` button then `$EDITOR` be used.
If `$EDITOR` is empty or not set, `vim` wll be used.
## Author
skanehira
================================================
FILE: cmd/ght/main.go
================================================
package main
import (
"flag"
"fmt"
"log"
"os/exec"
"strings"
"github.com/skanehira/ght/config"
"github.com/skanehira/ght/github"
"github.com/skanehira/ght/ui"
)
type Repo struct {
Owner string
Name string
}
func main() {
config.Init()
getRepoInfo()
github.NewClient(config.GitHub.Token)
if err := ui.New().Start(); err != nil {
log.Fatal(err)
}
}
func getRepoInfo() {
flag.Parse()
if len(flag.Args()) > 0 {
args := strings.Split(flag.Arg(0), "/")
if len(args) < 2 {
log.Fatal("invalid args")
}
config.GitHub.Owner = args[0]
config.GitHub.Repo = args[1]
} else {
repo, err := getOwnerRepo()
if err != nil {
log.Fatalf("invalid repo: %s", err)
}
config.GitHub.Owner = repo.Owner
config.GitHub.Repo = repo.Name
}
}
func getOwnerRepo() (*Repo, error) {
if _, err := exec.LookPath("git"); err != nil {
return nil, err
}
cmd := exec.Command("git", "remote", "get-url", "origin")
out, err := cmd.CombinedOutput()
result := strings.TrimRight(string(out), "\r\n")
if err != nil {
return nil, err
}
return parseRemote(result)
}
func parseRemote(remote string) (*Repo, error) {
if strings.HasSuffix(remote, ".git") {
remote = strings.TrimRight(remote, ".git")
}
var ownerRepo []string
if strings.HasPrefix(remote, "ssh") {
p := strings.Split(remote, "/")
if len(p) < 1 {
return nil, fmt.Errorf("cannot get owner/repo from remote: %s", remote)
}
ownerRepo = p[len(p)-2:]
} else if strings.HasPrefix(remote, "git") {
p := strings.Split(remote, ":")
if len(p) < 1 {
return nil, fmt.Errorf("cannot get owner/repo from remote: %s", remote)
}
ownerRepo = strings.Split(p[1], "/")
} else if strings.HasPrefix(remote, "http") || strings.HasPrefix(remote, "https") {
p := strings.Split(remote, "/")
if len(p) < 1 {
return nil, fmt.Errorf("cannot get owner/repo from remote: %s", remote)
}
ownerRepo = p[len(p)-2:]
}
repo := Repo{
Owner: ownerRepo[0],
Name: ownerRepo[1],
}
return &repo, nil
}
================================================
FILE: config/config.go
================================================
package config
import (
"io"
"log"
"os"
"path/filepath"
"github.com/goccy/go-yaml"
)
type github struct {
Owner string
Repo string
Token string `yaml:"token"`
}
type app struct {
File string `yaml:"file"`
}
const readThisMessage = "read this https://github.com/skanehira/github-tui?tab=readme-ov-file#settings to know more"
var (
GitHub github
App app
)
func Init() {
configDir, err := os.UserConfigDir()
if err != nil {
log.Fatal(err)
}
logFile := filepath.Join(configDir, "ght", "debug.log")
output, err := os.Create(logFile)
if err != nil {
log.Fatal(err)
}
log.SetOutput(io.MultiWriter(output, os.Stderr))
configFile := filepath.Join(configDir, "ght", "config.yaml")
b, err := os.ReadFile(configFile)
if err != nil {
if !os.IsNotExist(err) {
log.Fatal(err)
}
log.Fatalf("Could not find configuration file, %s", readThisMessage)
}
var conf struct {
GitHub github `yaml:"github"`
}
if err := yaml.Unmarshal(b, &conf); err != nil {
log.Fatalf("cannot deserialize config file: %s", err)
}
if conf.GitHub.Token == "" {
log.Fatalf("github token is empty, %s", readThisMessage)
}
App.File = configFile
GitHub = conf.GitHub
}
================================================
FILE: domain/assignees.go
================================================
package domain
import "github.com/gdamore/tcell/v2"
type AssignableUser struct {
Login string
}
func (a *AssignableUser) Key() string {
return a.Login
}
func (a *AssignableUser) Fields() []Field {
return []Field{
{Text: a.Login, Color: tcell.ColorFuchsia},
}
}
================================================
FILE: domain/comment.go
================================================
package domain
import "github.com/gdamore/tcell/v2"
type Comment struct {
ID string
Author string
UpdatedAt string
URL string
Body string
}
func (c *Comment) Key() string {
return c.ID
}
func (c *Comment) Fields() []Field {
f := []Field{
{Text: c.Author, Color: tcell.ColorYellow},
{Text: c.UpdatedAt, Color: tcell.ColorWhite},
}
return f
}
================================================
FILE: domain/error.go
================================================
package domain
import "errors"
var (
ErrCommentBodyIsEmpty = errors.New("comment body is empty")
ErrNotFoundComment = errors.New("not found comment")
ErrNotFoundIssue = errors.New("not found issue")
)
================================================
FILE: domain/issue.go
================================================
package domain
import (
"fmt"
"github.com/gdamore/tcell/v2"
)
type Issue struct {
ID string
Repo string
RepoOwner string
Number string
State string
Title string
Body string
Author string
URL string
Labels []Item
Assignees []Item
Comments []Item
MileStone []Item
Projects []Item
}
func (i *Issue) Key() string {
return i.ID
}
func (i *Issue) Fields() []Field {
stateColor := tcell.ColorGreen
if i.State == "CLOSED" {
stateColor = tcell.ColorRed
}
f := []Field{
{Text: fmt.Sprintf("%s/%s", i.RepoOwner, i.Repo), Color: tcell.ColorLightSalmon},
{Text: i.Number, Color: tcell.ColorBlue},
{Text: i.State, Color: stateColor},
{Text: i.Author, Color: tcell.ColorYellow},
{Text: i.Title, Color: tcell.ColorWhite},
}
return f
}
================================================
FILE: domain/item.go
================================================
package domain
import "github.com/gdamore/tcell/v2"
type Item interface {
Key() string
Fields() []Field
}
type Field struct {
Text string
Color tcell.Color
}
================================================
FILE: domain/label.go
================================================
package domain
import "github.com/gdamore/tcell/v2"
type Label struct {
Name string
Description string
}
func (l *Label) Key() string {
return l.Name
}
func (l *Label) Fields() []Field {
return []Field{
{Text: l.Name, Color: tcell.ColorLightYellow},
}
}
================================================
FILE: domain/milestone.go
================================================
package domain
import "github.com/gdamore/tcell/v2"
type Milestone struct {
ID string
Title string
State string
Description string
URL string
}
func (m *Milestone) Key() string {
return m.Title
}
func (m *Milestone) Fields() []Field {
return []Field{
{Text: m.Title, Color: tcell.ColorGreen},
}
}
================================================
FILE: domain/project.go
================================================
package domain
import "github.com/gdamore/tcell/v2"
type Project struct {
Name string
URL string
}
func (p *Project) Key() string {
return p.Name
}
func (p *Project) Fields() []Field {
return []Field{
{Text: p.Name, Color: tcell.ColorLightSalmon},
}
}
================================================
FILE: github/client.go
================================================
package github
import (
"context"
"github.com/shurcooL/githubv4"
"golang.org/x/oauth2"
)
var client *githubv4.Client
func NewClient(token string) {
src := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: token},
)
httpClient := oauth2.NewClient(context.Background(), src)
client = githubv4.NewClient(httpClient)
}
func CreateIssue(input githubv4.CreateIssueInput) error {
var m MutateCreateIssue
return client.Mutate(context.Background(), &m, input, nil)
}
func GetRepos(variables map[string]interface{}) (*Repositories, error) {
var q struct {
RepositoryOwner struct {
Repositories `graphql:"repositories(first: $first, after: $cursor, orderBy: {field: CREATED_AT, direction: DESC})"`
} `graphql:"repositoryOwner(login: $login)"`
}
if err := client.Query(context.Background(), &q, variables); err != nil {
return nil, err
}
return &q.RepositoryOwner.Repositories, nil
}
func GetRepo(variables map[string]interface{}) (*Repository, error) {
var q struct {
Repository `graphql:"repository(owner: $owner, name: $name)"`
}
if err := client.Query(context.Background(), &q, variables); err != nil {
return nil, err
}
return &q.Repository, nil
}
func GetIssues(variables map[string]interface{}) (*Issues, error) {
var q struct {
Search Issues `graphql:"search(query: $query, type: ISSUE, first: $first, after: $cursor)"`
}
if err := client.Query(context.Background(), &q, variables); err != nil {
return nil, err
}
issues := &Issues{
Nodes: q.Search.Nodes,
PageInfo: q.Search.PageInfo,
}
return issues, nil
}
func GetIssue(variables map[string]interface{}) (*Issue, error) {
var q struct {
Repository struct {
Issue *Issue `graphql:"issue(number: $number)"`
} `graphql:"repository(owner: $owner, name: $name)"`
}
if err := client.Query(context.Background(), &q, variables); err != nil {
return nil, err
}
return q.Repository.Issue, nil
}
func GetIssueTemplates(variables map[string]interface{}) ([]IssueTemplate, error) {
var q struct {
Repository struct {
IssueTemplates []IssueTemplate
} `graphql:"repository(name: $name, owner: $owner)"`
}
if err := client.Query(context.Background(), &q, variables); err != nil {
return nil, err
}
return q.Repository.IssueTemplates, nil
}
func ReopenIssue(id string) error {
input := githubv4.ReopenIssueInput{
IssueID: githubv4.String(id),
}
var m MutateOpenIsseue
return client.Mutate(context.Background(), &m, input, nil)
}
func CloseIssue(id string) error {
input := githubv4.CloseIssueInput{
IssueID: githubv4.String(id),
}
var m MutateCoseIssue
return client.Mutate(context.Background(), &m, input, nil)
}
func GetRepoLabels(variables map[string]interface{}) (*Labels, error) {
var q struct {
Repository struct {
Labels `graphql:"labels(first: $first, after: $cursor, orderBy: {field: CREATED_AT, direction: DESC})"`
} `graphql:"repository(name: $name, owner: $owner)"`
}
if err := client.Query(context.Background(), &q, variables); err != nil {
return nil, err
}
return &q.Repository.Labels, nil
}
func GetRepoMillestones(variables map[string]interface{}) (*Milestones, error) {
var q struct {
Repository struct {
Milestones `graphql:"milestones(first: $first, after: $cursor, orderBy: {field: CREATED_AT, direction: DESC})"`
} `graphql:"repository(name: $name, owner: $owner)"`
}
if err := client.Query(context.Background(), &q, variables); err != nil {
return nil, err
}
return &q.Repository.Milestones, nil
}
func GetRepoProjects(variables map[string]interface{}) (*Projects, error) {
var q struct {
Repository struct {
Projects `graphql:"projects(first: $first, after: $cursor, orderBy: {field: CREATED_AT, direction: DESC})"`
} `graphql:"repository(name: $name, owner: $owner)"`
}
if err := client.Query(context.Background(), &q, variables); err != nil {
return nil, err
}
return &q.Repository.Projects, nil
}
func GetRepoAssignableUsers(variables map[string]interface{}) (*AssignableUsers, error) {
var q struct {
Repository struct {
AssignableUsers `graphql:"assignableUsers(first: $first, after: $cursor)"`
} `graphql:"repository(name: $name, owner: $owner)"`
}
if err := client.Query(context.Background(), &q, variables); err != nil {
return nil, err
}
return &q.Repository.AssignableUsers, nil
}
func DeleteIssueComment(id string) error {
var m MutateDeleteComment
input := githubv4.DeleteIssueCommentInput{
ID: githubv4.ID(id),
}
return client.Mutate(context.Background(), &m, input, nil)
}
func UpdateIssue(input githubv4.UpdateIssueInput) error {
var m MutateUpdateIssue
return client.Mutate(context.Background(), &m, input, nil)
}
func UpdateIssueComment(input githubv4.UpdateIssueCommentInput) error {
var m MutateUpdateIssueComment
return client.Mutate(context.Background(), &m, input, nil)
}
func AddIssueComment(input githubv4.AddCommentInput) error {
var m MutateAddIssueComment
return client.Mutate(context.Background(), &m, input, nil)
}
================================================
FILE: github/mutation_comment.go
================================================
package github
import "github.com/shurcooL/githubv4"
type MutateDeleteComment struct {
DeleteIssueComment struct {
ClientMutationId githubv4.String
} `graphql:"deleteIssueComment(input: $input)"`
}
type MutateUpdateIssueComment struct {
UpdateIssueComment struct {
ClientMutationId githubv4.String
} `graphql:"updateIssueComment(input: $input)"`
}
================================================
FILE: github/mutation_issue.go
================================================
package github
import "github.com/shurcooL/githubv4"
type MutateOpenIsseue struct {
ReopenIssue struct {
Issue struct {
ID githubv4.String
}
} `graphql:"reopenIssue(input: $input)"`
}
type MutateCoseIssue struct {
CloseIssue struct {
Issue struct {
ID githubv4.String
}
} `graphql:"closeIssue(input: $input)"`
}
type MutateCreateIssue struct {
CreateIssue struct {
Issue struct {
ID githubv4.String
}
} `graphql:"createIssue(input: $input)"`
}
type MutateUpdateIssue struct {
UpdateIssue struct {
Issue struct {
ID githubv4.ID
}
} `graphql:"updateIssue(input: $input)"`
}
type MutateAddIssueComment struct {
AddIssueComment struct {
ClientMutationID githubv4.String
} `graphql:"addComment(input: $input)"`
}
================================================
FILE: github/query.go
================================================
package github
import "github.com/shurcooL/githubv4"
type PageInfo struct {
EndCursor githubv4.String
HasNextPage githubv4.Boolean
}
================================================
FILE: github/query_assignees.go
================================================
package github
import (
"github.com/shurcooL/githubv4"
"github.com/skanehira/ght/domain"
)
type AssignableUser struct {
Login githubv4.String
}
func (a *AssignableUser) ToDomain() *domain.AssignableUser {
assignableUser := &domain.AssignableUser{
Login: string(a.Login),
}
return assignableUser
}
type AssignableUsers struct {
Nodes []struct {
ID githubv4.ID
Login githubv4.String
}
PageInfo PageInfo
}
================================================
FILE: github/query_comment.go
================================================
package github
import (
"github.com/shurcooL/githubv4"
"github.com/skanehira/ght/domain"
)
type Comment struct {
ID githubv4.String
Author struct {
Login githubv4.String
}
UpdatedAt githubv4.DateTime
Body githubv4.String
URL githubv4.URI
}
func (c *Comment) ToDomain() *domain.Comment {
comment := &domain.Comment{
ID: string(c.ID),
Author: string(c.Author.Login),
UpdatedAt: c.UpdatedAt.Local().Format("2006/01/02 15:04:05"),
URL: c.URL.String(),
Body: string(c.Body),
}
return comment
}
================================================
FILE: github/query_issue.go
================================================
package github
import (
"reflect"
"strconv"
"github.com/shurcooL/githubv4"
"github.com/skanehira/ght/domain"
)
type Issue struct {
ID githubv4.String
Repository struct {
ID githubv4.String
Owner struct {
Login githubv4.String
}
Name githubv4.String
}
Number githubv4.Int
Body githubv4.String
State githubv4.String
Author struct {
Login githubv4.String
}
Title githubv4.String
URL githubv4.URI
Labels Labels `graphql:"labels(first: 10)"`
Assignees struct {
Nodes []AssignableUser
} `graphql:"assignees(first: 10)"`
ProjectCards struct {
Nodes []struct {
Project Project
}
} `graphql:"projectCards(first: 10)"`
Milestone Milestone
Comments struct {
Nodes []Comment
} `graphql:"comments(first: 100)"`
}
func (i *Issue) ToDomain() *domain.Issue {
issue := &domain.Issue{
ID: string(i.ID),
Repo: string(i.Repository.Name),
RepoOwner: string(i.Repository.Owner.Login),
Number: strconv.Itoa(int(i.Number)),
State: string(i.State),
Author: string(i.Author.Login),
URL: i.URL.String(),
Title: string(i.Title),
Body: string(i.Body),
}
labels := make([]domain.Item, len(i.Labels.Nodes))
for i, label := range i.Labels.Nodes {
labels[i] = label.ToDomain()
}
issue.Labels = labels
assignees := make([]domain.Item, len(i.Assignees.Nodes))
for i, a := range i.Assignees.Nodes {
assignees[i] = a.ToDomain()
}
issue.Assignees = assignees
comments := make([]domain.Item, len(i.Comments.Nodes))
for i, comment := range i.Comments.Nodes {
comments[i] = comment.ToDomain()
}
issue.Comments = comments
if !reflect.ValueOf(i.Milestone).IsZero() {
issue.MileStone = append(issue.MileStone, i.Milestone.ToDomain())
}
projects := make([]domain.Item, len(i.ProjectCards.Nodes))
for i, card := range i.ProjectCards.Nodes {
projects[i] = card.Project.ToDomain()
}
issue.Projects = projects
return issue
}
type Issues struct {
Nodes []struct {
Issue Issue `graphql:"... on Issue"`
}
PageInfo PageInfo
}
type IssueTemplate struct {
About githubv4.String
Body githubv4.String
Name githubv4.String
Title githubv4.String
}
================================================
FILE: github/query_label.go
================================================
package github
import (
"github.com/shurcooL/githubv4"
"github.com/skanehira/ght/domain"
)
type Label struct {
ID githubv4.ID
Name githubv4.String
Description githubv4.String
Color githubv4.String
}
func (l *Label) ToDomain() *domain.Label {
label := &domain.Label{
Name: string(l.Name),
Description: string(l.Description),
}
return label
}
type Labels struct {
Nodes []Label
PageInfo PageInfo
}
================================================
FILE: github/query_milestone.go
================================================
package github
import (
"github.com/shurcooL/githubv4"
"github.com/skanehira/ght/domain"
)
type Milestone struct {
ID githubv4.ID
Title githubv4.String
State githubv4.String
Description githubv4.String
URL githubv4.URI
}
func (m *Milestone) ToDomain() *domain.Milestone {
milestone := &domain.Milestone{
ID: m.ID.(string),
Title: string(m.Title),
State: string(m.State),
Description: string(m.Description),
URL: m.URL.String(),
}
return milestone
}
type Milestones struct {
Nodes []Milestone
PageInfo PageInfo
}
================================================
FILE: github/query_project.go
================================================
package github
import (
"github.com/shurcooL/githubv4"
"github.com/skanehira/ght/domain"
)
type Project struct {
ID githubv4.ID
Name githubv4.String
URL githubv4.URI
}
func (p *Project) ToDomain() *domain.Project {
project := &domain.Project{
Name: string(p.Name),
URL: p.URL.String(),
}
return project
}
type Projects struct {
Nodes []Project
PageInfo PageInfo
}
================================================
FILE: github/query_repository.go
================================================
package github
import "github.com/shurcooL/githubv4"
type Repository struct {
ID githubv4.ID
Name githubv4.String
NameWithOwner githubv4.String
CreatedAt githubv4.DateTime
DefaultBranchRef struct {
Name githubv4.String
}
Description githubv4.String
LicenseInfo struct {
Name githubv4.String
}
StargazerCount githubv4.Int
URL githubv4.URI
SSHURL githubv4.String
}
type Repositories struct {
Nodes []Repository
PageInfo PageInfo
}
================================================
FILE: go.mod
================================================
module github.com/skanehira/ght
go 1.15
require (
github.com/atotto/clipboard v0.1.2
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gdamore/tcell/v2 v2.2.0
github.com/goccy/go-yaml v1.8.3
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-colorable v0.1.6 // indirect
github.com/rivo/tview v0.0.0-20210312174852-ae9464cc3598
github.com/shurcooL/githubv4 v0.0.0-20200928013246-d292edc3691b
github.com/shurcooL/graphql v0.0.0-20200928012149-18c5c3165e3a // indirect
golang.org/x/net v0.0.0-20201026091529-146b70c837a4 // indirect
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208
golang.org/x/sys v0.0.0-20210316092937-0b90fd5c4c48 // indirect
)
================================================
FILE: go.sum
================================================
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/atotto/clipboard v0.1.2 h1:YZCtFu5Ie8qX2VmVTBnrqLSiU9XOWwqNRmdT3gIQzbY=
github.com/atotto/clipboard v0.1.2/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell v1.4.0 h1:vUnHwJRvcPQa3tzi+0QI4U9JINXYJlOz9yiaiPQ2wMU=
github.com/gdamore/tcell v1.4.0/go.mod h1:vxEiSDZdW3L+Uhjii9c3375IlDmR05bzxY404ZVSMo0=
github.com/gdamore/tcell/v2 v2.0.1-0.20201017141208-acf90d56d591/go.mod h1:vSVL/GV5mCSlPC6thFP5kfOFdM9MGZcalipmpTxTgQA=
github.com/gdamore/tcell/v2 v2.1.0 h1:UnSmozHgBkQi2PGsFr+rpdXuAPRRucMegpQp3Z3kDro=
github.com/gdamore/tcell/v2 v2.1.0/go.mod h1:vSVL/GV5mCSlPC6thFP5kfOFdM9MGZcalipmpTxTgQA=
github.com/gdamore/tcell/v2 v2.2.0 h1:vSyEgKwraXPSOkvCk7IwOSyX+Pv3V2cV9CikJMXg4U4=
github.com/gdamore/tcell/v2 v2.2.0/go.mod h1:cTTuF84Dlj/RqmaCIV5p4w8uG1zWdk0SF6oBpwHp4fU=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/goccy/go-yaml v1.8.3 h1:VGzw2KWSUyQX0yXai02S0nttBc+Oa4Kvh6RCFoxt8SE=
github.com/goccy/go-yaml v1.8.3/go.mod h1:wS4gNoLalDSJxo/SpngzPQ2BN4uuZVLCmbM4S3vd4+Y=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg=
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rivo/tview v0.0.0-20210217110421-8a8f78a6dd01 h1:rtCzDXdaqhiRakJsz0bUj+3sOUjw82bJDcJrAzQ0u+M=
github.com/rivo/tview v0.0.0-20210217110421-8a8f78a6dd01/go.mod h1:n2q/ydglZJ1kqxiNrnYO+FaX1H14vA0wKyIo953QakU=
github.com/rivo/tview v0.0.0-20210312174852-ae9464cc3598 h1:AbRrGXhagPRDItERv7nauBUUPi7Ma3IGIj9FqkQKW6k=
github.com/rivo/tview v0.0.0-20210312174852-ae9464cc3598/go.mod h1:VzCN9WX13RF88iH2CaGkmdHOlsy1ZZQcTmNwROqC+LI=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/shurcooL/githubv4 v0.0.0-20200928013246-d292edc3691b h1:0/ecDXh/HTHRtSDSFnD2/Ta1yQ5J76ZspVY4u0/jGFk=
github.com/shurcooL/githubv4 v0.0.0-20200928013246-d292edc3691b/go.mod h1:hAF0iLZy4td2EX+/8Tw+4nodhlMrwN3HupfaXj3zkGo=
github.com/shurcooL/graphql v0.0.0-20200928012149-18c5c3165e3a h1:KikTa6HtAK8cS1qjvUvvq4QO21QnwC+EfvB+OAuZ/ZU=
github.com/shurcooL/graphql v0.0.0-20200928012149-18c5c3165e3a/go.mod h1:AuYgA5Kyo4c7HfUmvRGs/6rGlMMV/6B1bVnB9JxJEEg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201026091529-146b70c837a4 h1:awiuzyrRjJDb+OXi9ceHO3SDxVoN3JER57mhtqkdQBs=
golang.org/x/net v0.0.0-20201026091529-146b70c837a4/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 h1:ld7aEMNHoBnnDAX15v1T6z31v8HwR2A9FYOuAhWqkwc=
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210218155724-8ebf48af031b h1:lAZ0/chPUDWwjqosYR0X4M490zQhMsiJ4K3DbA7o+3g=
golang.org/x/sys v0.0.0-20210218155724-8ebf48af031b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210316092937-0b90fd5c4c48 h1:70qalHWW1n9yoI8B8zEQxFJO/D6NUWIX8SNmJO+rvNw=
golang.org/x/sys v0.0.0-20210316092937-0b90fd5c4c48/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
gopkg.in/go-playground/validator.v9 v9.30.0 h1:Wk0Z37oBmKj9/n+tPyBHZmeL19LaCoK3Qq48VwYENss=
gopkg.in/go-playground/validator.v9 v9.30.0/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
================================================
FILE: ui/assignees.go
================================================
package ui
import (
"github.com/gdamore/tcell/v2"
)
var AssigneesUI *SelectUI
func NewAssignableUI() {
//getList := func(cursor *string) ([]List, github.PageInfo) {
// v := map[string]interface{}{
// "owner": githubv4.String(config.GitHub.Owner),
// "name": githubv4.String(config.GitHub.Repo),
// "first": githubv4.Int(100),
// "cursor": (*githubv4.String)(cursor),
// }
// resp, err := github.GetRepoAssignableUsers(v)
// if err != nil {
// return nil, github.PageInfo{}
// }
// assignees := make([]List, len(resp.Nodes))
// for i, p := range resp.Nodes {
// assignees[i] = &AssignableUser{
// Login: string(p.Login),
// }
// }
// return assignees, resp.PageInfo
//}
setOpt := func(ui *SelectUI) {
ui.capture = func(event *tcell.EventKey) *tcell.EventKey {
return event
}
}
ui := NewSelectListUI(UIKindAssignee, tcell.ColorFuchsia, setOpt)
AssigneesUI = ui
}
================================================
FILE: ui/comments.go
================================================
package ui
import (
"fmt"
"log"
"strconv"
"strings"
"github.com/gdamore/tcell/v2"
"github.com/shurcooL/githubv4"
"github.com/skanehira/ght/domain"
"github.com/skanehira/ght/github"
"github.com/skanehira/ght/utils"
"golang.org/x/sync/errgroup"
)
var CommentUI *SelectUI
func NewCommentUI() {
setOpt := func(ui *SelectUI) {
ui.capture = func(event *tcell.EventKey) *tcell.EventKey {
switch event.Rune() {
case 'd':
deleteComment()
case 'n':
item := IssueUI.GetSelect()
if item == nil {
return event
}
if err := createComment(item, ""); err != nil {
UI.Message(err.Error(), func() {
UI.app.SetFocus(CommentUI)
})
}
case 'e':
if err := editComment(); err != nil {
UI.Message(err.Error(), func() {
UI.app.SetFocus(CommentUI)
})
}
case 'r':
if err := quoteReply(); err != nil {
UI.Message(err.Error(), func() {
UI.app.SetFocus(CommentUI)
})
}
}
switch event.Key() {
case tcell.KeyCtrlO:
for _, comment := range getSelectedComments() {
if err := utils.Open(comment.URL); err != nil {
log.Println(err)
}
}
CommentUI.ClearSelected()
CommentUI.UpdateView()
}
return event
}
ui.header = []string{
"",
"Author",
"UpdatedAt",
}
ui.hasHeader = len(ui.header) > 0
}
ui := NewSelectListUI(UIKindComment, tcell.ColorYellow, setOpt)
ui.SetSelectionChangedFunc(func(row, col int) {
if row > 0 {
CommentViewUI.updateView(ui.items[row-1].(*domain.Comment).Body)
}
})
CommentUI = ui
}
func quoteReply() error {
item := CommentUI.GetSelect()
if item == nil {
return domain.ErrNotFoundComment
}
comment := item.(*domain.Comment)
lines := strings.Split(comment.Body, "\n")
for i := range lines {
lines[i] = fmt.Sprintf("> %s", lines[i])
}
body := strings.Join(lines, "\n")
item = IssueUI.GetSelect()
if item == nil {
return domain.ErrNotFoundIssue
}
if err := createComment(item, body); err != nil {
return err
}
return nil
}
func createComment(item domain.Item, body string) error {
if err := editCommentBody(&body); err != nil {
return err
}
input := githubv4.AddCommentInput{
SubjectID: githubv4.ID(item.Key()),
Body: githubv4.String(body),
}
if err := github.AddIssueComment(input); err != nil {
return err
}
if err := updateCommentUI(); err != nil {
return err
}
return nil
}
func deleteComment() {
UI.Confirm("Do you want to delete comments?", "Yes", func() error {
comments := getSelectedComments()
if len(comments) == 0 {
return nil
}
var eg errgroup.Group
for _, comment := range comments {
id := comment.ID
eg.Go(func() error {
return github.DeleteIssueComment(id)
})
}
// When all the processing is completed this error be returned
// because if some of delete action be success, need to update view
deleteErr := eg.Wait()
if deleteErr != nil {
log.Println(deleteErr)
}
if err := updateCommentUI(); err != nil {
return err
}
return deleteErr
}, func() {
UI.app.SetFocus(CommentUI)
})
}
func editComment() error {
item := CommentUI.GetSelect()
if item == nil {
return domain.ErrNotFoundComment
}
comment := item.(*domain.Comment)
oldBody := comment.Body
if err := editCommentBody(&comment.Body); err != nil {
return err
}
// if comment body is not changed, do nothing
if oldBody == comment.Body {
return nil
}
input := githubv4.UpdateIssueCommentInput{
ID: githubv4.ID(comment.ID),
Body: githubv4.String(comment.Body),
}
if err := github.UpdateIssueComment(input); err != nil {
return err
}
if err := updateCommentUI(); err != nil {
return err
}
return nil
}
func editCommentBody(body *string) (err error) {
UI.app.Suspend(func() {
err = utils.Edit(body)
})
if err != nil {
return
}
if *body == "" {
return domain.ErrCommentBodyIsEmpty
}
return
}
func getSelectedComments() []*domain.Comment {
var comments []*domain.Comment
if len(CommentUI.selected) == 0 {
data := CommentUI.GetSelect()
comments = append(comments, data.(*domain.Comment))
} else {
for _, item := range CommentUI.selected {
comments = append(comments, item.(*domain.Comment))
}
}
return comments
}
func updateCommentUI() error {
item := IssueUI.GetSelect()
if item == nil {
return domain.ErrNotFoundIssue
}
oldIssue := item.(*domain.Issue)
number, err := strconv.Atoi(oldIssue.Number)
if err != nil {
return err
}
m := map[string]interface{}{
"owner": githubv4.String(oldIssue.RepoOwner),
"name": githubv4.String(oldIssue.Repo),
"number": githubv4.Int(number),
}
issue, err := github.GetIssue(m)
if err != nil {
return err
}
newIssue := issue.ToDomain()
IssueUI.UpdateItem(newIssue)
if len(newIssue.Comments) > 0 {
CommentUI.SetList(newIssue.Comments)
CommentViewUI.updateView(newIssue.Comments[0].(*domain.Comment).Body)
} else {
CommentUI.ClearView()
CommentViewUI.Clear()
}
return nil
}
================================================
FILE: ui/filter.go
================================================
package ui
import (
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
)
var IssueFilterUI *FilterUI
type (
SetFilterOpt func(ui *FilterUI)
FilterUI struct {
*tview.InputField
}
)
func NewFilterUI() {
ui := &FilterUI{
InputField: tview.NewInputField().SetLabel("Filters").SetLabelWidth(8),
}
ui.SetBorderPadding(0, 0, 1, 0)
ui.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
switch event.Key() {
case tcell.KeyEnter:
go IssueUI.GetList()
}
return event
})
IssueFilterUI = ui
}
func (ui *FilterUI) SetQuery(query string) {
ui.SetText(query)
}
func (ui *FilterUI) GetQuery() string {
return ui.GetText()
}
func (ui *FilterUI) focus() {
}
func (ui *FilterUI) blur() {
}
================================================
FILE: ui/issues.go
================================================
package ui
import (
"fmt"
"log"
"strings"
"sync"
"time"
"github.com/atotto/clipboard"
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
"github.com/shurcooL/githubv4"
"github.com/skanehira/ght/config"
"github.com/skanehira/ght/domain"
"github.com/skanehira/ght/github"
"github.com/skanehira/ght/utils"
)
var IssueUI *SelectUI
func NewIssueUI() {
opt := func(ui *SelectUI) {
// initial query
queries := []string{
fmt.Sprintf("repo:%s/%s", config.GitHub.Owner, config.GitHub.Repo),
"state:open",
}
IssueFilterUI.SetQuery(strings.Join(queries, " "))
ui.getList = func(cursor *string) ([]domain.Item, *github.PageInfo) {
var queries []string
query := IssueFilterUI.GetQuery()
if !strings.Contains(query, "is:issue") {
queries = append(queries, "is:issue")
}
for _, q := range strings.Split(query, " ") {
// execlude Pull request
if strings.Contains(q, "type:pr") || strings.Contains(q, "is:pr") {
continue
}
queries = append(queries, q)
}
query = strings.Join(queries, " ")
IssueFilterUI.SetQuery(query)
v := map[string]interface{}{
"query": githubv4.String(query),
"first": githubv4.Int(30),
"cursor": (*githubv4.String)(cursor),
}
resp, err := github.GetIssues(v)
if err != nil {
log.Println(err)
return nil, nil
}
issues := make([]domain.Item, len(resp.Nodes))
for i, node := range resp.Nodes {
issues[i] = node.Issue.ToDomain()
}
return issues, &resp.PageInfo
}
ui.capture = func(event *tcell.EventKey) *tcell.EventKey {
switch event.Rune() {
case 'y':
yankIssueURLs()
case 'o':
go openIssues()
case 'c':
go closeIssues()
case 'n':
createIssueForm()
case 'e':
editIssue()
}
switch event.Key() {
case tcell.KeyCtrlO:
openBrowser()
}
return event
}
ui.header = []string{
"",
"Repo",
"Number",
"State",
"Author",
"Title",
}
ui.hasHeader = len(ui.header) > 0
}
ui := NewSelectListUI(UIKindIssue, tcell.ColorBlue, opt)
ui.SetSelectionChangedFunc(func(row, col int) {
updateUIRelatedIssue(ui, row)
})
IssueUI = ui
}
func getSelectedIssues() []*domain.Issue {
var issues []*domain.Issue
if len(IssueUI.selected) == 0 {
data := IssueUI.GetSelect()
if data != nil {
issues = append(issues, data.(*domain.Issue))
}
} else {
for _, item := range IssueUI.selected {
issues = append(issues, item.(*domain.Issue))
}
}
return issues
}
func yankIssueURLs() {
var urls []string
for _, issue := range getSelectedIssues() {
urls = append(urls, issue.URL)
}
url := strings.Join(urls, "\n")
if err := clipboard.WriteAll(url); err != nil {
log.Println(err)
}
IssueUI.ClearSelected()
IssueUI.UpdateView()
}
func openIssues() {
var wg sync.WaitGroup
for _, issue := range getSelectedIssues() {
wg.Add(1)
go func(issue *domain.Issue) {
defer wg.Done()
if err := github.ReopenIssue(issue.ID); err != nil {
log.Println(err)
return
}
issue.State = "OPEN"
}(issue)
}
wg.Wait()
IssueUI.ClearSelected()
IssueUI.UpdateView()
}
func closeIssues() {
var wg sync.WaitGroup
for _, issue := range getSelectedIssues() {
wg.Add(1)
go func(issue *domain.Issue) {
defer wg.Done()
if err := github.CloseIssue(issue.ID); err != nil {
log.Println(err)
return
}
issue.State = "CLOSED"
}(issue)
}
wg.Wait()
IssueUI.ClearSelected()
IssueUI.UpdateView()
}
func openBrowser() {
for _, issue := range getSelectedIssues() {
if err := utils.Open(issue.URL); err != nil {
log.Println(err)
}
}
IssueUI.ClearSelected()
IssueUI.UpdateView()
}
func createIssueForm() {
// repo
var repo string
input := IssueFilterUI.GetQuery()
for _, word := range strings.Split(input, " ") {
if strings.Contains(word, "repo:") {
repo = strings.TrimPrefix(word, "repo:")
break
}
}
if repo == "" {
return
}
form := tview.NewForm()
form.SetBorder(true)
form.SetTitle("New issue")
form.SetTitleAlign(tview.AlignLeft)
inputWidth := 70
repoInput := tview.NewInputField().SetLabel("Repository").
SetText(repo).SetLabelWidth(inputWidth).
SetAcceptanceFunc(func(textToCheck string, lastChar rune) bool {
return false
})
repoInput.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
if event.Key() == tcell.KeyCtrlN {
form.SetFocus(2)
}
return event
})
form.AddFormItem(repoInput)
s := strings.Split(repoInput.GetText(), "/")
owner := s[0]
name := s[1]
var repoID githubv4.ID
resp, err := github.GetRepo(map[string]interface{}{
"owner": githubv4.String(owner),
"name": githubv4.String(name),
})
if err != nil {
UI.Message(err.Error(), func() {
UI.app.SetFocus(IssueUI)
})
return
}
repoID = resp.ID
form.SetFocus(1)
// autocomplete for assignees, labels, projects, milestones
autocompleteFunc := func(text string, items []string) []string {
if text == "" {
return nil
}
words := strings.Split(text, ",")
word := words[len(words)-1]
var results []string
for _, l := range items {
var isDuplicate bool
for _, w := range words[:len(words)-1] {
if w == l {
isDuplicate = true
break
}
}
if isDuplicate {
continue
}
if strings.Contains(strings.ToLower(l), strings.ToLower(word)) {
words = append(words[:len(words)-1], l)
results = append(results, strings.Join(words, ","))
}
}
return results
}
// graphql query variables
v := map[string]interface{}{
"owner": githubv4.String(owner),
"name": githubv4.String(name),
"first": githubv4.Int(100),
"cursor": (*githubv4.String)(nil),
}
// title
titleInput := tview.NewInputField().SetLabel("Title").SetLabelWidth(inputWidth)
form.AddFormItem(titleInput)
// assignees
assigneesInput := tview.NewInputField().SetLabel("Assignees").SetLabelWidth(inputWidth)
form.AddFormItem(assigneesInput)
userMap := map[string]githubv4.ID{}
go func() {
resp, err := github.GetRepoAssignableUsers(v)
if err != nil {
log.Println(err)
return
}
if len(resp.Nodes) == 0 {
UI.app.QueueUpdateDraw(func() {
form.RemoveFormItem(2)
})
return
}
var users []string
for _, u := range resp.Nodes {
name := string(u.Login)
userMap[name] = u.ID
users = append(users, name)
}
assigneesInput.SetAutocompleteFunc(func(text string) []string {
return autocompleteFunc(text, users)
})
}()
// labels
labelInput := tview.NewInputField().SetLabel("Labels").SetLabelWidth(inputWidth)
labelMap := map[string]githubv4.ID{}
form.AddFormItem(labelInput)
go func() {
resp, err := github.GetRepoLabels(v)
if err != nil {
log.Println(err)
return
}
if len(resp.Nodes) == 0 {
UI.app.QueueUpdateDraw(func() {
form.RemoveFormItem(3)
})
return
}
var labels []string
for _, l := range resp.Nodes {
name := string(l.Name)
labelMap[name] = l.ID
labels = append(labels, name)
}
labelInput.SetAutocompleteFunc(func(text string) []string {
return autocompleteFunc(text, labels)
})
}()
// projects
projectInput := tview.NewInputField().SetLabel("Projects").SetLabelWidth(inputWidth)
projectMap := map[string]githubv4.ID{}
form.AddFormItem(projectInput)
go func() {
resp, err := github.GetRepoProjects(v)
if err != nil {
log.Println(err)
return
}
if len(resp.Nodes) == 0 {
UI.app.QueueUpdateDraw(func() {
form.RemoveFormItem(4)
})
return
}
var projects []string
for _, project := range resp.Nodes {
name := string(project.Name)
projectMap[name] = project.ID
projects = append(projects, name)
}
projectInput.SetAutocompleteFunc(func(text string) []string {
return autocompleteFunc(text, projects)
})
}()
// milestones
milestoneDropDown := tview.NewDropDown().SetLabel("MileStone").SetLabelWidth(inputWidth)
var milestoneID *githubv4.ID
form.AddFormItem(milestoneDropDown)
go func() {
resp, err := github.GetRepoMillestones(v)
if err != nil {
log.Println(err)
return
}
if len(resp.Nodes) == 0 {
UI.app.QueueUpdateDraw(func() {
form.RemoveFormItem(5)
})
return
}
milestones := map[string]*githubv4.ID{}
var titles []string
for _, milestone := range resp.Nodes {
title := string(milestone.Title)
milestones[title] = &milestone.ID
titles = append(titles, title)
}
milestoneDropDown.SetOptions(titles, func(text string, index int) {
milestoneID = milestones[text]
})
}()
var issueBody string
templateDropDown := tview.NewDropDown().SetLabel("Template").SetLabelWidth(inputWidth)
go func() {
v := map[string]interface{}{
"owner": githubv4.String(owner),
"name": githubv4.String(name),
}
resp, err := github.GetIssueTemplates(v)
if err != nil {
log.Println(err)
return
}
if len(resp) == 0 {
return
}
issueTemplates := map[string]string{}
var names []string
for _, te := range resp {
issueTemplates[string(te.Name)] = string(te.Body)
names = append(names, string(te.Name))
}
templateDropDown.SetOptions(names, func(text string, index int) {
issueBody = issueTemplates[text]
})
UI.app.QueueUpdateDraw(func() {
form.AddFormItem(templateDropDown)
})
}()
form.AddButton("Edit Body", func() {
UI.app.Suspend(func() {
if err := utils.Edit(&issueBody); err != nil {
log.Println(err)
return
}
})
})
form.AddButton("Create", func() {
input := githubv4.CreateIssueInput{
Title: githubv4.String(titleInput.GetText()),
RepositoryID: repoID,
}
if milestoneID != nil {
input.MilestoneID = milestoneID
}
// get assignee users
var userIDs []githubv4.ID
if text := assigneesInput.GetText(); text != "" {
for _, name := range strings.Split(text, ",") {
if name == "" {
continue
}
userIDs = append(userIDs, userMap[name])
}
input.AssigneeIDs = &userIDs
}
// get labels
var labelIDs []githubv4.ID
if text := labelInput.GetText(); text != "" {
for _, name := range strings.Split(text, ",") {
if name == "" {
continue
}
labelIDs = append(labelIDs, labelMap[name])
}
input.LabelIDs = &labelIDs
}
// get projects
var projectIDs []githubv4.ID
if text := projectInput.GetText(); text != "" {
for _, name := range strings.Split(text, ",") {
if name == "" {
continue
}
projectIDs = append(projectIDs, projectMap[name])
}
input.ProjectIDs = &projectIDs
}
body := githubv4.String(issueBody)
input.Body = &body
if err := github.CreateIssue(input); err != nil {
UI.Message(err.Error(), func() {
UI.pages.SwitchToPage("form").ShowPage("main")
})
} else {
UI.pages.RemovePage("form").ShowPage("main")
UI.app.SetFocus(IssueUI)
go func() {
time.Sleep(1 * time.Second)
IssueUI.GetList()
}()
}
})
form.AddButton("Cancel", func() {
UI.pages.RemovePage("form").ShowPage("main")
UI.app.SetFocus(IssueUI)
})
form.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
switch event.Key() {
case tcell.KeyCtrlN:
k := tcell.NewEventKey(tcell.KeyTab, 0, tcell.ModNone)
UI.app.QueueEvent(k)
case tcell.KeyCtrlP:
k := tcell.NewEventKey(tcell.KeyBacktab, 0, tcell.ModNone)
UI.app.QueueEvent(k)
}
return event
})
UI.pages.AddAndSwitchToPage("form", UI.Modal(form, 100, 19), true).ShowPage("main")
}
func editIssue() {
item := IssueUI.GetSelect()
if item == nil {
return
}
issue := item.(*domain.Issue)
focus := func() {
UI.app.SetFocus(IssueUI)
}
var err error
old := issue.Body
UI.app.Suspend(func() {
err = utils.Edit(&issue.Body)
})
if err != nil {
UI.Message(err.Error(), focus)
return
}
// if issue body not edited do nothing
if old == issue.Body {
return
}
input := githubv4.UpdateIssueInput{
ID: githubv4.ID(issue.ID),
Body: githubv4.NewString(githubv4.String(issue.Body)),
}
if err := github.UpdateIssue(input); err != nil {
UI.Message(err.Error(), focus)
return
}
IssueViewUI.updateView(issue.Body)
}
func updateUIRelatedIssue(ui *SelectUI, row int) {
if row > 0 && row <= len(ui.items) {
issue := ui.items[row-1].(*domain.Issue)
IssueViewUI.updateView(issue.Body)
if len(issue.Comments) > 0 {
CommentUI.SetList(issue.Comments)
CommentViewUI.updateView(issue.Comments[0].(*domain.Comment).Body)
} else {
CommentUI.ClearView()
CommentViewUI.Clear()
}
if len(issue.Assignees) > 0 {
AssigneesUI.SetList(issue.Assignees)
} else {
AssigneesUI.ClearView()
}
if len(issue.Labels) > 0 {
LabelUI.SetList(issue.Labels)
} else {
LabelUI.ClearView()
}
if len(issue.MileStone) > 0 {
MilestoneUI.SetList(issue.MileStone)
} else {
MilestoneUI.ClearView()
}
if len(issue.Projects) > 0 {
ProjectUI.SetList(issue.Projects)
} else {
ProjectUI.ClearView()
}
}
}
================================================
FILE: ui/labels.go
================================================
package ui
import (
"github.com/gdamore/tcell/v2"
)
var LabelUI *SelectUI
func NewLabelsUI() {
//getList := func(cursor *string) ([]List, github.PageInfo) {
// v := map[string]interface{}{
// "owner": githubv4.String(config.GitHub.Owner),
// "name": githubv4.String(config.GitHub.Repo),
// "first": githubv4.Int(100),
// "cursor": (*githubv4.String)(cursor),
// }
// resp, err := github.GetRepoLabels(v)
// if err != nil {
// log.Println(err)
// return nil, github.PageInfo{}
// }
// labels := make([]List, len(resp.Nodes))
// for i, l := range resp.Nodes {
// name := string(l.Name)
// description := string(l.Description)
// labels[i] = &Label{
// Name: name,
// Description: description,
// }
// }
// return labels, resp.PageInfo
//}
setOpt := func(ui *SelectUI) {
ui.capture = func(event *tcell.EventKey) *tcell.EventKey {
return event
}
}
ui := NewSelectListUI(UIKindLabel, tcell.ColorLightYellow, setOpt)
LabelUI = ui
}
================================================
FILE: ui/milestones.go
================================================
package ui
import (
"log"
"github.com/gdamore/tcell/v2"
"github.com/skanehira/ght/domain"
"github.com/skanehira/ght/utils"
)
var MilestoneUI *SelectUI
func NewMilestoneUI() {
//getList := func(cursor *string) ([]List, github.PageInfo) {
// v := map[string]interface{}{
// "owner": githubv4.String(config.GitHub.Owner),
// "name": githubv4.String(config.GitHub.Repo),
// "first": githubv4.Int(100),
// "cursor": (*githubv4.String)(cursor),
// }
// resp, err := github.GetRepoMillestones(v)
// if err != nil {
// return nil, github.PageInfo{}
// }
// milestones := make([]List, len(resp.Nodes))
// for i, m := range resp.Nodes {
// milestones[i] = &Milestone{
// Title: string(m.Title),
// }
// }
// return milestones, resp.PageInfo
//}
setOpt := func(ui *SelectUI) {
ui.capture = func(event *tcell.EventKey) *tcell.EventKey {
switch event.Key() {
case tcell.KeyCtrlO:
var urls []string
if len(MilestoneUI.selected) == 0 {
data := MilestoneUI.GetSelect()
if data != nil {
urls = append(urls, data.(*domain.Milestone).URL)
}
} else {
for _, s := range MilestoneUI.selected {
urls = append(urls, s.(*domain.Milestone).URL)
}
}
for _, url := range urls {
if err := utils.Open(url); err != nil {
log.Println(err)
}
}
}
return event
}
}
ui := NewSelectListUI(UIKindMilestones, tcell.ColorGreen, setOpt)
MilestoneUI = ui
}
================================================
FILE: ui/projects.go
================================================
package ui
import (
"log"
"github.com/gdamore/tcell/v2"
"github.com/skanehira/ght/domain"
"github.com/skanehira/ght/utils"
)
var ProjectUI *SelectUI
func NewProjectUI() {
//getList := func(cursor *string) ([]List, github.PageInfo) {
// v := map[string]interface{}{
// "owner": githubv4.String(config.GitHub.Owner),
// "name": githubv4.String(config.GitHub.Repo),
// "first": githubv4.Int(100),
// "cursor": (*githubv4.String)(cursor),
// }
// resp, err := github.GetRepoProjects(v)
// if err != nil {
// return nil, github.PageInfo{}
// }
// projects := make([]List, len(resp.Nodes))
// for i, m := range resp.Nodes {
// projects[i] = &Project{
// Name: string(m.Name),
// }
// }
// return projects, resp.PageInfo
//}
setOpt := func(ui *SelectUI) {
ui.capture = func(event *tcell.EventKey) *tcell.EventKey {
switch event.Key() {
case tcell.KeyCtrlO:
var urls []string
if len(ProjectUI.selected) == 0 {
data := ProjectUI.GetSelect()
if data != nil {
urls = append(urls, data.(*domain.Project).URL)
}
} else {
for _, s := range ProjectUI.selected {
urls = append(urls, s.(*domain.Project).URL)
}
}
for _, url := range urls {
if err := utils.Open(url); err != nil {
log.Println(err)
}
}
}
return event
}
}
ui := NewSelectListUI(UIKindProject, tcell.ColorLightSalmon, setOpt)
ProjectUI = ui
}
================================================
FILE: ui/search.go
================================================
package ui
import (
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
)
var SearchUI *searchUI
type SearchFunc func(text string)
type searchUI struct {
*tview.InputField
SearchFunc SearchFunc
FocusFunc func()
}
func NewSearchUI() {
ui := &searchUI{
InputField: tview.NewInputField(),
SearchFunc: func(text string) {},
FocusFunc: func() {},
}
ui.SetDoneFunc(func(key tcell.Key) {
ui.FocusFunc()
})
SearchUI = ui
}
func (s *searchUI) SetSerachFunc(f SearchFunc) {
s.SetChangedFunc(f)
}
func (s *searchUI) SetFocusFunc(f func()) {
s.FocusFunc = f
}
func (s *searchUI) focus() {
}
func (s *searchUI) blur() {
}
================================================
FILE: ui/select.go
================================================
package ui
import (
"strings"
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
"github.com/skanehira/ght/domain"
"github.com/skanehira/ght/github"
)
const (
unselected = "\u25ef"
selected = "\u25c9"
)
type UIKind string
const (
UIKindIssue UIKind = "issues"
UIKindAssignee = "assignees"
UIKindComment = "comments"
UIKindLabel = "labels"
UIKindMilestones = "milestones"
UIKindProject = "projects"
UIKindIssueView = "issue preview"
UIKindCommentView = "comment preview"
UIKindCommonView = "preview"
)
type (
SetSelectUIOpt func(ui *SelectUI)
GetListFunc func(cursor *string) ([]domain.Item, *github.PageInfo)
CaptureFunc func(event *tcell.EventKey) *tcell.EventKey
)
type SelectUI struct {
uiKind UIKind
cursor *string
hasNext bool
getList GetListFunc
capture CaptureFunc
header []string
hasHeader bool
originItems []domain.Item
items []domain.Item
selected map[string]domain.Item
boxColor tcell.Color
searchWord string
*tview.Table
}
func NewSelectListUI(uiKind UIKind, boxColor tcell.Color, setOpt SetSelectUIOpt) *SelectUI {
ui := &SelectUI{
uiKind: uiKind,
hasNext: true,
selected: make(map[string]domain.Item),
boxColor: boxColor,
Table: tview.NewTable().SetSelectable(false, false),
}
ui.SetBorder(true).SetTitle(string(uiKind)).SetTitleAlign(tview.AlignLeft)
ui.SetBorderColor(boxColor)
setOpt(ui)
go ui.Init()
return ui
}
func (ui *SelectUI) GetList() {
if ui.getList != nil {
list, pageInfo := ui.getList(nil)
if pageInfo != nil {
ui.hasNext = bool(pageInfo.HasNextPage)
cursor := string(pageInfo.EndCursor)
ui.originItems = list
ui.cursor = &cursor
ui.Select(0, 0)
ui.UpdateView()
}
}
}
func (ui *SelectUI) SetList(list []domain.Item) {
ui.originItems = list
ui.selected = make(map[string]domain.Item)
ui.Select(0, 0)
ui.UpdateView()
}
func (ui *SelectUI) FetchList() {
if ui.hasNext && ui.getList != nil {
list, pageInfo := ui.getList(ui.cursor)
ui.hasNext = bool(pageInfo.HasNextPage)
cursor := string(pageInfo.EndCursor)
ui.originItems = append(ui.originItems, list...)
ui.cursor = &cursor
ui.UpdateView()
}
}
func (ui *SelectUI) UpdateView() {
UI.updater <- func() {
ui.Clear()
for i, h := range ui.header {
ui.SetCell(0, i, &tview.TableCell{
Text: h,
NotSelectable: true,
Align: tview.AlignLeft,
Color: tcell.ColorWhite,
BackgroundColor: tcell.ColorDefault,
Attributes: tcell.AttrBold | tcell.AttrUnderline,
})
}
if len(ui.originItems) < 1 {
return
}
h := 0
if ui.hasHeader {
h++
ui.SetFixed(1, 0)
}
selectColor := ui.originItems[0].Fields()[0].Color
ui.items = []domain.Item{}
if ui.searchWord != "" {
for _, data := range ui.originItems {
for _, f := range data.Fields() {
if strings.Contains(strings.ToLower(f.Text), strings.ToLower(ui.searchWord)) {
ui.items = append(ui.items, data)
break
}
}
}
} else {
ui.items = ui.originItems
}
for i, data := range ui.items {
if _, ok := ui.selected[data.Key()]; ok {
ui.SetCell(i+h, 0, tview.NewTableCell(selected).SetTextColor(selectColor))
} else {
ui.SetCell(i+h, 0, tview.NewTableCell(unselected).SetTextColor(selectColor))
}
for j, f := range data.Fields() {
ui.SetCell(i+h, j+1, tview.NewTableCell(f.Text).SetTextColor(f.Color))
}
}
ui.ScrollToBeginning()
// when update filter, then update ui related issue primitives
if ui.uiKind == UIKindIssue {
row, _ := ui.GetSelection()
if row == 0 {
row = 1
}
updateUIRelatedIssue(ui, row)
}
}
}
func (ui *SelectUI) Init() {
ui.GetList()
searchFunc := func(text string) {
ui.searchWord = text
ui.UpdateView()
}
ui.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
switch event.Key() {
case tcell.KeyCtrlJ:
row, col := ui.GetSelection()
max := len(ui.items)
if ui.hasHeader {
max++
}
if row < max {
ui.toggleSelected(row)
}
if row+1 < max {
ui.Select(row+1, col)
}
case tcell.KeyCtrlK:
row, col := ui.GetSelection()
min := 0
if ui.hasHeader {
min++
}
if row > min {
ui.toggleSelected(row - 1)
}
if row > min {
ui.Select(row-1, col)
}
}
switch event.Rune() {
case 'f':
go ui.FetchList()
case '/':
SearchUI.SetSerachFunc(searchFunc)
SearchUI.SetFocusFunc(func() {
UI.app.SetFocus(ui)
})
UI.app.SetFocus(SearchUI)
}
return ui.capture(event)
})
}
func (ui *SelectUI) toggleSelected(row int) {
var data domain.Item
if ui.hasHeader {
data = ui.items[row-1]
} else {
data = ui.items[row]
}
selectColor := ui.items[0].Fields()[0].Color
if _, ok := ui.selected[data.Key()]; ok {
delete(ui.selected, data.Key())
ui.SetCell(row, 0, tview.NewTableCell(unselected).SetTextColor(selectColor))
} else {
ui.selected[data.Key()] = data
ui.SetCell(row, 0, tview.NewTableCell(selected).SetTextColor(selectColor))
}
}
func (ui *SelectUI) UpdateItem(item domain.Item) {
for i, t := range ui.originItems {
if t.Key() == item.Key() {
ui.originItems[i] = item
}
}
}
func (ui *SelectUI) GetSelect() domain.Item {
row, _ := ui.GetSelection()
if ui.hasHeader {
row = row - 1
}
if len(ui.items) > row {
id := ui.items[row].Key()
for _, item := range ui.originItems {
if item.Key() == id {
return item
}
}
}
return nil
}
func (ui *SelectUI) focus() {
ui.SetSelectable(true, false)
}
func (ui *SelectUI) blur() {
ui.SetSelectable(false, false)
}
func (ui *SelectUI) ClearView() {
ui.Clear()
ui.ClearSelected()
}
func (ui *SelectUI) ClearSelected() {
ui.selected = make(map[string]domain.Item)
}
================================================
FILE: ui/ui.go
================================================
package ui
import (
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
)
var (
UI *ui
)
type Primitive interface {
focus()
blur()
tview.Primitive
}
type ui struct {
app *tview.Application
pages *tview.Pages
current int
primitives []Primitive
primitiveLen int
updater chan func()
}
func New() *ui {
ui := &ui{
app: tview.NewApplication(),
}
ui.updater = make(chan func(), 100)
UI = ui
return ui
}
func (ui *ui) canFocus() bool {
fs := ui.app.GetFocus()
if fs == nil {
return false
}
switch fs.(type) {
case *FilterUI, *SelectUI, *ViewUI:
return true
}
return false
}
func (ui *ui) toNextUI() {
if !ui.canFocus() {
return
}
ui.primitives[ui.current].blur()
if ui.primitiveLen-1 > ui.current {
ui.current++
} else {
ui.current = 0
}
p := ui.primitives[ui.current]
p.focus()
ui.app.SetFocus(p)
}
func (ui *ui) toPrevUI() {
if !ui.canFocus() {
return
}
ui.primitives[ui.current].blur()
if ui.current == 0 {
ui.current = ui.primitiveLen - 1
} else {
ui.current--
}
p := ui.primitives[ui.current]
p.focus()
ui.app.SetFocus(p)
}
func (ui *ui) Modal(p tview.Primitive, width, height int) tview.Primitive {
return tview.NewGrid().
SetColumns(0, width, 0).
SetRows(0, height, 0).
AddItem(p, 1, 1, 1, 1, 0, 0, true)
}
func (ui *ui) Message(msg string, focusFunc func()) {
modal := tview.NewModal().
SetText(msg).
AddButtons([]string{"OK"}).
SetDoneFunc(func(_ int, _ string) {
ui.pages.RemovePage("message").ShowPage("main")
focusFunc()
})
ui.pages.AddAndSwitchToPage("message", ui.Modal(modal, 80, 29), true).ShowPage("main")
}
func (ui *ui) FullScreenPreview(contents string, focus func()) {
CommonViewUI.SetText(contents).ScrollToBeginning()
CommonViewUI.setFocus = focus
grid := tview.NewGrid().SetRows(0, 1).
AddItem(CommonViewUI, 0, 0, 1, 1, 0, 0, true)
ui.pages.AddAndSwitchToPage("fullScreenPreview", grid, true).ShowPage("main")
}
func (ui *ui) Confirm(msg, doLabel string, doFunc func() error, focusFunc func()) {
modal := tview.NewModal().
SetText(msg).
AddButtons([]string{doLabel, "Cancel"}).
SetDoneFunc(func(_ int, buttonLabel string) {
ui.pages.RemovePage("modal").ShowPage("main")
focusFunc()
if buttonLabel == doLabel {
if err := doFunc(); err != nil {
ui.Message(err.Error(), func() {
focusFunc()
})
}
}
})
ui.pages.AddAndSwitchToPage("modal", ui.Modal(modal, 80, 29), true).ShowPage("main")
}
func (ui *ui) Start() error {
NewFilterUI()
NewViewUI(UIKindIssueView)
NewViewUI(UIKindCommentView)
NewViewUI(UIKindCommonView)
NewIssueUI()
NewLabelsUI()
NewMilestoneUI()
NewProjectUI()
NewAssignableUI()
NewCommentUI()
NewSearchUI()
ui.primitives = []Primitive{IssueFilterUI, AssigneesUI, LabelUI, MilestoneUI,
ProjectUI, IssueUI, IssueViewUI, CommentUI, CommentViewUI}
ui.primitiveLen = len(ui.primitives)
// for readability
row, col, rowSpan, colSpan := 0, 0, 0, 0
grid := tview.NewGrid().SetRows(1, 0, 0, 0, 0, 0, 0, 0, 0, 1).
AddItem(IssueFilterUI, row, col, rowSpan+1, colSpan+3, 0, 0, true).
AddItem(IssueUI, row+1, col+1, rowSpan+4, colSpan+3, 0, 0, true).
AddItem(AssigneesUI, row+1, col, rowSpan+1, colSpan+1, 0, 0, true).
AddItem(LabelUI, row+2, col, rowSpan+1, colSpan+1, 0, 0, true).
AddItem(MilestoneUI, row+3, col, rowSpan+1, colSpan+1, 0, 0, true).
AddItem(ProjectUI, row+4, col, rowSpan+1, colSpan+1, 0, 0, true).
AddItem(CommentUI, row+5, col, rowSpan+4, colSpan+4, 0, 0, true).
AddItem(IssueViewUI, row+1, col+4, rowSpan+4, colSpan+3, 0, 0, true).
AddItem(CommentViewUI, row+5, col+4, rowSpan+4, colSpan+3, 0, 0, true).
AddItem(SearchUI, row+9, col, rowSpan+1, colSpan+7, 0, 0, true)
ui.pages = tview.NewPages().
AddAndSwitchToPage("main", grid, true)
ui.app.SetRoot(ui.pages, true)
ui.app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
switch event.Key() {
case tcell.KeyCtrlN:
UI.toNextUI()
case tcell.KeyCtrlP:
UI.toPrevUI()
case tcell.KeyCtrlG:
ui.primitives[ui.current].blur()
ui.current = 5
p := ui.primitives[ui.current]
p.focus()
ui.app.SetFocus(IssueUI)
case tcell.KeyCtrlT:
ui.primitives[ui.current].blur()
ui.current = 0
p := ui.primitives[ui.current]
p.focus()
ui.app.SetFocus(IssueFilterUI)
}
return event
})
ui.current = 5
ui.app.SetFocus(IssueUI)
IssueUI.focus()
go func() {
for f := range UI.updater {
go ui.app.QueueUpdateDraw(f)
}
}()
if err := ui.app.Run(); err != nil {
ui.app.Stop()
return err
}
return nil
}
================================================
FILE: ui/view.go
================================================
package ui
import (
"strconv"
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
"github.com/skanehira/ght/utils"
)
var (
IssueViewUI *ViewUI
CommentViewUI *ViewUI
CommonViewUI *ViewUI
)
type ViewUI struct {
*tview.TextView
regionIndex int
regionLength int
regionIDs []string
uiKind UIKind
setFocus func()
}
func NewViewUI(uiKind UIKind) {
ui := &ViewUI{
TextView: tview.NewTextView(),
uiKind: uiKind,
}
ui.SetBorder(true).SetTitle(string(uiKind)).SetTitleAlign(tview.AlignLeft)
ui.SetDynamicColors(true).SetWordWrap(false).SetRegions(true)
ui.SetBorderPadding(1, 1, 1, 1)
var setFocus func()
switch uiKind {
case UIKindIssueView:
IssueViewUI = ui
setFocus = func() {
UI.app.SetFocus(IssueViewUI)
}
case UIKindCommentView:
CommentViewUI = ui
setFocus = func() {
UI.app.SetFocus(CommentViewUI)
}
case UIKindCommonView:
CommonViewUI = ui
}
searchFunc := func(input string) {
text := ui.GetText(true)
if input != "" {
ui.regionIDs, text = utils.Replace(text, input, `[#ff0000]["%d"]`+input+`[""][white]`, -1)
ui.regionLength = len(ui.regionIDs)
if ui.regionLength > 0 {
ui.regionIndex = 0
ui.Highlight(ui.regionIDs[0]).ScrollToHighlight()
}
}
go ui.updateView(text)
}
ui.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
switch event.Rune() {
case '/':
SearchUI.SetText("")
SearchUI.SetSerachFunc(searchFunc)
SearchUI.SetFocusFunc(func() {
UI.app.SetFocus(ui)
})
UI.app.SetFocus(SearchUI)
case 'n':
if ui.regionLength > 0 {
ui.regionIndex = (ui.regionIndex + 1) % ui.regionLength
ui.Highlight(strconv.Itoa(ui.regionIndex)).ScrollToHighlight()
}
case 'N':
if ui.regionLength > 0 {
ui.regionIndex = (ui.regionIndex - 1 + ui.regionLength) % ui.regionLength
ui.Highlight(strconv.Itoa(ui.regionIndex)).ScrollToHighlight()
}
case 'o':
if ui.uiKind == UIKindCommonView {
UI.pages.SwitchToPage("main")
ui.setFocus()
return event
}
UI.FullScreenPreview(ui.GetText(true), setFocus)
}
//switch event.Key() {
//}
return event
})
}
func (ui *ViewUI) updateView(text string) {
UI.updater <- func() {
ui.SetText(text).ScrollToBeginning()
//out, err := glamour.Render(text, "dark")
//if err != nil {
// out = err.Error()
//}
//ui.SetText(tview.TranslateANSI(out)).ScrollToBeginning()
}
}
func (v *ViewUI) focus() {}
func (v *ViewUI) blur() {}
================================================
FILE: utils/open.go
================================================
package utils
import (
"errors"
"os/exec"
"runtime"
"strings"
)
func Open(url string) error {
args := []string{}
switch runtime.GOOS {
case "windows":
r := strings.NewReplacer("&", "^&")
args = []string{"cmd", "start", "/", r.Replace(url)}
case "linux":
args = []string{"xdg-open", url}
case "darwin":
args = []string{"open", url}
}
out, err := exec.Command(args[0], args[1:]...).CombinedOutput()
if err != nil {
return errors.New(string(out))
}
return nil
}
================================================
FILE: utils/strings.go
================================================
package utils
import (
"fmt"
"strconv"
"strings"
"unicode/utf8"
)
// Replace is customized for this project
// https://github.com/golang/go/blob/a8942d2cffd80c68febe1c908a0eb464d2f5bb40/src/strings/strings.go#L924
func Replace(s, old, new string, n int) ([]string, string) {
if old == new || n == 0 {
return nil, s // avoid allocation
}
// Compute number of replacements.
if m := strings.Count(s, old); m == 0 {
return nil, s // avoid allocation
} else if n < 0 || m < n {
n = m
}
// Apply replacements to buffer.
t := make([]byte, len(s)+n*(len(new)-len(old)))
w := 0
start := 0
var regionIDs []string
for i := 0; i < n; i++ {
j := start
if len(old) == 0 {
if i > 0 {
_, wid := utf8.DecodeRuneInString(s[start:])
j += wid
}
} else {
j += strings.Index(s[start:], old)
}
w += copy(t[w:], s[start:j])
w += copy(t[w:], fmt.Sprintf(new, i))
regionIDs = append(regionIDs, strconv.Itoa(i))
start = j + len(old)
}
w += copy(t[w:], s[start:])
return regionIDs, string(t[0:w])
}
================================================
FILE: utils/utils.go
================================================
package utils
import (
"io"
"io/ioutil"
"log"
"os"
"os/exec"
"strings"
)
func Edit(contents *string) error {
f, err := ioutil.TempFile("", "*.md")
if err != nil {
return err
}
defer os.Remove(f.Name())
if *contents != "" {
if _, err := io.Copy(f, strings.NewReader(*contents)); err != nil {
log.Println(err)
}
}
f.Close()
editor := os.Getenv("EDITOR")
if editor == "" {
editor = "vim"
}
cmd := exec.Command(editor, f.Name())
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return err
}
b, err := ioutil.ReadFile(f.Name())
if err != nil {
return err
}
newContents := string(b)
*contents = newContents
return nil
}
gitextract_8momnkvm/
├── LICENSE
├── README.md
├── cmd/
│ └── ght/
│ └── main.go
├── config/
│ └── config.go
├── domain/
│ ├── assignees.go
│ ├── comment.go
│ ├── error.go
│ ├── issue.go
│ ├── item.go
│ ├── label.go
│ ├── milestone.go
│ └── project.go
├── github/
│ ├── client.go
│ ├── mutation_comment.go
│ ├── mutation_issue.go
│ ├── query.go
│ ├── query_assignees.go
│ ├── query_comment.go
│ ├── query_issue.go
│ ├── query_label.go
│ ├── query_milestone.go
│ ├── query_project.go
│ └── query_repository.go
├── go.mod
├── go.sum
├── ui/
│ ├── assignees.go
│ ├── comments.go
│ ├── filter.go
│ ├── issues.go
│ ├── labels.go
│ ├── milestones.go
│ ├── projects.go
│ ├── search.go
│ ├── select.go
│ ├── ui.go
│ └── view.go
└── utils/
├── open.go
├── strings.go
└── utils.go
SYMBOL INDEX (157 symbols across 34 files)
FILE: cmd/ght/main.go
type Repo (line 15) | type Repo struct
function main (line 20) | func main() {
function getRepoInfo (line 29) | func getRepoInfo() {
function getOwnerRepo (line 48) | func getOwnerRepo() (*Repo, error) {
function parseRemote (line 63) | func parseRemote(remote string) (*Repo, error) {
FILE: config/config.go
type github (line 12) | type github struct
type app (line 18) | type app struct
constant readThisMessage (line 22) | readThisMessage = "read this https://github.com/skanehira/github-tui?tab...
function Init (line 29) | func Init() {
FILE: domain/assignees.go
type AssignableUser (line 5) | type AssignableUser struct
method Key (line 9) | func (a *AssignableUser) Key() string {
method Fields (line 13) | func (a *AssignableUser) Fields() []Field {
FILE: domain/comment.go
type Comment (line 5) | type Comment struct
method Key (line 13) | func (c *Comment) Key() string {
method Fields (line 17) | func (c *Comment) Fields() []Field {
FILE: domain/issue.go
type Issue (line 9) | type Issue struct
method Key (line 26) | func (i *Issue) Key() string {
method Fields (line 30) | func (i *Issue) Fields() []Field {
FILE: domain/item.go
type Item (line 5) | type Item interface
type Field (line 10) | type Field struct
FILE: domain/label.go
type Label (line 5) | type Label struct
method Key (line 10) | func (l *Label) Key() string {
method Fields (line 14) | func (l *Label) Fields() []Field {
FILE: domain/milestone.go
type Milestone (line 5) | type Milestone struct
method Key (line 13) | func (m *Milestone) Key() string {
method Fields (line 17) | func (m *Milestone) Fields() []Field {
FILE: domain/project.go
type Project (line 5) | type Project struct
method Key (line 10) | func (p *Project) Key() string {
method Fields (line 14) | func (p *Project) Fields() []Field {
FILE: github/client.go
function NewClient (line 12) | func NewClient(token string) {
function CreateIssue (line 20) | func CreateIssue(input githubv4.CreateIssueInput) error {
function GetRepos (line 25) | func GetRepos(variables map[string]interface{}) (*Repositories, error) {
function GetRepo (line 38) | func GetRepo(variables map[string]interface{}) (*Repository, error) {
function GetIssues (line 48) | func GetIssues(variables map[string]interface{}) (*Issues, error) {
function GetIssue (line 63) | func GetIssue(variables map[string]interface{}) (*Issue, error) {
function GetIssueTemplates (line 76) | func GetIssueTemplates(variables map[string]interface{}) ([]IssueTemplat...
function ReopenIssue (line 88) | func ReopenIssue(id string) error {
function CloseIssue (line 98) | func CloseIssue(id string) error {
function GetRepoLabels (line 107) | func GetRepoLabels(variables map[string]interface{}) (*Labels, error) {
function GetRepoMillestones (line 119) | func GetRepoMillestones(variables map[string]interface{}) (*Milestones, ...
function GetRepoProjects (line 131) | func GetRepoProjects(variables map[string]interface{}) (*Projects, error) {
function GetRepoAssignableUsers (line 143) | func GetRepoAssignableUsers(variables map[string]interface{}) (*Assignab...
function DeleteIssueComment (line 155) | func DeleteIssueComment(id string) error {
function UpdateIssue (line 163) | func UpdateIssue(input githubv4.UpdateIssueInput) error {
function UpdateIssueComment (line 168) | func UpdateIssueComment(input githubv4.UpdateIssueCommentInput) error {
function AddIssueComment (line 173) | func AddIssueComment(input githubv4.AddCommentInput) error {
FILE: github/mutation_comment.go
type MutateDeleteComment (line 5) | type MutateDeleteComment struct
type MutateUpdateIssueComment (line 11) | type MutateUpdateIssueComment struct
FILE: github/mutation_issue.go
type MutateOpenIsseue (line 5) | type MutateOpenIsseue struct
type MutateCoseIssue (line 13) | type MutateCoseIssue struct
type MutateCreateIssue (line 21) | type MutateCreateIssue struct
type MutateUpdateIssue (line 29) | type MutateUpdateIssue struct
type MutateAddIssueComment (line 37) | type MutateAddIssueComment struct
FILE: github/query.go
type PageInfo (line 5) | type PageInfo struct
FILE: github/query_assignees.go
type AssignableUser (line 8) | type AssignableUser struct
method ToDomain (line 12) | func (a *AssignableUser) ToDomain() *domain.AssignableUser {
type AssignableUsers (line 19) | type AssignableUsers struct
FILE: github/query_comment.go
type Comment (line 8) | type Comment struct
method ToDomain (line 18) | func (c *Comment) ToDomain() *domain.Comment {
FILE: github/query_issue.go
type Issue (line 11) | type Issue struct
method ToDomain (line 43) | func (i *Issue) ToDomain() *domain.Issue {
type Issues (line 86) | type Issues struct
type IssueTemplate (line 93) | type IssueTemplate struct
FILE: github/query_label.go
type Label (line 8) | type Label struct
method ToDomain (line 15) | func (l *Label) ToDomain() *domain.Label {
type Labels (line 23) | type Labels struct
FILE: github/query_milestone.go
type Milestone (line 8) | type Milestone struct
method ToDomain (line 16) | func (m *Milestone) ToDomain() *domain.Milestone {
type Milestones (line 27) | type Milestones struct
FILE: github/query_project.go
type Project (line 8) | type Project struct
method ToDomain (line 14) | func (p *Project) ToDomain() *domain.Project {
type Projects (line 22) | type Projects struct
FILE: github/query_repository.go
type Repository (line 5) | type Repository struct
type Repositories (line 22) | type Repositories struct
FILE: ui/assignees.go
function NewAssignableUI (line 9) | func NewAssignableUI() {
FILE: ui/comments.go
function NewCommentUI (line 19) | func NewCommentUI() {
function quoteReply (line 82) | func quoteReply() error {
function createComment (line 107) | func createComment(item domain.Item, body string) error {
function deleteComment (line 128) | func deleteComment() {
function editComment (line 160) | func editComment() error {
function editCommentBody (line 193) | func editCommentBody(body *string) (err error) {
function getSelectedComments (line 207) | func getSelectedComments() []*domain.Comment {
function updateCommentUI (line 220) | func updateCommentUI() error {
FILE: ui/filter.go
type SetFilterOpt (line 11) | type SetFilterOpt
type FilterUI (line 12) | type FilterUI struct
method SetQuery (line 33) | func (ui *FilterUI) SetQuery(query string) {
method GetQuery (line 37) | func (ui *FilterUI) GetQuery() string {
method focus (line 41) | func (ui *FilterUI) focus() {
method blur (line 44) | func (ui *FilterUI) blur() {
function NewFilterUI (line 17) | func NewFilterUI() {
FILE: ui/issues.go
function NewIssueUI (line 22) | func NewIssueUI() {
function getSelectedIssues (line 111) | func getSelectedIssues() []*domain.Issue {
function yankIssueURLs (line 126) | func yankIssueURLs() {
function openIssues (line 140) | func openIssues() {
function closeIssues (line 158) | func closeIssues() {
function openBrowser (line 176) | func openBrowser() {
function createIssueForm (line 187) | func createIssueForm() {
function editIssue (line 522) | func editIssue() {
function updateUIRelatedIssue (line 560) | func updateUIRelatedIssue(ui *SelectUI, row int) {
FILE: ui/labels.go
function NewLabelsUI (line 9) | func NewLabelsUI() {
FILE: ui/milestones.go
function NewMilestoneUI (line 13) | func NewMilestoneUI() {
FILE: ui/projects.go
function NewProjectUI (line 13) | func NewProjectUI() {
FILE: ui/search.go
type SearchFunc (line 10) | type SearchFunc
type searchUI (line 12) | type searchUI struct
method SetSerachFunc (line 32) | func (s *searchUI) SetSerachFunc(f SearchFunc) {
method SetFocusFunc (line 36) | func (s *searchUI) SetFocusFunc(f func()) {
method focus (line 40) | func (s *searchUI) focus() {
method blur (line 44) | func (s *searchUI) blur() {
function NewSearchUI (line 18) | func NewSearchUI() {
FILE: ui/select.go
constant unselected (line 13) | unselected = "\u25ef"
constant selected (line 14) | selected = "\u25c9"
type UIKind (line 17) | type UIKind
constant UIKindIssue (line 20) | UIKindIssue UIKind = "issues"
constant UIKindAssignee (line 21) | UIKindAssignee = "assignees"
constant UIKindComment (line 22) | UIKindComment = "comments"
constant UIKindLabel (line 23) | UIKindLabel = "labels"
constant UIKindMilestones (line 24) | UIKindMilestones = "milestones"
constant UIKindProject (line 25) | UIKindProject = "projects"
constant UIKindIssueView (line 26) | UIKindIssueView = "issue preview"
constant UIKindCommentView (line 27) | UIKindCommentView = "comment preview"
constant UIKindCommonView (line 28) | UIKindCommonView = "preview"
type SetSelectUIOpt (line 32) | type SetSelectUIOpt
type GetListFunc (line 33) | type GetListFunc
type CaptureFunc (line 34) | type CaptureFunc
type SelectUI (line 37) | type SelectUI struct
method GetList (line 71) | func (ui *SelectUI) GetList() {
method SetList (line 85) | func (ui *SelectUI) SetList(list []domain.Item) {
method FetchList (line 92) | func (ui *SelectUI) FetchList() {
method UpdateView (line 103) | func (ui *SelectUI) UpdateView() {
method Init (line 166) | func (ui *SelectUI) Init() {
method toggleSelected (line 218) | func (ui *SelectUI) toggleSelected(row int) {
method UpdateItem (line 235) | func (ui *SelectUI) UpdateItem(item domain.Item) {
method GetSelect (line 243) | func (ui *SelectUI) GetSelect() domain.Item {
method focus (line 259) | func (ui *SelectUI) focus() {
method blur (line 263) | func (ui *SelectUI) blur() {
method ClearView (line 267) | func (ui *SelectUI) ClearView() {
method ClearSelected (line 272) | func (ui *SelectUI) ClearSelected() {
function NewSelectListUI (line 53) | func NewSelectListUI(uiKind UIKind, boxColor tcell.Color, setOpt SetSele...
FILE: ui/ui.go
type Primitive (line 12) | type Primitive interface
type ui (line 18) | type ui struct
method canFocus (line 39) | func (ui *ui) canFocus() bool {
method toNextUI (line 51) | func (ui *ui) toNextUI() {
method toPrevUI (line 66) | func (ui *ui) toPrevUI() {
method Modal (line 81) | func (ui *ui) Modal(p tview.Primitive, width, height int) tview.Primit...
method Message (line 88) | func (ui *ui) Message(msg string, focusFunc func()) {
method FullScreenPreview (line 99) | func (ui *ui) FullScreenPreview(contents string, focus func()) {
method Confirm (line 107) | func (ui *ui) Confirm(msg, doLabel string, doFunc func() error, focusF...
method Start (line 125) | func (ui *ui) Start() error {
function New (line 27) | func New() *ui {
FILE: ui/view.go
type ViewUI (line 17) | type ViewUI struct
method updateView (line 102) | func (ui *ViewUI) updateView(text string) {
method focus (line 113) | func (v *ViewUI) focus() {}
method blur (line 115) | func (v *ViewUI) blur() {}
function NewViewUI (line 26) | func NewViewUI(uiKind UIKind) {
FILE: utils/open.go
function Open (line 10) | func Open(url string) error {
FILE: utils/strings.go
function Replace (line 12) | func Replace(s, old, new string, n int) ([]string, string) {
FILE: utils/utils.go
function Edit (line 12) | func Edit(contents *string) error {
Condensed preview — 39 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (116K chars).
[
{
"path": "LICENSE",
"chars": 1076,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2020 skanehira\n\nPermission is hereby granted, free of charge, to any person obtaini"
},
{
"path": "README.md",
"chars": 4846,
"preview": "# github-tui\nThis is a TUI Client for GitHub.\n**Still Under Development**\n\nIf you are using Vim, you can use [gh.vim](ht"
},
{
"path": "cmd/ght/main.go",
"chars": 1994,
"preview": "package main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"log\"\n\t\"os/exec\"\n\t\"strings\"\n\n\t\"github.com/skanehira/ght/config\"\n\t\"github.com/skan"
},
{
"path": "config/config.go",
"chars": 1192,
"preview": "package config\n\nimport (\n\t\"io\"\n\t\"log\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/goccy/go-yaml\"\n)\n\ntype github struct {\n\tOwner"
},
{
"path": "domain/assignees.go",
"chars": 271,
"preview": "package domain\n\nimport \"github.com/gdamore/tcell/v2\"\n\ntype AssignableUser struct {\n\tLogin string\n}\n\nfunc (a *AssignableU"
},
{
"path": "domain/comment.go",
"chars": 380,
"preview": "package domain\n\nimport \"github.com/gdamore/tcell/v2\"\n\ntype Comment struct {\n\tID string\n\tAuthor string\n\tUpdated"
},
{
"path": "domain/error.go",
"chars": 214,
"preview": "package domain\n\nimport \"errors\"\n\nvar (\n\tErrCommentBodyIsEmpty = errors.New(\"comment body is empty\")\n\tErrNotFoundComment "
},
{
"path": "domain/issue.go",
"chars": 803,
"preview": "package domain\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/gdamore/tcell/v2\"\n)\n\ntype Issue struct {\n\tID string\n\tRepo stri"
},
{
"path": "domain/item.go",
"chars": 166,
"preview": "package domain\n\nimport \"github.com/gdamore/tcell/v2\"\n\ntype Item interface {\n\tKey() string\n\tFields() []Field\n}\n\ntype Fiel"
},
{
"path": "domain/label.go",
"chars": 272,
"preview": "package domain\n\nimport \"github.com/gdamore/tcell/v2\"\n\ntype Label struct {\n\tName string\n\tDescription string\n}\n\nfun"
},
{
"path": "domain/milestone.go",
"chars": 340,
"preview": "package domain\n\nimport \"github.com/gdamore/tcell/v2\"\n\ntype Milestone struct {\n\tID string\n\tTitle string\n\tS"
},
{
"path": "domain/project.go",
"chars": 264,
"preview": "package domain\n\nimport \"github.com/gdamore/tcell/v2\"\n\ntype Project struct {\n\tName string\n\tURL string\n}\n\nfunc (p *Projec"
},
{
"path": "github/client.go",
"chars": 4988,
"preview": "package github\n\nimport (\n\t\"context\"\n\n\t\"github.com/shurcooL/githubv4\"\n\t\"golang.org/x/oauth2\"\n)\n\nvar client *githubv4.Clie"
},
{
"path": "github/mutation_comment.go",
"chars": 359,
"preview": "package github\n\nimport \"github.com/shurcooL/githubv4\"\n\ntype MutateDeleteComment struct {\n\tDeleteIssueComment struct {\n\t\t"
},
{
"path": "github/mutation_issue.go",
"chars": 754,
"preview": "package github\n\nimport \"github.com/shurcooL/githubv4\"\n\ntype MutateOpenIsseue struct {\n\tReopenIssue struct {\n\t\tIssue stru"
},
{
"path": "github/query.go",
"chars": 139,
"preview": "package github\n\nimport \"github.com/shurcooL/githubv4\"\n\ntype PageInfo struct {\n\tEndCursor githubv4.String\n\tHasNextPage "
},
{
"path": "github/query_assignees.go",
"chars": 425,
"preview": "package github\n\nimport (\n\t\"github.com/shurcooL/githubv4\"\n\t\"github.com/skanehira/ght/domain\"\n)\n\ntype AssignableUser struc"
},
{
"path": "github/query_comment.go",
"chars": 551,
"preview": "package github\n\nimport (\n\t\"github.com/shurcooL/githubv4\"\n\t\"github.com/skanehira/ght/domain\"\n)\n\ntype Comment struct {\n\tID"
},
{
"path": "github/query_issue.go",
"chars": 2171,
"preview": "package github\n\nimport (\n\t\"reflect\"\n\t\"strconv\"\n\n\t\"github.com/shurcooL/githubv4\"\n\t\"github.com/skanehira/ght/domain\"\n)\n\nty"
},
{
"path": "github/query_label.go",
"chars": 447,
"preview": "package github\n\nimport (\n\t\"github.com/shurcooL/githubv4\"\n\t\"github.com/skanehira/ght/domain\"\n)\n\ntype Label struct {\n\tID "
},
{
"path": "github/query_milestone.go",
"chars": 599,
"preview": "package github\n\nimport (\n\t\"github.com/shurcooL/githubv4\"\n\t\"github.com/skanehira/ght/domain\"\n)\n\ntype Milestone struct {\n\t"
},
{
"path": "github/query_project.go",
"chars": 389,
"preview": "package github\n\nimport (\n\t\"github.com/shurcooL/githubv4\"\n\t\"github.com/skanehira/ght/domain\"\n)\n\ntype Project struct {\n\tID"
},
{
"path": "github/query_repository.go",
"chars": 508,
"preview": "package github\n\nimport \"github.com/shurcooL/githubv4\"\n\ntype Repository struct {\n\tID githubv4.ID\n\tName "
},
{
"path": "go.mod",
"chars": 750,
"preview": "module github.com/skanehira/ght\n\ngo 1.15\n\nrequire (\n\tgithub.com/atotto/clipboard v0.1.2\n\tgithub.com/davecgh/go-spew v1.1"
},
{
"path": "go.sum",
"chars": 43886,
"preview": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.34.0/go.mod h1"
},
{
"path": "ui/assignees.go",
"chars": 911,
"preview": "package ui\n\nimport (\n\t\"github.com/gdamore/tcell/v2\"\n)\n\nvar AssigneesUI *SelectUI\n\nfunc NewAssignableUI() {\n\t//getList :="
},
{
"path": "ui/comments.go",
"chars": 4984,
"preview": "package ui\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/gdamore/tcell/v2\"\n\t\"github.com/shurcooL/githubv4\""
},
{
"path": "ui/filter.go",
"chars": 725,
"preview": "package ui\n\nimport (\n\t\"github.com/gdamore/tcell/v2\"\n\t\"github.com/rivo/tview\"\n)\n\nvar IssueFilterUI *FilterUI\n\ntype (\n\tSet"
},
{
"path": "ui/issues.go",
"chars": 12799,
"preview": "package ui\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/atotto/clipboard\"\n\t\"github.com/gdamore/tcell"
},
{
"path": "ui/labels.go",
"chars": 992,
"preview": "package ui\n\nimport (\n\t\"github.com/gdamore/tcell/v2\"\n)\n\nvar LabelUI *SelectUI\n\nfunc NewLabelsUI() {\n\t//getList := func(cu"
},
{
"path": "ui/milestones.go",
"chars": 1458,
"preview": "package ui\n\nimport (\n\t\"log\"\n\n\t\"github.com/gdamore/tcell/v2\"\n\t\"github.com/skanehira/ght/domain\"\n\t\"github.com/skanehira/gh"
},
{
"path": "ui/projects.go",
"chars": 1431,
"preview": "package ui\n\nimport (\n\t\"log\"\n\n\t\"github.com/gdamore/tcell/v2\"\n\t\"github.com/skanehira/ght/domain\"\n\t\"github.com/skanehira/gh"
},
{
"path": "ui/search.go",
"chars": 646,
"preview": "package ui\n\nimport (\n\t\"github.com/gdamore/tcell/v2\"\n\t\"github.com/rivo/tview\"\n)\n\nvar SearchUI *searchUI\n\ntype SearchFunc "
},
{
"path": "ui/select.go",
"chars": 5826,
"preview": "package ui\n\nimport (\n\t\"strings\"\n\n\t\"github.com/gdamore/tcell/v2\"\n\t\"github.com/rivo/tview\"\n\t\"github.com/skanehira/ght/doma"
},
{
"path": "ui/ui.go",
"chars": 4570,
"preview": "package ui\n\nimport (\n\t\"github.com/gdamore/tcell/v2\"\n\t\"github.com/rivo/tview\"\n)\n\nvar (\n\tUI *ui\n)\n\ntype Primitive interfac"
},
{
"path": "ui/view.go",
"chars": 2454,
"preview": "package ui\n\nimport (\n\t\"strconv\"\n\n\t\"github.com/gdamore/tcell/v2\"\n\t\"github.com/rivo/tview\"\n\t\"github.com/skanehira/ght/util"
},
{
"path": "utils/open.go",
"chars": 486,
"preview": "package utils\n\nimport (\n\t\"errors\"\n\t\"os/exec\"\n\t\"runtime\"\n\t\"strings\"\n)\n\nfunc Open(url string) error {\n\targs := []string{}\n"
},
{
"path": "utils/strings.go",
"chars": 1033,
"preview": "package utils\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n\t\"unicode/utf8\"\n)\n\n// Replace is customized for this project\n// htt"
},
{
"path": "utils/utils.go",
"chars": 715,
"preview": "package utils\n\nimport (\n\t\"io\"\n\t\"io/ioutil\"\n\t\"log\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strings\"\n)\n\nfunc Edit(contents *string) error {\n\tf,"
}
]
About this extraction
This page contains the full source code of the skanehira/github-tui GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 39 files (104.3 KB), approximately 43.2k tokens, and a symbol index with 157 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.