made thingy initial commit
sophuwu sophie@skisiel.com
Sat, 22 Mar 2025 03:03:29 +0100
3 files changed,
258 insertions(+),
0 deletions(-)
A
rbprompt.go
@@ -0,0 +1,253 @@
+package rbprompt + +/* +FMT strings: + +%u = username +%h = hostname +%i = ip address as colours +%e = random emote +%l = line number + +%<n>p = last <n> directories in the current working directory +%p = current working directory, short for %0p + +*/ + +import ( + "fmt" + "math" + "math/rand" + "strings" +) + +var ( + // EmoteEyes contains characters that can be used as eyes in emotes. + // If this is empty, using %e in the prompt will cause an error. + EmoteEyes = ":;=" + // EmoteMouths contains characters that can be used as mouths in emotes. + // If this is empty, using %e in the prompt will cause an error. + EmoteMouths = ")3>]DPO" +) + +func randInt(max int) int { + return rand.Int() % max +} + +func emote() string { + lenEyes := len(EmoteEyes) + lenMouths := len(EmoteMouths) + if lenEyes == 0 || lenMouths == 0 { + return "" + } + return string(EmoteEyes[randInt(lenEyes)]) + string(EmoteMouths[randInt(lenMouths)]) +} + +type Rainbow struct { + R, G, B, S int +} + +func (r *Rainbow) init() { + r.R = 255 + r.G = 0 + r.B = 0 + r.S = 55 - randInt(20) +} + +func (r *Rainbow) copy() Rainbow { + return Rainbow{r.R, r.G, r.B, r.S} +} + +func (r *Rainbow) next() { + if r.S <= 0 || (r.R == r.G && r.G == r.B) { + r.init() + return + } + for v := []*int{&r.R, &r.G, &r.B}; ; v[0], v[1], v[2] = v[1], v[2], v[0] { + if *v[0] == 255 && *v[2] == 000 { + *v[1] += r.S + if *v[1] > 255 { + *v[0] += 255 - *v[1] + *v[1] = 255 + } + break + } else if *v[1] == 255 && *v[2] == 000 { + *v[0] -= r.S + if *v[0] < 0 { + *v[2] += 0 - *v[0] + *v[0] = 0 + } + break + } + } +} + +func (r *Rainbow) escape() string { + return fmt.Sprintf(ESCAPESTR+"38;2;%d;%d;%dm", r.R, r.G, r.B) +} + +func (r *Rainbow) colour(in string) (out string) { + for i := range in { + out += r.escape() + in[i:i+1] + r.next() + } + out += "\033[0m" + return +} + +type PS struct { + LineNo uint + NoColor bool + Current string + Fmt, + User, + Home, + HostName, + PWD string + HostIP [4]byte + Rainbow Rainbow +} + +func (p *PS) String() string { + return p.Current +} + +var ESCAPESTR = "\033[" + +func (p *PS) ip2col() string { + if p.NoColor { + return "" + } + n := func() func() int { + i := 0 + return func() int { + i++ + return int(p.HostIP[i-1])*216/256 + 16 + } + }() + return fmt.Sprintf(ESCAPESTR+"48;5;%d;38;5;%dm▄"+ESCAPESTR+"48;5;%d;38;5;%dm▄"+ESCAPESTR+"0m", n(), n(), n(), n()) +} + +func (p *PS) pwd(n int) string { + pwd := p.PWD + trimmed := false + if strings.HasPrefix(pwd, p.Home) { + trimmed = true + pwd = strings.TrimPrefix(pwd, p.Home) + if len(pwd) == 0 { + pwd = "~" + } else { + if pwd[0] != '/' { + pwd = "/" + pwd + } + pwd = "~" + pwd + } + } + if n <= 0 { + return pwd + } + a := strings.Split(pwd, "/") + if !trimmed { + a = append([]string{"/"}, a...) + } + ll := len(a) - 1 + s := "" + for j := 0; j < n && j <= ll; j++ { + s = a[ll-j] + " " + s + } + return strings.TrimSpace(s) +} + +func errGen(fnName string) func(s string) error { + return func(s string) error { + return fmt.Errorf("rbprompt: %s: %s", fnName, s) + } +} + +func (p *PS) doFmt(rbfn *func(string) string, dst *string, f *string, i *int) error { + errFn := errGen("PS.doFmt") + s := "" + var j int = *i + *i++ + if *i >= len(*f) { + return errFn("unexpected end of format string") + } + switch (*f)[*i] { + case 'u': + s = p.User + break + case 'h': + s = p.HostName + break + case 'i': + *dst += p.ip2col() + *i++ + return nil + case 'e': + if ss := emote(); len(ss) == 2 { + s = ss + } else { + return errFn("cannot use %e in format string if emote list is empty") + } + break + case 'p': + s = p.pwd(0) + break + case 'l': + s = fmt.Sprint(p.LineNo) + break + case '%': + s = "%" + break + default: + for l := 0; *i < len(*f); *i++ { + if (*f)[*i] >= '0' && (*f)[*i] <= '9' { + l = l*10 + int((*f)[*i]-'0') + continue + } else if (*f)[*i] == 'p' { + s = p.pwd(l) + } + break + } + break + } + if s == "" { + return errFn("invalid format " + (*f)[j:*i]) + } + *dst += (*rbfn)(s) + *i++ + return nil +} + +func (p *PS) Next() error { + p.Rainbow.next() + r := p.Rainbow.copy() + r.S += int(10 * (math.Sin(float64(p.LineNo) * math.Pi / 10))) + + p.Current = "" + f := p.Fmt + var i int + var err error + rbfn := r.colour + if p.NoColor { + rbfn = func(in string) string { + return in + } + } + for { + i = strings.Index(f, "%") + if i < 0 { + p.Current += rbfn(f) + break + } + if i > 0 { + p.Current += rbfn(f[:i]) + } + if err = p.doFmt(&rbfn, &p.Current, &f, &i); err != nil { + return err + } + f = f[i:] + } + p.LineNo++ + return nil +}