mirror of
https://gitea.com/gitea/tea.git
synced 2025-09-02 09:58:29 +02:00
Refactor: apply new internal structurs (#206)
fix lint fix lint Move print TrackedTimesList to print package Move AbsPathWithExpansion to utils/path.go rename module intern to config Move Subcomands into it's own Packages Split times subcomands into own sourcefiles Split repos subcomands into own sourcefiles Split releases subcomands into own sourcefiles Split pulls subcomands into own sourcefiles Split milestones subcomands into own sourcefiles Split login subcomands into own sourcefiles Split labels subcomands into own sourcefiles split issues subcomands into own sourcefiles mv Move Interactive Login Creation to interact package Move Add Login function to intern/login.go apply from review lint: add description to exported func smal nits Move DetailViews stdout print func to print package Refactor: * Move Config & Login routines into intern package * rename global var in cmd * Move help func to utils Co-authored-by: 6543 <6543@obermui.de> Reviewed-on: https://gitea.com/gitea/tea/pulls/206 Reviewed-by: Norwin <noerw@noreply.gitea.io> Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
This commit is contained in:
160
modules/config/config.go
Normal file
160
modules/config/config.go
Normal file
@ -0,0 +1,160 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/tea/modules/git"
|
||||
"code.gitea.io/tea/modules/utils"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// LocalConfig represents local configurations
|
||||
type LocalConfig struct {
|
||||
Logins []Login `yaml:"logins"`
|
||||
}
|
||||
|
||||
var (
|
||||
// Config contain if loaded local tea config
|
||||
Config LocalConfig
|
||||
yamlConfigPath string
|
||||
)
|
||||
|
||||
// TODO: do not use init function to detect the tea configuration, use GetConfigPath()
|
||||
func init() {
|
||||
homeDir, err := utils.Home()
|
||||
if err != nil {
|
||||
log.Fatal("Retrieve home dir failed")
|
||||
}
|
||||
|
||||
dir := filepath.Join(homeDir, ".tea")
|
||||
err = os.MkdirAll(dir, os.ModePerm)
|
||||
if err != nil {
|
||||
log.Fatal("Init tea config dir " + dir + " failed")
|
||||
}
|
||||
|
||||
yamlConfigPath = filepath.Join(dir, "tea.yml")
|
||||
}
|
||||
|
||||
// GetConfigPath return path to tea config file
|
||||
func GetConfigPath() string {
|
||||
return yamlConfigPath
|
||||
}
|
||||
|
||||
// LoadConfig load config into global Config var
|
||||
func LoadConfig() error {
|
||||
ymlPath := GetConfigPath()
|
||||
exist, _ := utils.FileExist(ymlPath)
|
||||
if exist {
|
||||
fmt.Println("Found config file", ymlPath)
|
||||
bs, err := ioutil.ReadFile(ymlPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = yaml.Unmarshal(bs, &Config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SaveConfig save config from global Config var into config file
|
||||
func SaveConfig() error {
|
||||
ymlPath := GetConfigPath()
|
||||
bs, err := yaml.Marshal(Config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ioutil.WriteFile(ymlPath, bs, 0660)
|
||||
}
|
||||
|
||||
func curGitRepoPath(repoValue, remoteValue string) (*Login, string, error) {
|
||||
var err error
|
||||
var repo *git.TeaRepo
|
||||
if len(repoValue) == 0 {
|
||||
repo, err = git.RepoForWorkdir()
|
||||
} else {
|
||||
repo, err = git.RepoFromPath(repoValue)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
gitConfig, err := repo.Config()
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
// if no remote
|
||||
if len(gitConfig.Remotes) == 0 {
|
||||
return nil, "", errors.New("No remote(s) found in this Git repository")
|
||||
}
|
||||
|
||||
// if only one remote exists
|
||||
if len(gitConfig.Remotes) >= 1 && len(remoteValue) == 0 {
|
||||
for remote := range gitConfig.Remotes {
|
||||
remoteValue = remote
|
||||
}
|
||||
if len(gitConfig.Remotes) > 1 {
|
||||
// if master branch is present, use it as the default remote
|
||||
masterBranch, ok := gitConfig.Branches["master"]
|
||||
if ok {
|
||||
if len(masterBranch.Remote) > 0 {
|
||||
remoteValue = masterBranch.Remote
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
remoteConfig, ok := gitConfig.Remotes[remoteValue]
|
||||
if !ok || remoteConfig == nil {
|
||||
return nil, "", errors.New("Remote " + remoteValue + " not found in this Git repository")
|
||||
}
|
||||
|
||||
for _, l := range Config.Logins {
|
||||
for _, u := range remoteConfig.URLs {
|
||||
p, err := git.ParseURL(strings.TrimSpace(u))
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("Git remote URL parse failed: %s", err.Error())
|
||||
}
|
||||
if strings.EqualFold(p.Scheme, "http") || strings.EqualFold(p.Scheme, "https") {
|
||||
if strings.HasPrefix(u, l.URL) {
|
||||
ps := strings.Split(p.Path, "/")
|
||||
path := strings.Join(ps[len(ps)-2:], "/")
|
||||
return &l, strings.TrimSuffix(path, ".git"), nil
|
||||
}
|
||||
} else if strings.EqualFold(p.Scheme, "ssh") {
|
||||
if l.GetSSHHost() == strings.Split(p.Host, ":")[0] {
|
||||
return &l, strings.TrimLeft(strings.TrimSuffix(p.Path, ".git"), "/"), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, "", errors.New("No Gitea login found. You might want to specify --repo (and --login) to work outside of a repository")
|
||||
}
|
||||
|
||||
// GetOwnerAndRepo return repoOwner and repoName
|
||||
// based on relative path and default owner (if not in path)
|
||||
func GetOwnerAndRepo(repoPath, user string) (string, string) {
|
||||
if len(repoPath) == 0 {
|
||||
return "", ""
|
||||
}
|
||||
p := strings.Split(repoPath, "/")
|
||||
if len(p) >= 2 {
|
||||
return p[0], p[1]
|
||||
}
|
||||
return user, repoPath
|
||||
}
|
282
modules/config/login.go
Normal file
282
modules/config/login.go
Normal file
@ -0,0 +1,282 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/tea/modules/utils"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
)
|
||||
|
||||
// Login represents a login to a gitea server, you even could add multiple logins for one gitea server
|
||||
type Login struct {
|
||||
Name string `yaml:"name"`
|
||||
URL string `yaml:"url"`
|
||||
Token string `yaml:"token"`
|
||||
Default bool `yaml:"default"`
|
||||
SSHHost string `yaml:"ssh_host"`
|
||||
// optional path to the private key
|
||||
SSHKey string `yaml:"ssh_key"`
|
||||
Insecure bool `yaml:"insecure"`
|
||||
// optional gitea username
|
||||
User string `yaml:"user"`
|
||||
}
|
||||
|
||||
// Client returns a client to operate Gitea API
|
||||
func (l *Login) Client() *gitea.Client {
|
||||
httpClient := &http.Client{}
|
||||
if l.Insecure {
|
||||
cookieJar, _ := cookiejar.New(nil)
|
||||
|
||||
httpClient = &http.Client{
|
||||
Jar: cookieJar,
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}}
|
||||
}
|
||||
|
||||
client, err := gitea.NewClient(l.URL,
|
||||
gitea.SetToken(l.Token),
|
||||
gitea.SetHTTPClient(httpClient),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
||||
// GetSSHHost returns SSH host name
|
||||
func (l *Login) GetSSHHost() string {
|
||||
if l.SSHHost != "" {
|
||||
return l.SSHHost
|
||||
}
|
||||
|
||||
u, err := url.Parse(l.URL)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return u.Hostname()
|
||||
}
|
||||
|
||||
// GetDefaultLogin return the default login
|
||||
func GetDefaultLogin() (*Login, error) {
|
||||
if len(Config.Logins) == 0 {
|
||||
return nil, errors.New("No available login")
|
||||
}
|
||||
for _, l := range Config.Logins {
|
||||
if l.Default {
|
||||
return &l, nil
|
||||
}
|
||||
}
|
||||
|
||||
return &Config.Logins[0], nil
|
||||
}
|
||||
|
||||
// GetLoginByName get login by name
|
||||
func GetLoginByName(name string) *Login {
|
||||
for _, l := range Config.Logins {
|
||||
if l.Name == name {
|
||||
return &l
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddLogin add login to config ( global var & file)
|
||||
func AddLogin(name, token, user, passwd, sshKey, giteaURL string, insecure bool) error {
|
||||
|
||||
if len(giteaURL) == 0 {
|
||||
log.Fatal("You have to input Gitea server URL")
|
||||
}
|
||||
if len(token) == 0 && (len(user)+len(passwd)) == 0 {
|
||||
log.Fatal("No token set")
|
||||
} else if len(user) != 0 && len(passwd) == 0 {
|
||||
log.Fatal("No password set")
|
||||
} else if len(user) == 0 && len(passwd) != 0 {
|
||||
log.Fatal("No user set")
|
||||
}
|
||||
|
||||
err := LoadConfig()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
httpClient := &http.Client{}
|
||||
if insecure {
|
||||
cookieJar, _ := cookiejar.New(nil)
|
||||
httpClient = &http.Client{
|
||||
Jar: cookieJar,
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}}
|
||||
}
|
||||
client, err := gitea.NewClient(giteaURL,
|
||||
gitea.SetToken(token),
|
||||
gitea.SetBasicAuth(user, passwd),
|
||||
gitea.SetHTTPClient(httpClient),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
u, _, err := client.GetMyUserInfo()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if len(token) == 0 {
|
||||
// create token
|
||||
host, _ := os.Hostname()
|
||||
tl, _, err := client.ListAccessTokens(gitea.ListAccessTokensOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tokenName := host + "-tea"
|
||||
for i := range tl {
|
||||
if tl[i].Name == tokenName {
|
||||
tokenName += time.Now().Format("2006-01-02_15-04-05")
|
||||
break
|
||||
}
|
||||
}
|
||||
t, _, err := client.CreateAccessToken(gitea.CreateAccessTokenOption{Name: tokenName})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
token = t.Token
|
||||
}
|
||||
|
||||
fmt.Println("Login successful! Login name " + u.UserName)
|
||||
|
||||
if len(name) == 0 {
|
||||
parsedURL, err := url.Parse(giteaURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
name = strings.ReplaceAll(strings.Title(parsedURL.Host), ".", "")
|
||||
for _, l := range Config.Logins {
|
||||
if l.Name == name {
|
||||
name += "_" + u.UserName
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = addLoginToConfig(Login{
|
||||
Name: name,
|
||||
URL: giteaURL,
|
||||
Token: token,
|
||||
Insecure: insecure,
|
||||
SSHKey: sshKey,
|
||||
User: u.UserName,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = SaveConfig()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// addLoginToConfig add a login to global Config var
|
||||
func addLoginToConfig(login Login) error {
|
||||
for _, l := range Config.Logins {
|
||||
if l.Name == login.Name {
|
||||
if l.URL == login.URL && l.Token == login.Token {
|
||||
return nil
|
||||
}
|
||||
return errors.New("Login name has already been used")
|
||||
}
|
||||
if l.URL == login.URL && l.Token == login.Token {
|
||||
return errors.New("URL has been added")
|
||||
}
|
||||
}
|
||||
|
||||
u, err := url.Parse(login.URL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if login.SSHHost == "" {
|
||||
login.SSHHost = u.Hostname()
|
||||
}
|
||||
Config.Logins = append(Config.Logins, login)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// InitCommand returns repository and *Login based on flags
|
||||
func InitCommand(repoValue, loginValue, remoteValue string) (*Login, string, string) {
|
||||
var login *Login
|
||||
|
||||
err := LoadConfig()
|
||||
if err != nil {
|
||||
log.Fatal("load config file failed ", yamlConfigPath)
|
||||
}
|
||||
|
||||
if login, err = GetDefaultLogin(); err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
|
||||
exist, err := utils.PathExists(repoValue)
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
|
||||
if exist || len(repoValue) == 0 {
|
||||
login, repoValue, err = curGitRepoPath(repoValue, remoteValue)
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if loginValue != "" {
|
||||
login = GetLoginByName(loginValue)
|
||||
if login == nil {
|
||||
log.Fatal("Login name " + loginValue + " does not exist")
|
||||
}
|
||||
}
|
||||
|
||||
owner, repo := GetOwnerAndRepo(repoValue, login.User)
|
||||
return login, owner, repo
|
||||
}
|
||||
|
||||
// InitCommandLoginOnly return *Login based on flags
|
||||
func InitCommandLoginOnly(loginValue string) *Login {
|
||||
err := LoadConfig()
|
||||
if err != nil {
|
||||
log.Fatal("load config file failed ", yamlConfigPath)
|
||||
}
|
||||
|
||||
var login *Login
|
||||
if loginValue == "" {
|
||||
login, err = GetDefaultLogin()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
} else {
|
||||
login = GetLoginByName(loginValue)
|
||||
if login == nil {
|
||||
log.Fatal("Login name " + loginValue + " does not exist")
|
||||
}
|
||||
}
|
||||
|
||||
return login
|
||||
}
|
@ -10,10 +10,10 @@ import (
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/tea/modules/utils"
|
||||
|
||||
git_transport "github.com/go-git/go-git/v5/plumbing/transport"
|
||||
gogit_http "github.com/go-git/go-git/v5/plumbing/transport/http"
|
||||
gogit_ssh "github.com/go-git/go-git/v5/plumbing/transport/ssh"
|
||||
@ -67,9 +67,9 @@ func GetAuthForURL(remoteURL *url.URL, httpUser, keyFile string) (auth git_trans
|
||||
|
||||
func readSSHPrivKey(keyFile string) (sig ssh.Signer, err error) {
|
||||
if keyFile != "" {
|
||||
keyFile, err = absPathWithExpansion(keyFile)
|
||||
keyFile, err = utils.AbsPathWithExpansion(keyFile)
|
||||
} else {
|
||||
keyFile, err = absPathWithExpansion("~/.ssh/id_rsa")
|
||||
keyFile, err = utils.AbsPathWithExpansion("~/.ssh/id_rsa")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -104,17 +104,3 @@ func promptPass(domain string) (string, error) {
|
||||
pass, err := terminal.ReadPassword(0)
|
||||
return string(pass), err
|
||||
}
|
||||
|
||||
func absPathWithExpansion(p string) (string, error) {
|
||||
u, err := user.Current()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if p == "~" {
|
||||
return u.HomeDir, nil
|
||||
} else if strings.HasPrefix(p, "~/") {
|
||||
return filepath.Join(u.HomeDir, p[2:]), nil
|
||||
} else {
|
||||
return filepath.Abs(p)
|
||||
}
|
||||
}
|
||||
|
87
modules/interact/login.go
Normal file
87
modules/interact/login.go
Normal file
@ -0,0 +1,87 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package interact
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/tea/modules/config"
|
||||
)
|
||||
|
||||
// CreateLogin create an login interactive
|
||||
func CreateLogin() error {
|
||||
var stdin, name, token, user, passwd, sshKey, giteaURL string
|
||||
var insecure = false
|
||||
|
||||
fmt.Print("URL of Gitea instance: ")
|
||||
if _, err := fmt.Scanln(&stdin); err != nil {
|
||||
stdin = ""
|
||||
}
|
||||
giteaURL = strings.TrimSpace(stdin)
|
||||
if len(giteaURL) == 0 {
|
||||
fmt.Println("URL is required!")
|
||||
return nil
|
||||
}
|
||||
|
||||
parsedURL, err := url.Parse(giteaURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
name = strings.ReplaceAll(strings.Title(parsedURL.Host), ".", "")
|
||||
|
||||
fmt.Print("Name of new Login [" + name + "]: ")
|
||||
if _, err := fmt.Scanln(&stdin); err != nil {
|
||||
stdin = ""
|
||||
}
|
||||
if len(strings.TrimSpace(stdin)) != 0 {
|
||||
name = strings.TrimSpace(stdin)
|
||||
}
|
||||
|
||||
fmt.Print("Do you have a token [Yes/no]: ")
|
||||
if _, err := fmt.Scanln(&stdin); err != nil {
|
||||
stdin = ""
|
||||
}
|
||||
if len(stdin) != 0 && strings.ToLower(stdin[:1]) == "n" {
|
||||
fmt.Print("Username: ")
|
||||
if _, err := fmt.Scanln(&stdin); err != nil {
|
||||
stdin = ""
|
||||
}
|
||||
user = strings.TrimSpace(stdin)
|
||||
|
||||
fmt.Print("Password: ")
|
||||
if _, err := fmt.Scanln(&stdin); err != nil {
|
||||
stdin = ""
|
||||
}
|
||||
passwd = strings.TrimSpace(stdin)
|
||||
} else {
|
||||
fmt.Print("Token: ")
|
||||
if _, err := fmt.Scanln(&stdin); err != nil {
|
||||
stdin = ""
|
||||
}
|
||||
token = strings.TrimSpace(stdin)
|
||||
}
|
||||
|
||||
fmt.Print("Set Optional settings [yes/No]: ")
|
||||
if _, err := fmt.Scanln(&stdin); err != nil {
|
||||
stdin = ""
|
||||
}
|
||||
if len(stdin) != 0 && strings.ToLower(stdin[:1]) == "y" {
|
||||
fmt.Print("SSH Key Path: ")
|
||||
if _, err := fmt.Scanln(&stdin); err != nil {
|
||||
stdin = ""
|
||||
}
|
||||
sshKey = strings.TrimSpace(stdin)
|
||||
|
||||
fmt.Print("Allow Insecure connections [yes/No]: ")
|
||||
if _, err := fmt.Scanln(&stdin); err != nil {
|
||||
stdin = ""
|
||||
}
|
||||
insecure = len(stdin) != 0 && strings.ToLower(stdin[:1]) == "y"
|
||||
}
|
||||
|
||||
return config.AddLogin(name, token, user, passwd, sshKey, giteaURL, insecure)
|
||||
}
|
31
modules/print/issue.go
Normal file
31
modules/print/issue.go
Normal file
@ -0,0 +1,31 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package print
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
"github.com/charmbracelet/glamour"
|
||||
)
|
||||
|
||||
// IssueDetails print an issue rendered to stdout
|
||||
func IssueDetails(issue *gitea.Issue) {
|
||||
|
||||
in := fmt.Sprintf("# #%d %s (%s)\n%s created %s\n\n%s\n", issue.Index,
|
||||
issue.Title,
|
||||
issue.State,
|
||||
issue.Poster.UserName,
|
||||
issue.Created.Format("2006-01-02 15:04:05"),
|
||||
issue.Body,
|
||||
)
|
||||
out, err := glamour.Render(in, getGlamourTheme())
|
||||
if err != nil {
|
||||
// TODO: better Error handling
|
||||
fmt.Printf("Error:\n%v\n\n", err)
|
||||
return
|
||||
}
|
||||
fmt.Print(out)
|
||||
}
|
92
modules/print/list.go
Normal file
92
modules/print/list.go
Normal file
@ -0,0 +1,92 @@
|
||||
// Copyright 2018 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package print
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/olekukonko/tablewriter"
|
||||
)
|
||||
|
||||
var (
|
||||
showLog bool
|
||||
)
|
||||
|
||||
// errorf printf content as an error information
|
||||
func errorf(format string, a ...interface{}) {
|
||||
fmt.Printf(format, a...)
|
||||
}
|
||||
|
||||
// outputtable prints structured data as table
|
||||
func outputtable(headers []string, values [][]string) {
|
||||
table := tablewriter.NewWriter(os.Stdout)
|
||||
if len(headers) > 0 {
|
||||
table.SetHeader(headers)
|
||||
}
|
||||
for _, value := range values {
|
||||
table.Append(value)
|
||||
}
|
||||
table.Render()
|
||||
}
|
||||
|
||||
// outputsimple prints structured data as space delimited value
|
||||
func outputsimple(headers []string, values [][]string) {
|
||||
for _, value := range values {
|
||||
fmt.Printf(strings.Join(value, " "))
|
||||
fmt.Printf("\n")
|
||||
}
|
||||
}
|
||||
|
||||
// outputdsv prints structured data as delimiter separated value format
|
||||
func outputdsv(headers []string, values [][]string, delimiterOpt ...string) {
|
||||
delimiter := ","
|
||||
if len(delimiterOpt) > 0 {
|
||||
delimiter = delimiterOpt[0]
|
||||
}
|
||||
fmt.Println("\"" + strings.Join(headers, "\""+delimiter+"\"") + "\"")
|
||||
for _, value := range values {
|
||||
fmt.Printf("\"")
|
||||
fmt.Printf(strings.Join(value, "\""+delimiter+"\""))
|
||||
fmt.Printf("\"")
|
||||
fmt.Printf("\n")
|
||||
}
|
||||
}
|
||||
|
||||
// outputyaml prints structured data as yaml
|
||||
func outputyaml(headers []string, values [][]string) {
|
||||
for _, value := range values {
|
||||
fmt.Println("-")
|
||||
for j, val := range value {
|
||||
intVal, _ := strconv.Atoi(val)
|
||||
if strconv.Itoa(intVal) == val {
|
||||
fmt.Printf(" %s: %s\n", headers[j], val)
|
||||
} else {
|
||||
fmt.Printf(" %s: '%s'\n", headers[j], val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// OutputList provides general function to convert given list of items
|
||||
// into several outputs (table, csv, simple, tsv, yaml)
|
||||
func OutputList(output string, headers []string, values [][]string) {
|
||||
switch {
|
||||
case output == "" || output == "table":
|
||||
outputtable(headers, values)
|
||||
case output == "csv":
|
||||
outputdsv(headers, values, ",")
|
||||
case output == "simple":
|
||||
outputsimple(headers, values)
|
||||
case output == "tsv":
|
||||
outputdsv(headers, values, "\t")
|
||||
case output == "yaml":
|
||||
outputyaml(headers, values)
|
||||
default:
|
||||
errorf("unknown output type '" + output + "', available types are:\n- csv: comma-separated values\n- simple: space-separated values\n- table: auto-aligned table format (default)\n- tsv: tab-separated values\n- yaml: YAML format\n")
|
||||
}
|
||||
}
|
24
modules/print/milestone.go
Normal file
24
modules/print/milestone.go
Normal file
@ -0,0 +1,24 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package print
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
)
|
||||
|
||||
// MilestoneDetails print an milestone formatted to stdout
|
||||
func MilestoneDetails(milestone *gitea.Milestone) {
|
||||
fmt.Printf("%s\n",
|
||||
milestone.Title,
|
||||
)
|
||||
if len(milestone.Description) != 0 {
|
||||
fmt.Printf("\n%s\n", milestone.Description)
|
||||
}
|
||||
if milestone.Deadline != nil && !milestone.Deadline.IsZero() {
|
||||
fmt.Printf("\nDeadline: %s\n", milestone.Deadline.Format("2006-01-02 15:04:05"))
|
||||
}
|
||||
}
|
@ -2,12 +2,23 @@
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package utils
|
||||
package print
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
// FormatSize get kb in int and return string
|
||||
func FormatSize(kb int64) string {
|
||||
"github.com/muesli/termenv"
|
||||
)
|
||||
|
||||
func getGlamourTheme() string {
|
||||
if termenv.HasDarkBackground() {
|
||||
return "dark"
|
||||
}
|
||||
return "light"
|
||||
}
|
||||
|
||||
// formatSize get kb in int and return string
|
||||
func formatSize(kb int64) string {
|
||||
if kb < 1024 {
|
||||
return fmt.Sprintf("%d Kb", kb)
|
||||
}
|
31
modules/print/pull.go
Normal file
31
modules/print/pull.go
Normal file
@ -0,0 +1,31 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package print
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
"github.com/charmbracelet/glamour"
|
||||
)
|
||||
|
||||
// PullDetails print an pull rendered to stdout
|
||||
func PullDetails(pr *gitea.PullRequest) {
|
||||
|
||||
in := fmt.Sprintf("# #%d %s (%s)\n%s created %s\n\n%s\n", pr.Index,
|
||||
pr.Title,
|
||||
pr.State,
|
||||
pr.Poster.UserName,
|
||||
pr.Created.Format("2006-01-02 15:04:05"),
|
||||
pr.Body,
|
||||
)
|
||||
out, err := glamour.Render(in, getGlamourTheme())
|
||||
if err != nil {
|
||||
// TODO: better Error handling
|
||||
fmt.Printf("Error:\n%v\n\n", err)
|
||||
return
|
||||
}
|
||||
fmt.Print(out)
|
||||
}
|
44
modules/print/repo.go
Normal file
44
modules/print/repo.go
Normal file
@ -0,0 +1,44 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package print
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
)
|
||||
|
||||
// RepoDetails print an repo formatted to stdout
|
||||
func RepoDetails(repo *gitea.Repository, topics []string) {
|
||||
output := repo.FullName
|
||||
if repo.Mirror {
|
||||
output += " (mirror)"
|
||||
}
|
||||
if repo.Fork {
|
||||
output += " (fork)"
|
||||
}
|
||||
if repo.Archived {
|
||||
output += " (archived)"
|
||||
}
|
||||
if repo.Empty {
|
||||
output += " (empty)"
|
||||
}
|
||||
output += "\n"
|
||||
if len(topics) != 0 {
|
||||
output += "Topics: " + strings.Join(topics, ", ") + "\n"
|
||||
}
|
||||
output += "\n"
|
||||
output += repo.Description + "\n\n"
|
||||
output += fmt.Sprintf(
|
||||
"Open Issues: %d, Stars: %d, Forks: %d, Size: %s\n\n",
|
||||
repo.OpenIssues,
|
||||
repo.Stars,
|
||||
repo.Forks,
|
||||
formatSize(int64(repo.Size)),
|
||||
)
|
||||
|
||||
fmt.Print(output)
|
||||
}
|
69
modules/print/times.go
Normal file
69
modules/print/times.go
Normal file
@ -0,0 +1,69 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package print
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/sdk/gitea"
|
||||
)
|
||||
|
||||
func formatDuration(seconds int64, outputType string) string {
|
||||
switch outputType {
|
||||
case "yaml":
|
||||
case "csv":
|
||||
return fmt.Sprint(seconds)
|
||||
}
|
||||
return time.Duration(1e9 * seconds).String()
|
||||
}
|
||||
|
||||
// TrackedTimesList print list of tracked times to stdout
|
||||
func TrackedTimesList(times []*gitea.TrackedTime, outputType string, from, until time.Time, printTotal bool) {
|
||||
var outputValues [][]string
|
||||
var totalDuration int64
|
||||
|
||||
localLoc, err := time.LoadLocation("Local") // local timezone for time formatting
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for _, t := range times {
|
||||
if !from.IsZero() && from.After(t.Created) {
|
||||
continue
|
||||
}
|
||||
if !until.IsZero() && until.Before(t.Created) {
|
||||
continue
|
||||
}
|
||||
|
||||
totalDuration += t.Time
|
||||
|
||||
outputValues = append(
|
||||
outputValues,
|
||||
[]string{
|
||||
t.Created.In(localLoc).Format("2006-01-02 15:04:05"),
|
||||
"#" + strconv.FormatInt(t.Issue.Index, 10),
|
||||
t.UserName,
|
||||
formatDuration(t.Time, outputType),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if printTotal {
|
||||
outputValues = append(outputValues, []string{
|
||||
"TOTAL", "", "", formatDuration(totalDuration, outputType),
|
||||
})
|
||||
}
|
||||
|
||||
headers := []string{
|
||||
"Created",
|
||||
"Issue",
|
||||
"User",
|
||||
"Duration",
|
||||
}
|
||||
OutputList(outputType, headers, outputValues)
|
||||
}
|
18
modules/utils/parse.go
Normal file
18
modules/utils/parse.go
Normal file
@ -0,0 +1,18 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ArgToIndex take issue/pull index as string and return int64
|
||||
func ArgToIndex(arg string) (int64, error) {
|
||||
if strings.HasPrefix(arg, "#") {
|
||||
arg = arg[1:]
|
||||
}
|
||||
return strconv.ParseInt(arg, 10, 64)
|
||||
}
|
@ -5,7 +5,11 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// PathExists returns whether the given file or directory exists or not
|
||||
@ -19,3 +23,33 @@ func PathExists(path string) (bool, error) {
|
||||
}
|
||||
return true, err
|
||||
}
|
||||
|
||||
// FileExist returns whether the given file exists or not
|
||||
func FileExist(fileName string) (bool, error) {
|
||||
f, err := os.Stat(fileName)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
if f.IsDir() {
|
||||
return false, errors.New("A directory with the same name exists")
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// AbsPathWithExpansion expand path beginning with "~/" to absolute path
|
||||
func AbsPathWithExpansion(p string) (string, error) {
|
||||
u, err := user.Current()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if p == "~" {
|
||||
return u.HomeDir, nil
|
||||
} else if strings.HasPrefix(p, "~/") {
|
||||
return filepath.Join(u.HomeDir, p[2:]), nil
|
||||
} else {
|
||||
return filepath.Abs(p)
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user