commit b2141dda0f4f244f6c73e4366e43fdd7c2d7ca69
Author: hhvn <dev@hhvn.uk>
Date: Tue, 16 Apr 2024 22:11:37 +0100
Init - it works well enough
Diffstat:
A | .gitignore | | | 1 | + |
A | config/config.go | | | 25 | +++++++++++++++++++++++++ |
A | config/type.go | | | 25 | +++++++++++++++++++++++++ |
A | fn.go | | | 196 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | git.go | | | 234 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | go.mod | | | 29 | +++++++++++++++++++++++++++++ |
A | go.sum | | | 121 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | main.go | | | 152 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | smallify.go | | | 38 | ++++++++++++++++++++++++++++++++++++++ |
A | smallify_test.go | | | 24 | ++++++++++++++++++++++++ |
A | tput/tput.go | | | 37 | +++++++++++++++++++++++++++++++++++++ |
11 files changed, 882 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1 @@
+prompt
diff --git a/config/config.go b/config/config.go
@@ -0,0 +1,25 @@
+package config
+
+var Colours = colours{
+ Success: 15,
+ Fail: 10,
+ Pipe: 3,
+ Username: 5,
+ Branch: 3,
+ Unstaged: 8,
+ Staged: 15,
+ Unpushed: 14,
+ Dir: 7,
+ Dollar: 7,
+}
+
+var Settings = settings{
+ Smallpipe: true,
+
+ Smalluser: true,
+
+ Smallbranch: true,
+ Uniqbranch: true,
+
+ Smalldiff: true,
+}
diff --git a/config/type.go b/config/type.go
@@ -0,0 +1,25 @@
+package config
+
+type colours struct {
+ Success int // Successful returns
+ Fail int // Failed returns
+ Pipe int // | symbol seperating returns
+ Username int // duh
+ Branch int // duh
+ Unstaged int // Unstaged changes, or all changes if smalldiff is set
+ Staged int // Staged changes, can be overridden by smalldiff
+ Unpushed int // Unpushed commits
+ Dir int // duh
+ Dollar int // The $ or # symbol at the end of the prompt
+}
+
+type settings struct {
+ Smallpipe bool // Collapse a successful pipeline to one element
+
+ Smalluser bool // Remove vowels from username
+
+ Smallbranch bool // Remove vowels from branch
+ Uniqbranch bool // Only do ^ when the result is unique from all other branches
+
+ Smalldiff bool // Collapse the number of changes into one number
+}
diff --git a/fn.go b/fn.go
@@ -0,0 +1,196 @@
+package main
+
+import (
+ "os"
+ "os/user"
+ "fmt"
+ "strings"
+
+ "hhvn.uk/prompt/tput"
+)
+
+type Fn func(ch chan string)
+
+func FnExitStatus(ch chan string) {
+ defer close(ch)
+
+ status := os.Args[1:]
+
+ if cnf.Smallpipe {
+ allzero := true
+ for _, v := range status {
+ if v != "0" {
+ allzero = false
+ break
+ }
+ }
+
+ if allzero {
+ status = status[:1]
+ }
+ }
+
+ for i, v := range status {
+ if v == "0" {
+ ch <- tput.Fg(col.Success)
+ } else {
+ ch <- tput.Fg(col.Fail)
+ }
+
+ ch <- v
+
+ if i < len(status) - 1 {
+ ch <- tput.Fg(col.Pipe)
+ ch <- "|"
+ }
+ }
+}
+
+func FnUsername(ch chan string) {
+ defer close(ch)
+
+ ch <- tput.Fg(col.Username)
+
+ user, err := user.Current()
+ if err != nil {
+ Err(err)
+ return
+ }
+
+ usr := user.Username
+
+ if cnf.Smalluser {
+ ch <- Smalltxt(usr)
+ } else {
+ ch <- usr
+ }
+}
+
+// Git branch/HEAD
+func FnBranch(ch chan string) {
+ defer close(ch)
+
+ ch <- tput.Fg(col.Branch)
+
+ brch, err := GitHEAD()
+ if err != nil {
+ Err(Ignore(err, GitErrIgns...))
+ return
+ }
+
+ ch <- brch
+}
+
+func FnDiff(ch chan string) {
+ defer close(ch)
+
+ m, a, d, s, err := GitDiff()
+ if err != nil {
+ Err(Ignore(err, GitErrIgns...))
+ return
+ }
+
+ total := m + a + d + s
+
+ if total == 0 {
+ return
+ }
+
+ if cnf.Smalldiff {
+ if m + a + d == 0 && s > 0 {
+ ch <- tput.Fg(col.Staged)
+ } else {
+ ch <- tput.Fg(col.Unstaged)
+ }
+
+ ch <- fmt.Sprintf("%d", total)
+
+ if m > 0 { ch <- "m" }
+ if a > 0 { ch <- "+" }
+ if d > 0 { ch <- "-" }
+ if s > 0 { ch <- "s" }
+ } else {
+ fn := func(i int, s string) {
+ if i == 0 {
+ return
+ }
+
+ ch <- fmt.Sprintf("%d%s", i, s)
+ }
+
+ if m + a + d > 0 {
+ ch <- tput.Fg(col.Unstaged)
+ }
+
+ fn(m, "m")
+ fn(a, "+")
+ fn(d, "-")
+
+ if s > 0 {
+ ch <- tput.Fg(col.Staged)
+ }
+
+ fn(s, "s")
+ }
+}
+
+func FnUnpushed(ch chan string) {
+ defer close(ch)
+
+ n, err := GitUnpushed()
+ if err != nil {
+ Err(Ignore(err, GitErrIgns...))
+ return
+ }
+
+ if n == 0 {
+ return
+ }
+
+ ch <- tput.Fg(col.Unpushed)
+ ch <- fmt.Sprintf("%d^", n)
+}
+
+func FnDir(ch chan string) {
+ defer close(ch)
+
+ ch <- tput.Fg(col.Dir)
+
+ wd, err := os.Getwd()
+ if err != nil {
+ Err(err)
+ return
+ }
+
+ // TODO: hilight the dir that the repo belongs to?
+ // _, err = gitPrefix()
+ // if err != nil {
+ // Err(err)
+ // return
+ // }
+
+ home := os.Getenv("HOME")
+
+ if strings.HasPrefix(wd, home) {
+ bwd := []byte(wd)
+ bh := []byte(home)
+ l := len(bh) - 1
+
+ bwd[l] = '~'
+ ch <- string(bwd[l:])
+ } else {
+ ch <- wd
+ }
+}
+
+func FnDollar(ch chan string) {
+ defer close(ch)
+
+ ch <- tput.Fg(col.Dollar)
+
+ if os.Getuid() == 0 {
+ ch <- "#"
+ } else {
+ ch <- "$"
+ }
+}
diff --git a/git.go b/git.go
@@ -0,0 +1,234 @@
+package main
+
+import (
+ "io"
+ "fmt"
+ "sync"
+ "errors"
+ "strings"
+ "path/filepath"
+
+ "github.com/go-git/go-git/v5"
+ "github.com/go-git/go-git/v5/plumbing"
+)
+
+// Safely ignorable errors
+var GitErrIgns = []error{
+ plumbing.ErrReferenceNotFound,
+ git.ErrRepositoryNotExists,
+ ErrCmdNotRepo,
+}
+
+var (
+ ErrBadPrefix = errors.New("bad git prefix")
+ ErrMalformedDiff = errors.New("malfored git diff --name-status")
+ ErrCmdNotRepo = errors.New("git has been run in a directory that is not a repo")
+)
+
+func gitCmd(name string, arg ...string) ([]string, error) {
+ sl, err := Cmd(name, arg...)
+
+ // That's not a type. Some cmds output 'Not', some output 'not'.
+ // And I'm too lazy to do case insensitive matching.
+ if err != nil && strings.Contains(err.Error(), "ot a git repository") {
+ return nil, ErrCmdNotRepo
+ }
+
+ return sl, err
+}
+
+type gpCache struct {
+ sync.Mutex
+ sync.Once
+ P string
+ E error
+}
+
+var gpc gpCache
+
+func gitPrefix() (string, error) {
+ gpc.Lock()
+ defer gpc.Unlock()
+
+ gpc.Do(func(){
+ prefix, err := gitCmd("git", "rev-parse", "--show-prefix")
+ if err != nil {
+ gpc.E = err
+ } else if len(prefix) > 1 {
+ gpc.E = fmt.Errorf("%w: %v", ErrBadPrefix, prefix)
+ } else if len(prefix) == 1 {
+ gpc.P = filepath.Clean(prefix[0])
+ }
+ })
+
+ return gpc.P, gpc.E
+}
+
+func gitRepo() (*git.Repository, error) {
+ // Ignore the error - let's try PlainOpen anyway
+ p, _ := gitPrefix()
+
+ d, err := filepath.Abs(".")
+ if err != nil {
+ // Fine. Okay. We'll use "."
+ d = "."
+ } else if strings.HasSuffix(d, p) {
+ d = d[:len(d) - len(p)]
+ }
+
+ return git.PlainOpen(d)
+}
+
+func gitResolve(r *git.Repository, ref string) (*plumbing.Hash, error) {
+ return r.ResolveRevision(plumbing.Revision(ref))
+}
+
+func GitHEAD() (string, error) {
+ r, err := gitRepo()
+ if err != nil {
+ return "", err
+ }
+
+ head, err := r.Head()
+ if err != nil {
+ return "", err
+ }
+
+ brch := head.Name().Short()
+
+ if brch == "HEAD" {
+ // On a detached head, so let's show the commit.
+ hash, err := gitResolve(r, "HEAD")
+ if err != nil {
+ return "", err
+ }
+
+ return hash.String()[:7], nil
+ }
+
+ switch {
+ case cnf.Smallbranch && cnf.Uniqbranch:
+ s := Smalltxt(brch)
+
+ iter, err := r.Branches()
+ if err != nil {
+ return "", err
+ }
+
+ for {
+ b, err := iter.Next()
+ if err == io.EOF {
+ return s, nil
+ } else if err != nil {
+ return "", err
+ }
+
+ n := b.Name().Short()
+
+ // If n == brch, then it is the current branch, so it's fine.
+ if n != brch && (n == s || Smalltxt(n) == s) {
+ return brch, nil
+ }
+ }
+ case cnf.Smallbranch:
+ return Smalltxt(brch), nil
+ default:
+ return brch, nil
+ }
+}
+
+func GitUnpushed() (int, error) {
+ r, err := gitRepo()
+ if err != nil {
+ return 0, err
+ }
+
+ head, err := r.Head()
+ if err != nil {
+ return 0, err
+ }
+
+ b := head.Name().Short()
+
+ if b == "HEAD" {
+ // Alright we're on a detached head so we have no reference
+ // point any more. Say nothing.
+ return 0, nil
+ }
+
+ rmo, err := r.Remotes()
+ if err != nil {
+ return 0, err
+ }
+
+ rmh := make([]string, len(rmo))
+ for i := range rmo {
+ n := rmo[i].Config().Name
+ h, err := gitResolve(r, fmt.Sprintf("refs/remotes/%s/%s", n, b))
+ if err != nil {
+ return 0, err
+ }
+ rmh[i] = h.String()
+ }
+
+ iter, err := r.Log(&git.LogOptions{From: head.Hash()})
+ if err != nil {
+ return 0, err
+ }
+
+ var ret int
+ counter: for {
+ c, err := iter.Next()
+ if err == io.EOF {
+ break counter
+ } else if err != nil {
+ return 0, err
+ }
+
+ for i := range rmh {
+ if rmh[i] == c.Hash.String() {
+ break counter
+ }
+ }
+
+ ret++
+ }
+
+ return ret, nil
+}
+
+func malformedDiff(str string) error {
+ return fmt.Errorf("%w: \"%v\"", ErrMalformedDiff, str)
+}
+
+func GitDiff() (m, a, d, s int, err error) {
+ sl, err := gitCmd("git", "diff", "--name-status")
+ if err != nil {
+ return
+ }
+
+ // not in a git repo
+ if len(sl) == 1 && sl[0] == "" {
+ return
+ }
+
+ for i := range sl {
+ if len(sl[i]) < 1 {
+ err = malformedDiff(sl[i])
+ return
+ }
+
+ switch sl[i][0] {
+ case 'M':
+ m++
+ case 'A':
+ a++
+ case 'D':
+ d++
+ default:
+ err = malformedDiff(sl[i])
+ }
+ }
+
+ return
+}
diff --git a/go.mod b/go.mod
@@ -0,0 +1,29 @@
+module hhvn.uk/prompt
+
+go 1.22.0
+
+require (
+ dario.cat/mergo v1.0.0 // indirect
+ github.com/Microsoft/go-winio v0.6.1 // indirect
+ github.com/ProtonMail/go-crypto v1.0.0 // indirect
+ github.com/cloudflare/circl v1.3.7 // indirect
+ github.com/cyphar/filepath-securejoin v0.2.4 // indirect
+ github.com/emirpasic/gods v1.18.1 // indirect
+ github.com/gdamore/tcell/v2 v2.7.4 // indirect
+ github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
+ github.com/go-git/go-billy/v5 v5.5.0 // indirect
+ github.com/go-git/go-git/v5 v5.12.0 // indirect
+ github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
+ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
+ github.com/kevinburke/ssh_config v1.2.0 // indirect
+ github.com/pjbgf/sha1cd v0.3.0 // indirect
+ github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
+ github.com/skeema/knownhosts v1.2.2 // indirect
+ github.com/xanzy/ssh-agent v0.3.3 // indirect
+ golang.org/x/crypto v0.21.0 // indirect
+ golang.org/x/mod v0.12.0 // indirect
+ golang.org/x/net v0.22.0 // indirect
+ golang.org/x/sys v0.18.0 // indirect
+ golang.org/x/tools v0.13.0 // indirect
+ gopkg.in/warnings.v0 v0.1.2 // indirect
+)
diff --git a/go.sum b/go.sum
@@ -0,0 +1,121 @@
+dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
+dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
+github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
+github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
+github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
+github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78=
+github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
+github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
+github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
+github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
+github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
+github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
+github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
+github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
+github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
+github.com/gdamore/tcell/v2 v2.7.4 h1:sg6/UnTM9jGpZU+oFYAsDahfchWAFW8Xx2yFinNSAYU=
+github.com/gdamore/tcell/v2 v2.7.4/go.mod h1:dSXtXTSK0VsW1biw65DZLZ2NKr7j0qP/0J7ONmsraWg=
+github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
+github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
+github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU=
+github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow=
+github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys=
+github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY=
+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
+github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
+github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
+github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
+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/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
+github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
+github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
+github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
+github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
+github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
+github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
+github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
+github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A=
+github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
+github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
+golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
+golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
+golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
+golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
+golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
+golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
+golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
+golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
+golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
+golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
+golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
+golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
+golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
+gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/main.go b/main.go
@@ -0,0 +1,152 @@
+package main
+
+import (
+ "io"
+ "os"
+ "os/exec"
+ "strings"
+ "log"
+ "errors"
+ "runtime"
+
+ "hhvn.uk/prompt/config"
+ "hhvn.uk/prompt/tput"
+)
+
+var (
+ col = config.Colours
+ cnf = config.Settings
+)
+
+var fn = []Fn{
+ FnExitStatus,
+ nil,
+ FnUsername,
+ nil,
+ FnBranch,
+ FnDiff,
+ FnUnpushed,
+ nil,
+ FnDir,
+ FnDollar,
+}
+
+// Cmd runs a command and returns a slice of the lines produced by it.
+//
+// If the command writes to stderr, the output is returned as an error.
+func Cmd(name string, arg ...string) ([]string, error) {
+ cmd := exec.Command(name, arg...)
+
+ errp, err := cmd.StderrPipe()
+ if err != nil {
+ return nil, err
+ }
+
+ outp, err := cmd.StdoutPipe()
+ if err != nil {
+ return nil, err
+ }
+
+ if err := cmd.Start(); err != nil {
+ return nil, err
+ }
+ defer cmd.Wait()
+
+ b, err := io.ReadAll(errp)
+ if err != nil {
+ return nil, err
+ }
+ if len(b) != 0 {
+ return nil, errors.New(string(b))
+ }
+
+ out, err := io.ReadAll(outp)
+ if err != nil {
+ return nil, err
+ }
+
+ nnl := strings.TrimSuffix(string(out), "\n")
+
+ return strings.Split(nnl, "\n"), nil
+}
+
+func Err(e error) {
+ if e != nil {
+ pc, _, _, _ := runtime.Caller(1)
+ log.Printf("%s(): %s", runtime.FuncForPC(pc).Name(), e)
+ }
+}
+
+func Ignore(err error, ign ...error) error {
+ for i := range ign {
+ if errors.Is(err, ign[i]) {
+ return nil
+ }
+ }
+
+ return err
+}
+
+func ConsumeChan(ch chan string) string {
+ var str []string
+
+ for s := range ch {
+ str = append(str, s)
+ }
+
+ // TODO: find a better home for this
+ str = append(str, tput.Clear())
+
+ return strings.Join(str, "")
+}
+
+func Out(s string) {
+ os.Stdout.WriteString(s)
+}
+
+func main(){
+ if err := tput.CheckErr(); err != nil {
+ log.Fatal(err)
+ }
+
+ ch := make([]chan string, len(fn))
+ for i := range ch {
+ if fn[i] != nil {
+ ch[i] = make(chan string)
+ }
+ }
+
+ for i := range fn {
+ if fn[i] != nil {
+ go fn[i](ch[i])
+ }
+ }
+
+ s := make([]string, len(fn))
+ for i := range ch {
+ if fn[i] != nil {
+ s[i] = ConsumeChan(ch[i])
+ }
+ }
+
+ spacer := false
+ for i := range s {
+ if fn[i] == nil {
+ spacer = true
+ continue
+ }
+
+ if s[i] == "" {
+ continue
+ }
+
+ if spacer == true {
+ Out(" ")
+ spacer = false
+ }
+
+ Out(s[i])
+ }
+
+ Out(" ")
+}
diff --git a/smallify.go b/smallify.go
@@ -0,0 +1,38 @@
+package main
+
+// These functions only work on ASCII
+
+func Smalltxt(s string) string {
+ b := []byte(s)
+
+ var j int
+ for i := range b {
+ switch b[i] {
+ case 'a', 'e', 'i', 'o', 'u':
+ default:
+ b[j] = b[i]
+ j++
+ }
+ }
+
+ return string(b[:j])
+}
+
+func Smallcmp(ss string, sl string) bool {
+ s := []byte(ss)
+ l := []byte(sl)
+
+ var j int
+ for i := range l {
+ switch l[i] {
+ case 'a', 'e', 'i', 'o', 'u':
+ default:
+ if j == len(s) || s[j] != l[i] {
+ return false
+ }
+ j++
+ }
+ }
+
+ return j == len(s)
+}
diff --git a/smallify_test.go b/smallify_test.go
@@ -0,0 +1,24 @@
+package main
+
+import "testing"
+
+type testsmallcmpT struct {
+ s string
+ l string
+ r bool
+}
+
+var testsmallcmp = []testsmallcmpT {
+ {"tst", "test", true},
+ {"tst", "tests", false},
+ {"tst", "ts", false},
+}
+
+func TestSmallcmp(t *testing.T) {
+ for _, v := range testsmallcmp {
+ if Smallcmp(v.s, v.l) != v.r {
+ t.Fatalf("expected Smallcmp(\"%v\", \"%v\") to return %v\n",
+ v.s, v.l, v.r)
+ }
+ }
+}
diff --git a/tput/tput.go b/tput/tput.go
@@ -0,0 +1,37 @@
+package tput // hhvn.uk/prompt/tput
+
+import (
+ "os"
+ "fmt"
+
+ ti "github.com/gdamore/tcell/v2/terminfo"
+ tid "github.com/gdamore/tcell/v2/terminfo/dynamic"
+)
+
+var t *ti.Terminfo
+var err error
+
+func init() {
+ term := os.Getenv("TERM")
+
+ t, _, err = tid.LoadTerminfo(term)
+ if err != nil {
+ err = fmt.Errorf("%s: %w", term, err)
+ }
+}
+
+func rlify(s string) string {
+ return fmt.Sprintf("\001%s\002", s)
+}
+
+func CheckErr() error {
+ return err
+}
+
+func Fg(fg int) string {
+ return rlify(t.TParm(t.SetFg, fg))
+}
+
+func Clear() string {
+ return rlify(t.AttrOff)
+}