2023-11-28 04:05:23 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"errors"
|
|
|
|
"flag"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"os"
|
|
|
|
"os/user"
|
|
|
|
"path/filepath"
|
|
|
|
"regexp"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/adrg/xdg"
|
2023-12-15 02:14:31 +00:00
|
|
|
"github.com/charmbracelet/huh"
|
2023-11-28 04:05:23 +00:00
|
|
|
"github.com/go-git/go-git/v5"
|
|
|
|
"github.com/go-git/go-git/v5/config"
|
|
|
|
"github.com/go-git/go-git/v5/plumbing/transport/http"
|
|
|
|
"github.com/peterbourgon/ff/v3"
|
|
|
|
"github.com/peterbourgon/ff/v3/ffyaml"
|
|
|
|
)
|
|
|
|
|
|
|
|
type args struct {
|
|
|
|
username string
|
|
|
|
password string
|
|
|
|
passwordFile string
|
|
|
|
domain string
|
|
|
|
cli []string
|
|
|
|
}
|
|
|
|
|
|
|
|
func defaultConfig() string {
|
|
|
|
def, err := xdg.ConfigFile("gist/config.yaml")
|
|
|
|
if err != nil {
|
|
|
|
return "config.yaml"
|
|
|
|
}
|
|
|
|
return def
|
|
|
|
}
|
|
|
|
|
|
|
|
func defaultUsername() string {
|
|
|
|
u, err := user.Current()
|
|
|
|
if err != nil {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
return u.Username
|
|
|
|
}
|
|
|
|
|
|
|
|
func parseArgs(args []string) (a args, e error) {
|
|
|
|
fs := flag.NewFlagSet("gist", flag.ExitOnError)
|
|
|
|
fs.String("config", defaultConfig(), "Path to config file")
|
|
|
|
fs.StringVar(&a.username, "username", defaultUsername(), "opengist username")
|
|
|
|
fs.StringVar(&a.username, "u", a.username, "--username")
|
|
|
|
fs.StringVar(&a.password, "password", "", "opengist password")
|
|
|
|
fs.StringVar(&a.password, "p", a.password, "--password")
|
|
|
|
fs.StringVar(&a.passwordFile, "password-file", "", "Path to a file containing the opengist password")
|
|
|
|
fs.StringVar(&a.passwordFile, "f", a.passwordFile, "--password-file")
|
|
|
|
fs.StringVar(&a.domain, "domain", "", "opengist domain")
|
|
|
|
fs.StringVar(&a.domain, "d", a.domain, "--domain")
|
|
|
|
if err := ff.Parse(fs, args,
|
|
|
|
ff.WithConfigFileFlag("config"),
|
|
|
|
ff.WithAllowMissingConfigFile(true),
|
|
|
|
ff.WithConfigFileParser(ffyaml.Parser),
|
|
|
|
ff.WithEnvVarPrefix("GIST"),
|
|
|
|
); err != nil {
|
|
|
|
return a, err
|
|
|
|
}
|
|
|
|
a.cli = fs.Args()
|
|
|
|
return a, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func copyFile(src, dest string) error {
|
|
|
|
fi, err := os.Lstat(src)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
srcFi, err := os.Open(src)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("could not open src: %w", err)
|
|
|
|
}
|
|
|
|
defer srcFi.Close()
|
|
|
|
|
|
|
|
destFi, err := os.Create(dest)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("could not create dest: %w", err)
|
|
|
|
}
|
|
|
|
defer destFi.Close()
|
|
|
|
|
|
|
|
if err := os.Chmod(dest, fi.Mode()); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, err := io.Copy(destFi, srcFi); err != nil {
|
|
|
|
return fmt.Errorf("could not copy %s to %s: %w", src, dest, err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func maine() error {
|
|
|
|
args, err := parseArgs(os.Args[1:])
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("could not parse args: %w", err)
|
|
|
|
}
|
|
|
|
|
2023-12-15 02:14:31 +00:00
|
|
|
required := func(s string) error {
|
|
|
|
if strings.TrimSpace(s) == "" {
|
|
|
|
return errors.New("value is required")
|
2023-11-28 04:05:23 +00:00
|
|
|
}
|
2023-12-15 02:14:31 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var prompts []huh.Field
|
|
|
|
if args.username == "" {
|
|
|
|
prompts = append(prompts,
|
|
|
|
huh.NewInput().
|
|
|
|
Title("Opengist username").
|
|
|
|
Validate(required).
|
|
|
|
Value(&args.username),
|
|
|
|
)
|
2023-11-28 04:05:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if args.password == "" && args.passwordFile == "" {
|
2023-12-15 02:14:31 +00:00
|
|
|
prompts = append(prompts,
|
|
|
|
huh.NewInput().
|
|
|
|
Title("Opengist password").
|
|
|
|
Password(true).
|
|
|
|
Validate(required).
|
|
|
|
Value(&args.password),
|
|
|
|
)
|
2023-11-28 04:05:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if args.domain == "" {
|
2023-12-15 02:14:31 +00:00
|
|
|
prompts = append(prompts,
|
|
|
|
huh.NewInput().
|
|
|
|
Title("Opengist domain").
|
|
|
|
Validate(required).
|
|
|
|
Value(&args.domain),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(prompts) > 0 {
|
|
|
|
if err := huh.NewForm(huh.NewGroup(prompts...)).
|
|
|
|
WithTheme(huh.ThemeCatppuccin()).
|
|
|
|
Run(); err != nil {
|
|
|
|
return fmt.Errorf("missing fields are required: %w", err)
|
2023-11-28 04:05:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if args.passwordFile != "" {
|
|
|
|
b, err := os.ReadFile(args.passwordFile)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("could not read opengist password file: %w", err)
|
|
|
|
}
|
|
|
|
args.password = strings.TrimSpace(string(b))
|
|
|
|
}
|
|
|
|
|
|
|
|
tmp, err := os.MkdirTemp(os.TempDir(), "gist*")
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("could not make temp dir: %w", err)
|
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
if err := os.RemoveAll(tmp); err != nil {
|
|
|
|
fmt.Printf("could not clean up temp dir at %q: %v\n", tmp, err)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
repo, err := git.PlainInit(tmp, false)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("could not init git repo: %w", err)
|
|
|
|
}
|
|
|
|
if err := repo.CreateBranch(&config.Branch{
|
|
|
|
Name: "main",
|
|
|
|
}); err != nil {
|
|
|
|
return fmt.Errorf("could not create main branch: %w", err)
|
|
|
|
}
|
|
|
|
if _, err := repo.CreateRemote(&config.RemoteConfig{
|
|
|
|
Name: "origin",
|
|
|
|
URLs: []string{fmt.Sprintf("https://%s/init", args.domain)},
|
|
|
|
}); err != nil {
|
|
|
|
return fmt.Errorf("could not create origin remote: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, fp := range args.cli {
|
|
|
|
glob, err := filepath.Glob(fp)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("invalid glob: %w", err)
|
|
|
|
}
|
|
|
|
for _, g := range glob {
|
|
|
|
if err := copyFile(g, filepath.Join(tmp, filepath.Base(g))); err != nil {
|
|
|
|
return fmt.Errorf("could not copy file: %w", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
tree, err := repo.Worktree()
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("could not get git worktree: %w", err)
|
|
|
|
}
|
|
|
|
if err := tree.AddGlob("."); err != nil {
|
|
|
|
return fmt.Errorf("could not add all files to staging area: %w", err)
|
|
|
|
}
|
|
|
|
if _, err := tree.Commit("gist cli", &git.CommitOptions{}); err != nil {
|
|
|
|
return fmt.Errorf("could not commit files: %w", err)
|
|
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
|
|
if err := repo.Push(&git.PushOptions{
|
|
|
|
Auth: &http.BasicAuth{
|
|
|
|
Username: args.username,
|
|
|
|
Password: args.password,
|
|
|
|
},
|
|
|
|
Progress: &buf,
|
|
|
|
}); err != nil {
|
|
|
|
return fmt.Errorf("could not push to remote: %w", err)
|
|
|
|
}
|
|
|
|
re := regexp.MustCompile(fmt.Sprintf(`https://%s/%s/\w+`, args.domain, args.username))
|
|
|
|
u := re.FindString(buf.String())
|
|
|
|
// FIXME Progress is currently broken (?) for Push, this can be removed once fixed
|
|
|
|
u = fmt.Sprintf("https://%s/all", args.domain)
|
|
|
|
if u == "" {
|
|
|
|
return errors.New("no gist URL found")
|
|
|
|
}
|
|
|
|
fmt.Println(u)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
if err := maine(); err != nil {
|
|
|
|
fmt.Println(err)
|
|
|
|
}
|
|
|
|
}
|