1
0
Fork 0
gist/main.go

232 lines
5.4 KiB
Go

package main
import (
"bytes"
"errors"
"flag"
"fmt"
"io"
"os"
"os/user"
"path/filepath"
"regexp"
"strings"
"github.com/adrg/xdg"
"github.com/charmbracelet/huh"
"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)
}
required := func(s string) error {
if strings.TrimSpace(s) == "" {
return errors.New("value is required")
}
return nil
}
var prompts []huh.Field
if args.username == "" {
prompts = append(prompts,
huh.NewInput().
Title("Opengist username").
Validate(required).
Value(&args.username),
)
}
if args.password == "" && args.passwordFile == "" {
prompts = append(prompts,
huh.NewInput().
Title("Opengist password").
Password(true).
Validate(required).
Value(&args.password),
)
}
if args.domain == "" {
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)
}
}
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)
}
}