package rbprompt /* FMT strings: %u = username %h = hostname %i = ip address as colours %e = random emote %l = line number %p = last 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 }