├── .gitignore ├── LICENSE ├── README.adoc ├── build-local.sh ├── bullettrain.go ├── check-race.sh ├── code_of_conduct.md ├── defaults.go ├── docs ├── creating-new-cars.md └── creating-plugin-car.md ├── go.mod ├── go.sum ├── readme_assets ├── 3rdparty.png ├── 3rdparty_drop-shadow.png ├── ansi256.png ├── ansi256_drop-shadow.png ├── preview.jpg └── preview_drop-shadow.png ├── release ├── releases └── .gitkeep └── src ├── ansi └── ansi.go └── car ├── custom └── custom.go ├── date └── date.go ├── directory ├── dir.go └── dir_test.go ├── git └── git.go ├── golang └── golang.go ├── host └── host.go ├── kubernetes └── kubernetes.go ├── nodejs └── nodejs.go ├── openvpn └── openvpn.go ├── os └── os.go ├── php └── php.go ├── python └── python.go ├── ruby └── ruby.go ├── status └── status.go ├── time └── time.go └── user └── user.go /.gitignore: -------------------------------------------------------------------------------- 1 | releases/ 2 | !releases/.gitkeep 3 | vendor/ 4 | /tmp/ 5 | .idea/ 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017-2018 vad.viktor@gmail.com 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | = Bullet Train shell prompt 2 | :toc: 3 | 4 | == Synopsis 5 | 6 | Bullet Train is a http://www.zsh.org/[zsh] and https://www.gnu.org/software/bash/[bash] shell prompt theme inspired by the https://github.com/Lokaltog/vim-powerline[Powerline Vim plugin]. 7 | 8 | image::readme_assets/preview_drop-shadow.png[Preview] 9 | 10 | * Single binary with wide architecture support: Mac, Linux, Raspberry Pi (ARM) 11 | * Go and 3rd party language plugin support 12 | * Is as slow as it's slowest car, won't slow down linearily the more car you use. 13 | * Latest Go version support for developers 14 | * Triggering system to show only relevant information 15 | * Almost everything can be overwritten through environment variables 16 | * UTF-8 support out of the box (thanks to Go) 17 | * 256 colour support, with special effects where the terminal software has support for them 18 | 19 | == Core cars 20 | 21 | * Time 22 | * Date 23 | * Current directory 24 | * Exit code of last command 25 | * User and hostname 26 | * Background jobs 27 | * OS icon and name 28 | * Git status 29 | * Current Python version and/or virtualenv 30 | * Current Ruby version and/or gemset 31 | * Current Node.js version 32 | * Current Golang version 33 | * Current PHP version 34 | 35 | External modules could show: - perl - elixir - erlang - screen support - tmux support(?) - mercurial - _more are coming as we are going through the existing ones for the https://github.com/caiogondim/bullet-train.zsh[ZSH version]_ 36 | 37 | If you want to add some new feature, or fix some bug, open a ticket! 38 | 39 | == Requirements 40 | 41 | In order to use the theme, you will first need: 42 | 43 | * A very cool font: 44 | * https://nerdfonts.com/[Nerd fonts] (https://aur.archlinux.org/packages/nerd-fonts-complete/[Arch Linux AUR]) <- our default 45 | * Alternatively a Powerline compatible font like https://github.com/Lokaltog/powerline-fonts[Vim Powerline patched fonts], http://input.fontbureau.com/[Input Mono] or http://larsenwork.com/monoid/[Monoid]. 46 | * On Ubuntu like systems you'll need the `ttf-ancient-fonts` package to correctly display some unicode symbols that are not covered by the Powerline fonts above. (https://aur.archlinux.org/packages/ttf-ancient-fonts/[Arch Linux AUR]) 47 | * Make sure terminal is using 256-colors mode with `export TERM="xterm-256color"` 48 | * For http://iterm2.com/[iTerm 2] users, make sure you go into your settings and set both the regular font and the non-ascii font to powerline compatible https://github.com/powerline/fonts[fonts] or the prompt separators and special characters will not display correctly. 49 | 50 | == Compatible terminal emulators 51 | 52 | * Linux 53 | * https://gnunn1.github.io/tilix-web/[Tilix] 54 | * https://gnometerminator.blogspot.ie/p/introduction.html[Terminator] 55 | * https://konsole.kde.org/[Konsole] 56 | * Windows WSL2 on https://github.com/microsoft/terminal[Windows Terminal] 57 | * Mac 58 | * http://iterm2.com/[iTerm2] 59 | 60 | == Installing 61 | 62 | {counter:installing}. We have prepared release executables on our release page 63 | 64 | https://github.com/bullettrain-sh/bullettrain-go-core/releases. 65 | 66 | Download the one that matches your architecture and OS. 67 | 68 | Of course, you are more than welcomed to build your own, customised version if you feel comfortable with Go. link:docs/creating-new-cars.md[Here are some help to do that]. 69 | 70 | {counter:installing}. In your rc files you only need to set the single prompt variable. 71 | 72 | :icons: font 73 | IMPORTANT: Single quotes are important not to store the evaluated result in the variable, but to reevaluate on every call. 74 | 75 | *ZSH - .zshrc* 76 | 77 | `PROMPT='$(bullettrain $?)'` 78 | 79 | *BASH - .bashrc* 80 | 81 | [source,bash] 82 | ---- 83 | function _update_ps1() { 84 | PS1="$(/home/ikon/.local/bin/bullettrain $?)" 85 | } 86 | PROMPT_COMMAND="_update_ps1" 87 | ---- 88 | 89 | == Options 90 | 91 | Most of the behaviours can be configured through environment variables, making you free from the recompiling work. 92 | 93 | These are the *core* feature configuration variables and module configuration information can be found on their respective READMEs. 94 | 95 | All envirnment variables must be exported for Go to be able to pick up. 96 | 97 | E.g.: `export BULLETTRAIN_CAR_ORDER="time user host python ruby"` 98 | 99 | === Defining colours and text effects 100 | 101 | Form of the colourization string: `foregroundColor+attributes:backgroundColor+attributes` 102 | 103 | ==== Colors 104 | 105 | * black 106 | * red 107 | * green 108 | * yellow 109 | * blue 110 | * magenta 111 | * cyan 112 | * white 113 | * https://en.wikipedia.org/wiki/ANSI_escape_code#Colors[0...255 (256 colors)] 114 | 115 | image:readme_assets/ansi256_drop-shadow.png[ansi256] 116 | 117 | **NOTE**: support for some attributes varies across terminal emulator softwares. 118 | 119 | ==== Foreground Attributes 120 | 121 | * B = Blink 122 | * b = bold 123 | * I = italic 124 | * h = high intensity (bright) 125 | * i = inverse 126 | * s = strikethrough 127 | * u = underline 128 | 129 | ==== Background Attributes 130 | 131 | * h = high intensity (bright) 132 | 133 | == Defining car templates 134 | 135 | Car templates gives the user the most flexibility to rearrange/decorate the car's content further. With the template's help the user can also control which element of the car she wants to hide (although the processing of it is still done, so she gains no speed). The template language is using Go's standard library `text/template` package. 136 | 137 | Here is an example which will introduce the concepts and usage: 138 | 139 | The Time car could expose its icon and printed text (time). 140 | 141 | There are a few rules to follow: 142 | 143 | * exposed names start with a dot, because they will internally be bundled within a Go struct 144 | * exposed names start with a capital letter, because the struct needs to expose them and everything is exposed (aka public) in Go if it is capitalised 145 | * cars should define colouring functions that it should expose, with suggested naming: 146 | * `cs`: function to colour the symbol 147 | * `ci`: function to colour the information the car exposes 148 | * `c`: function to colour the car's base colouring 149 | * exposed names are enclosed in `{{` and `}}` as they are the Go template system's chosen delimiters 150 | * template environment variables have the suffix of `_TEMPLATE` 151 | 152 | Colour function names are suggestion and can vary in number and naming in plugins. 153 | 154 | `{{.Icon}}` : the icon of the car `{{.Time}}` : the information this car exposes 155 | 156 | Then one could surround the car's output text with spaces to her liking and define it in the env var: 157 | 158 | `export BULLETTRAIN_CAR_TIME_TEMPLATE="{{.Icon | printf " %s " | cs }}{{.Time | c}}"` 159 | 160 | `printf` is mapped to `fmt.Printf` and is perfect for adding spaces and other chars around the symbol, while in the second step of the pipeline is the colouring. This makes it easy not to leave the spaces without colours. 161 | 162 | == Basic behaviours 163 | 164 | |=== 165 | |Environment variable |Description |Default value 166 | 167 | |BULLETTRAIN_CARS 168 | |Control which cars to appear in what order, using their __callwords__. 169 | |`os time date user host dir python go ruby nodejs php git status` 170 | 171 | |BULLETTRAIN_CARS_SEPARATE_LINE 172 | |Whether the cars should be on their own line above the prompt. 173 | |true 174 | 175 | |BULLETTRAIN_NO_PAINT 176 | |Whether you wish not to use paint at all, aka black and white mode. 177 | |false 178 | 179 | |BULLETTRAIN_DEBUG 180 | |Turning debug print mode on to help seeing actual character codes. 181 | |false 182 | 183 | |BULLETTRAIN_SEPARATOR_ICON 184 | |Defines the car separator icon. 185 | |\uE0B0  186 | 187 | |BULLETTRAIN_SEPARATOR_PAINT 188 | |Defines the car separator icon's paint. 189 | |calculated on the fly 190 | 191 | |BULLETTRAIN_SEPARATOR_TEMPLATE 192 | |Defines the car separator's template. 193 | |`{{.Icon \| printf "%s " \| c}}` 194 | 195 | |BULLETTRAIN_PROMPT_CHAR 196 | |Redefines the end char of the prompt when you are a normal user. 197 | |`$` 198 | 199 | |BULLETTRAIN_PROMPT_CHAR_TEMPLATE 200 | |Normal user's end char template. 201 | |`{{.Icon \| printf "%s " \| c}}` 202 | 203 | |BULLETTRAIN_PROMPT_CHAR_PAINT 204 | |Redefines the end char's colour of the prompt when you are a normal user. 205 | |green 206 | 207 | |BULLETTRAIN_PROMPT_CHAR_ROOT 208 | |Redefines the end char of the prompt when you are a root user. 209 | |`#` 210 | 211 | |BULLETTRAIN_PROMPT_CHAR_ROOT_TEMPLATE 212 | |Root user's end char template. 213 | |`{{.Icon \| printf "%s " \| c}}` 214 | 215 | |BULLETTRAIN_PROMPT_CHAR_ROOT_PAINT 216 | |Redefines the end char's colour of the prompt when you are a root user. 217 | |red 218 | |=== 219 | 220 | == Core cars 221 | 222 | === Time Car 223 | 224 | Showing current time. 225 | 226 | **Callword**: `time` 227 | 228 | **Template variables**: 229 | 230 | * `.Icon`: the car's icon 231 | * `.Time`: the time text 232 | 233 | **Template colours**: 234 | 235 | * `c`: the car's colour 236 | * `cs`: the car symbol's colour 237 | 238 | *Options* 239 | 240 | |=== 241 | |Environment variable |Description |Default value 242 | 243 | |BULLETTRAIN_CAR_TIME_SHOW 244 | |Whether the car needs to be shown. 245 | |false 246 | 247 | |BULLETTRAIN_CAR_TIME_TEMPLATE 248 | |The car's template. 249 | |`{{.Icon \| prinf " %s " \| cs}}{{.Time \| c}}` 250 | 251 | |BULLETTRAIN_CAR_TIME_SYMBOL_ICON 252 | |Icon displayed on the car. 253 | |`\uF43A`  254 | 255 | |BULLETTRAIN_CAR_TIME_SYMBOL_PAINT 256 | |Colour override for the car's symbol. 257 | |white:black 258 | 259 | |BULLETTRAIN_CAR_TIME_PAINT 260 | |Colour override for the car's paint. 261 | |white:black 262 | 263 | |BULLETTRAIN_CAR_TIME_SEPARATOR_PAINT 264 | |Colour override for the car's right-hand side separator paint. 265 | |Using default painting algorythm. 266 | 267 | |BULLETTRAIN_CAR_TIME_SEPARATOR_SYMBOL 268 | |Override the car's right-hand side separator symbol. 269 | |Using global symbol. 270 | 271 | |BULLETTRAIN_CAR_TIME_12HR 272 | |Use 12 hour format. 273 | |false 274 | 275 | |BULLETTRAIN_CAR_TIME_SEPARATOR_TEMPLATE 276 | |Defines the car separator's template. 277 | |Using global template. 278 | 279 | |=== 280 | 281 | === Date Car 282 | 283 | Showing current date. Format: `YYYY-MM-DD` 284 | 285 | **Callword**: `date` 286 | 287 | **Template variables**: 288 | 289 | * `.Icon`: the car's icon 290 | * `.Date`: the date text 291 | 292 | **Template colours**: 293 | 294 | * `c`: the car's colour 295 | * `cs`: the car symbol's colour 296 | 297 | *Options* 298 | 299 | |=== 300 | |Environment variable |Description|Default value 301 | 302 | |BULLETTRAIN_CAR_DATE_SHOW 303 | |Whether the car needs to be shown. | 304 | false 305 | 306 | |BULLETTRAIN_CAR_DATE_TEMPLATE 307 | |The car's template. 308 | |`{{.Icon \| printf "%s" \| cs}}{{.Date \| c}}` 309 | 310 | |BULLETTRAIN_CAR_DATE_PAINT 311 | |Colour override for the car's paint. 312 | |red:black 313 | 314 | |BULLETTRAIN_CAR_DATE_SYMBOL_ICON 315 | |Icon displayed on the car. 316 | |`\uF073`  317 | 318 | |BULLETTRAIN_CAR_DATE_SYMBOL_PAINT 319 | |Colour override for the car's symbol. 320 | |white:black 321 | 322 | |BULLETTRAIN_CAR_DATE_SEPARATOR_PAINT 323 | |Colour override for the car's right-hand side separator paint. 324 | |Using default painting algorythm. 325 | 326 | |BULLETTRAIN_CAR_DATE_SEPARATOR_SYMBOL 327 | |Override the car's right-hand side separator symbol. 328 | |Using global symbol. 329 | 330 | |BULLETTRAIN_CAR_DATE_SEPARATOR_TEMPLATE 331 | |Defines the car separator's template. 332 | |Using global template. 333 | 334 | |=== 335 | 336 | === User Car 337 | 338 | Showing current username. 339 | 340 | **Callword**: `user` 341 | 342 | **Template variables**: 343 | 344 | * `.User`: the user name text 345 | 346 | **Template colours**: 347 | 348 | * `c`: the car's colour 349 | 350 | *Options* 351 | 352 | |=== 353 | |Environment variable |Description |Default value 354 | 355 | |BULLETTRAIN_CAR_USER_SHOW 356 | |Whether the car needs to be shown. 357 | |true 358 | 359 | |BULLETTRAIN_CAR_USER_PAINT 360 | |Colour override for the car's paint. 361 | |black:white 362 | 363 | |BULLETTRAIN_CAR_USER_TEMPLATE 364 | |The car's template. 365 | |`{{.User \| c}}` 366 | 367 | |BULLETTRAIN_CAR_USER_SEPARATOR_PAINT 368 | |Colour override for the car's right-hand side separator paint. 369 | |Using default painting algorythm. 370 | 371 | |BULLETTRAIN_CAR_USER_SEPARATOR_SYMBOL 372 | |Override the car's right-hand side separator symbol. 373 | |Using global symbol. 374 | 375 | |BULLETTRAIN_CAR_USER_SEPARATOR_TEMPLATE 376 | |Defines the car separator's template. 377 | |Using global template. 378 | 379 | |=== 380 | 381 | === Host Car 382 | 383 | Showing current hostname. 384 | 385 | **Callword**: `host` 386 | 387 | **Template variables**: 388 | 389 | * `.Icon`: the car's icon 390 | * `.Date`: the date text 391 | 392 | **Template colours**: 393 | 394 | * `c`: the car's colour 395 | * `cs`: the car symbol's colour 396 | 397 | *Options* 398 | 399 | |=== 400 | |Environment variable |Description |Default value 401 | 402 | |BULLETTRAIN_CAR_HOST_SHOW 403 | |Whether the car needs to be shown. 404 | |true 405 | 406 | |BULLETTRAIN_CAR_HOST_PAINT 407 | |Colour override for the car's paint. 408 | |black:white 409 | 410 | |BULLETTRAIN_CAR_HOST_TEMPLATE 411 | |The car's template. 412 | |`{{.Host \| c}}` 413 | 414 | |BULLETTRAIN_CAR_HOST_SEPARATOR_PAINT 415 | |Colour override for the car's right-hand side separator paint. 416 | |Using default painting algorythm. 417 | 418 | |BULLETTRAIN_CAR_HOST_SEPARATOR_SYMBOL 419 | |Override the car's right-hand side separator symbol. 420 | |Using global symbol. 421 | 422 | |BULLETTRAIN_CAR_HOST_SEPARATOR_TEMPLATE 423 | |Defines the car separator's template. 424 | |Using global template. 425 | 426 | |=== 427 | 428 | === Directory Car 429 | 430 | Showing current directory. 431 | 432 | **Callword**: `dir` 433 | 434 | *Options* 435 | 436 | |=== 437 | |Environment variable |Description |Default value 438 | 439 | |BULLETTRAIN_CAR_DIRECTORY_SHOW 440 | |Whether the car needs to be shown. 441 | |true 442 | 443 | |BULLETTRAIN_CAR_DIRECTORY_PAINT 444 | |Colour override for the car's paint. 445 | |white:blue 446 | 447 | |BULLETTRAIN_CAR_DIRECTORY_SEPARATOR_PAINT 448 | |Colour override for the car's right-hand side separator paint. 449 | |Using default painting algorythm. 450 | 451 | |BULLETTRAIN_CAR_DIRECTORY_SEPARATOR_SYMBOL 452 | |Override the car's right-hand side separator symbol. 453 | |Using global symbol. 454 | 455 | |BULLETTRAIN_CAR_DIRECTORY_PATH_SEPARATOR 456 | |Set a custom path separator character. 457 | |`\uF105`  458 | 459 | |BULLETTRAIN_CAR_DIRECTORY_FRONT_MAX_LENGTH 460 | |Set the number of parent directories's front displayed. Setting it to negative means to show all of directories. 461 | |2 462 | 463 | |BULLETTRAIN_CAR_DIRECTORY_TAIL_MAX_LENGTH 464 | |Set the number of parent directories's tail displayed. Setting it to negative means to show all of directories. 465 | |2 466 | 467 | |BULLETTRAIN_CAR_DIRECTORY_DEPTH_INDICATOR 468 | |Indicator of too deep directory structure in `merge` mode. 469 | |`\uF142`  470 | 471 | |BULLETTRAIN_CAR_DIRECTORY_ELLIPSIS 472 | |Ellipsis symbol of directory name in `acronym` mode. 473 | |`*` 474 | 475 | |BULLETTRAIN_CAR_DIRECTORY_ABBREVIATE_MODE 476 | |Abbreviate mode of too deep directory structure. Set to `merge` for using "/usr/lib/.../pkg" style, set to `acronym` for using "/user/lib/g__/s__/pkg" style. 477 | |`acronym` 478 | 479 | |=== 480 | 481 | === OS Car 482 | 483 | Showing current operating system logo. Mainly purposed as a design element. 484 | 485 | **Callword**: `os` 486 | 487 | **Template variables**: 488 | 489 | * `.Icon`: the car's icon 490 | * `.Name`: the OS name text 491 | 492 | **Template colours**: 493 | 494 | * `c`: the car's colour 495 | * `cs`: the car symbol's colour 496 | 497 | *Options* 498 | 499 | |=== 500 | |Environment variable |Description |Default value 501 | 502 | |BULLETTRAIN_CAR_OS_SHOW 503 | |Whether the car needs to be shown. 504 | |false 505 | 506 | |BULLETTRAIN_CAR_OS_PAINT 507 | |Colour override for the car's paint. 508 | |white:cyan 509 | 510 | |BULLETTRAIN_CAR_OS_TEMPLATE 511 | |The car's template. 512 | |`{{.Icon \| printf "%s " \| cs}}{{.Name \| c}}` 513 | 514 | |BULLETTRAIN_CAR_OS_SYMBOL_PAINT 515 | |Colour override for the car's symbol. 516 | |white:cyan 517 | 518 | |BULLETTRAIN_CAR_OS_SYMBOL_ICON 519 | |Icon displayed on the car. 520 | |`\uF83C`  521 | 522 | |BULLETTRAIN_CAR_OS_SEPARATOR_PAINT 523 | |Colour override for the car's right-hand side separator paint. 524 | |Using default painting algorythm. 525 | 526 | |BULLETTRAIN_CAR_OS_SEPARATOR_SYMBOL 527 | |Override the car's right-hand side separator symbol. 528 | |Using global symbol. 529 | 530 | |BULLETTRAIN_CAR_OS_SEPARATOR_TEMPLATE 531 | |Defines the car separator's template. 532 | |Using global template. 533 | 534 | |=== 535 | 536 | === Last command exit code Car 537 | 538 | Showing last command's exit code. 539 | 540 | Since the bullettrain executable is running as a separate process as the shell which is executing it, it needs to get the last command's exit code by an argument. 541 | 542 | Shells have a special global for it: `$?` 543 | 544 | ZSH example: 545 | 546 | `PROMPT='$(bullettrain $?)'` 547 | 548 | **Callword**: `status` 549 | 550 | **Template variables**: 551 | 552 | * `.Icon`: the car's icon 553 | * `.Code`: the error code text 554 | 555 | **Template colours**: 556 | 557 | * `c`: the car's colour 558 | * `cs`: the car symbol's colour 559 | 560 | *Options* 561 | 562 | |=== 563 | |Environment variable |Description |Default value 564 | 565 | |BULLETTRAIN_CAR_STATUS_SHOW 566 | |Whether the car needs to be shown. 567 | |false 568 | 569 | |BULLETTRAIN_CAR_STATUS_TEMPLATE 570 | |The car's template. 571 | |`{{.Icon \| printf "%s " \| cs}}{{.Code \| c}}` 572 | 573 | |BULLETTRAIN_CAR_STATUS_SYMBOL_ICON 574 | |Icon displayed on the car. 575 | |`\uF490`  576 | 577 | |BULLETTRAIN_CAR_STATUS_SYMBOL_PAINT 578 | |Colour override for the car's symbol. 579 | |yellow:red 580 | 581 | |BULLETTRAIN_CAR_STATUS_PAINT 582 | |Colour override for the car's paint. 583 | |white:red 584 | 585 | |BULLETTRAIN_CAR_STATUS_SEPARATOR_PAINT 586 | |Colour override for the car's right-hand side separator paint. 587 | |Using default painting algorythm. 588 | 589 | |BULLETTRAIN_CAR_STATUS_SEPARATOR_SYMBOL 590 | |Override the car's right-hand side separator symbol. 591 | |Using global symbol. 592 | 593 | |=== 594 | 595 | === Background jobs Car 596 | 597 | https://github.com/bullettrain-sh/bullettrain-go-core/issues/24 598 | 599 | === Git Car 600 | 601 | * Displaying only when needed 602 | * Current branch name 603 | * State of the branch 604 | 605 | **Callword**: `git` 606 | 607 | **Template variables**: 608 | 609 | * `.Icon`: the car's icon 610 | * `.Name`: the name of the branch 611 | * `.StatusIcon`: the status icons 612 | 613 | **Template colours**: 614 | 615 | * `c`: the car's colour 616 | * `cs`: the car symbol's colour 617 | * `css`: the status icon symbol's colour 618 | 619 | *Options* 620 | 621 | |=== 622 | |Environment variable |Description |Default value 623 | 624 | |BULLETTRAIN_CAR_GIT_PAINT 625 | |Colour override for the car't paint. 626 | |red:white 627 | 628 | |BULLETTRAIN_CAR_GIT_TEMPLATE 629 | |The car's template. 630 | |`{{.Icon \| printf "%s " \| cs}}{{.Name \| c}}{{.StatusIcon \| printf " %s"\| csi}}` 631 | 632 | |BULLETTRAIN_CAR_GIT_SYMBOL_ICON 633 | |Icon displayed on the car. 634 | |`\uE702`  635 | 636 | |BULLETTRAIN_CAR_GIT_SYMBOL_PAINT 637 | |Colour override for the car's symbol. 638 | |red:white 639 | 640 | |BULLETTRAIN_CAR_GIT_DIRTY_ICON 641 | |Icon displayed when there are changes. 642 | |`\uF00D`  643 | 644 | |BULLETTRAIN_CAR_GIT_DIRTY_PAINT 645 | |Colour override for the dirty symbol. 646 | |red:white 647 | 648 | |BULLETTRAIN_CAR_GIT_CLEAN_ICON 649 | |Icon displayed when there are no changes. 650 | |`\uF632`  651 | 652 | |BULLETTRAIN_CAR_GIT_CLEAN_PAINT 653 | |Colour override for the clean symbol. 654 | |green:white 655 | 656 | |BULLETTRAIN_CAR_GIT_SEPARATOR_PAINT 657 | |Colour override for the car's right-hand side separator paint. 658 | |Using default painting algorythm. 659 | 660 | |BULLETTRAIN_CAR_GIT_SEPARATOR_SYMBOL 661 | |Override the car's right-hand side separator symbol. 662 | |Using global symbol. 663 | 664 | |BULLETTRAIN_CAR_GIT_SEPARATOR_TEMPLATE 665 | |Defines the car separator's template. 666 | |Using global template. 667 | 668 | |=== 669 | 670 | === Go/Golang Car 671 | 672 | * Displaying only when needed 673 | * Go version display 674 | 675 | **Callword**: `go` 676 | 677 | **Template variables**: 678 | 679 | * `.Icon`: the car's icon 680 | * `.Info`: the Go version text 681 | 682 | **Template colours**: 683 | 684 | * `c`: the car's colour 685 | * `cs`: the car symbol's colour 686 | 687 | *Options* 688 | 689 | |=== 690 | |Environment variable |Description |Default value 691 | 692 | |BULLETTRAIN_CAR_GO_SHOW 693 | |Whether the car needs to be shown all the time. 694 | |false 695 | 696 | |BULLETTRAIN_CAR_GO_TEMPLATE 697 | |The car's template. 698 | |`{{.Icon \| printf "%s " \| cs}}{{.Info \| c}}` 699 | 700 | |BULLETTRAIN_CAR_GO_PAINT 701 | |Colour override for the car's paint. 702 | |black:123 703 | 704 | |BULLETTRAIN_CAR_GO_SYMBOL_ICON 705 | |Icon displayed on the car. 706 | |`\uE627`  707 | 708 | |BULLETTRAIN_CAR_GO_SYMBOL_PAINT 709 | |Colour override for the car's symbol. 710 | |black:123 711 | 712 | |BULLETTRAIN_CAR_GO_SEPARATOR_PAINT 713 | |Colour override for the car's right-hand side separator paint. 714 | |Using default painting algorythm. 715 | 716 | |BULLETTRAIN_CAR_GO_SEPARATOR_SYMBOL 717 | |Override the car's right-hand side separator symbol. 718 | |Using global symbol. 719 | 720 | |BULLETTRAIN_CAR_GO_SEPARATOR_TEMPLATE 721 | |Defines the car separator's template. 722 | |Using global template. 723 | 724 | |=== 725 | 726 | === Node.js Car 727 | 728 | * Displaying only when needed (*.js, .nvmrc) 729 | * Node version display 730 | 731 | **Callword**: `nodejs` 732 | 733 | **Template variables**: 734 | 735 | * `.Icon`: the car's icon 736 | * `.Info`: the Nodejs version text 737 | 738 | **Template colours**: 739 | 740 | * `c`: the car's colour 741 | * `cs`: the car symbol's colour 742 | 743 | *Options* 744 | 745 | |=== 746 | |Environment variable |Description |Default value 747 | 748 | |BULLETTRAIN_CAR_NODEJS_SHOW 749 | |Whether the car needs to be shown all the time. 750 | |false 751 | 752 | |BULLETTRAIN_CAR_NODEJS_TEMPLATE 753 | |The car's template. 754 | |`{{.Icon \| printf "%s " \| cs}}{{.Info \| c}}` 755 | 756 | |BULLETTRAIN_CAR_NODEJS_PAINT 757 | |Colour override for the car's paint. 758 | |white:green 759 | 760 | |BULLETTRAIN_CAR_NODEJS_SYMBOL_ICON 761 | |Icon displayed on the car. 762 | |`\uF898`  763 | 764 | |BULLETTRAIN_CAR_NODEJS_SYMBOL_PAINT 765 | |Colour override for the car's symbol. 766 | |black:green 767 | 768 | |BULLETTRAIN_CAR_NODEJS_SEPARATOR_PAINT 769 | |Colour override for the car's right-hand side separator paint. 770 | |Using default painting algorythm. 771 | 772 | |BULLETTRAIN_CAR_NODEJS_SEPARATOR_SYMBOL 773 | |Override the car's right-hand side separator symbol. 774 | |Using global symbol. 775 | 776 | |BULLETTRAIN_CAR_NODEJS_SEPARATOR_TEMPLATE 777 | |Defines the car separator's template. 778 | |Using global template. 779 | 780 | |=== 781 | 782 | === Openvpn Car 783 | 784 | * Displaying all known VPNs and their up/down status 785 | * Currently working only with systemd units 786 | 787 | **Callword**: `openvpn` 788 | 789 | **Template variables**: 790 | 791 | * `.Icon`: the car's icon 792 | * `.Statuses`: the VPN status icon 793 | 794 | **Template colours**: 795 | 796 | * `c`: the car's colour 797 | * `cs`: the car symbol's colour 798 | 799 | *Options* 800 | 801 | |=== 802 | |Environment variable |Description |Default value 803 | 804 | |BULLETTRAIN_CAR_OPENVPN_SHOW 805 | |Whether the car needs to be shown all the time. 806 | |false 807 | 808 | |BULLETTRAIN_CAR_OPENVPN_TEMPLATE 809 | |The car's template. 810 | |`{{.Icon | printf "%s " | cs}}{{range $name, $status := .Statuses}}{{printStatus $name $status}}{{end}}` 811 | 812 | |BULLETTRAIN_CAR_OPENVPN_PAINT 813 | |Colour override for the car't paint. 814 | |208:black 815 | 816 | |BULLETTRAIN_CAR_OPENVPN_SYMBOL_ICON 817 | |Icon displayed on the car. 818 | |`` 819 | 820 | |BULLETTRAIN_CAR_OPENVPN_SYMBOL_PAINT 821 | |Colour override for the car's symbol. 822 | |208:black 823 | 824 | |BULLETTRAIN_CAR_OPENVPN_SYMBOL_ICON_LOCKED 825 | |Icon displayed when the VPN connection is up. 826 | |`` 827 | 828 | |BULLETTRAIN_CAR_OPENVPN_SYMBOL_PAINT_LOCKED 829 | |Colour override for the VPN up symbol. 830 | |green:black 831 | 832 | |BULLETTRAIN_CAR_OPENVPN_SYMBOL_ICON_UNLOCKED 833 | |Icon displayed when the VPN connection is down. 834 | |`` 835 | 836 | |BULLETTRAIN_CAR_OPENVPN_SYMBOL_PAINT_UNLOCKED 837 | |Colour override for the VPN down symbol. 838 | |red:black 839 | 840 | |BULLETTRAIN_CAR_OPENVPN_SEPARATOR_PAINT 841 | |Colour override for the car's right-hand side separator paint. 842 | |Using default painting algorythm. 843 | 844 | |BULLETTRAIN_CAR_OPENVPN_SEPARATOR_SYMBOL 845 | |Override the car's right-hand side separator symbol. 846 | |Using global symbol. 847 | 848 | |BULLETTRAIN_CAR_OPENVPN_SEPARATOR_TEMPLATE 849 | |Defines the car separator's template. 850 | |Using global template. 851 | 852 | |=== 853 | 854 | === Python Car 855 | 856 | * Displaying only when needed 857 | * Python version display 858 | * Virtualenv display 859 | * Support for https://github.com/pyenv/pyenv[Pyenv] and it's https://github.com/pyenv/pyenv-virtualenv[virtualenv] plugin 860 | 861 | **Callword**: `python` 862 | 863 | **Template variables**: 864 | 865 | * `.VersionIcon`: the Python version icon 866 | * `.Version`: the Python version text 867 | * `.VenvIcon`: the Python virtualenv icon 868 | * `.Venv`: the Python virtualenv text 869 | 870 | **Template colours**: 871 | 872 | * `c`: the Python version colour 873 | * `cs`: the Python version symbol's colour 874 | * `cvs`: the Python virtualenv symbol's colour 875 | 876 | *Options* 877 | 878 | |=== 879 | |Environment variable |Description |Default value 880 | 881 | |BULLETTRAIN_CAR_PYTHON_SHOW 882 | |Whether the car needs to be shown all the time. 883 | |false 884 | 885 | |BULLETTRAIN_CAR_PYTHON_TEMPLATE 886 | |The car's template. 887 | |`{{.VersionIcon \| printf "%s " \| cs}}{{.Version \| printf "%s " \| c}}{{.VenvIcon \| printf "%s " \| cvs}}{{.Venv \| c}}` 888 | 889 | |BULLETTRAIN_CAR_PYTHON_PAINT 890 | |Colour override for the car't paint. 891 | |black:220 892 | 893 | |BULLETTRAIN_CAR_PYTHON_SYMBOL_ICON 894 | |Icon displayed on the car. 895 | |`\uE235`  896 | 897 | |BULLETTRAIN_CAR_PYTHON_SYMBOL_PAINT 898 | |Colour override for the car's symbol. 899 | |32:220 900 | 901 | |BULLETTRAIN_CAR_PYTHON_VIRTUALENV_SYMBOL_ICON 902 | |Icon displayed on the car. 903 | |`\xf0\x9f\x90\x8d` 🐍 904 | 905 | |BULLETTRAIN_CAR_PYTHON_VIRTUALENV_SYMBOL_PAINT 906 | |Colour override for the car's symbol. 907 | |32:220 908 | 909 | |BULLETTRAIN_CAR_PYTHON_SEPARATOR_PAINT 910 | |Colour override for the car's right-hand side separator paint. 911 | |Using default painting algorythm. 912 | 913 | |BULLETTRAIN_CAR_PYTHON_SEPARATOR_SYMBOL 914 | |Override the car's right-hand side separator symbol. 915 | |Using global symbol. 916 | 917 | |BULLETTRAIN_CAR_PYTHON_SEPARATOR_TEMPLATE 918 | |Defines the car separator's template. 919 | |Using global template. 920 | 921 | |=== 922 | 923 | === Ruby Car 924 | 925 | * Displaying only when needed 926 | * Ruby version display 927 | 928 | **Callword**: `ruby` 929 | 930 | **Template variables**: 931 | 932 | * `.Icon`: the car's icon 933 | * `.Info`: the Ruby version text 934 | 935 | **Template colours**: 936 | 937 | * `c`: the car's colour 938 | * `cs`: the car symbol's colour 939 | 940 | *Options* 941 | 942 | |=== 943 | |Environment variable |Description |Default value 944 | 945 | |BULLETTRAIN_CAR_RUBY_SHOW 946 | |Whether the car needs to be shown all the time. 947 | |false 948 | 949 | |BULLETTRAIN_CAR_RUBY_TEMPLATE 950 | |The car's template. 951 | |`{{.Icon \| printf "%s " \| cs}}{{.Info \| c}}` 952 | 953 | |BULLETTRAIN_CAR_RUBY_PAINT 954 | |Colour override for the car't paint. 955 | |white:red 956 | 957 | |BULLETTRAIN_CAR_RUBY_SYMBOL_ICON 958 | |Icon displayed on the car. 959 | |`\uE23E`  960 | 961 | |BULLETTRAIN_CAR_RUBY_SYMBOL_PAINT 962 | |Colour override for the car's symbol. 963 | |white:red 964 | 965 | |BULLETTRAIN_CAR_RUBY_SEPARATOR_PAINT 966 | |Colour override for the car's right-hand side separator paint. 967 | |Using default painting algorythm. 968 | 969 | |BULLETTRAIN_CAR_RUBY_SEPARATOR_SYMBOL 970 | |Override the car's right-hand side separator symbol. 971 | |Using global symbol. 972 | 973 | |BULLETTRAIN_CAR_RUBY_SEPARATOR_TEMPLATE 974 | |Defines the car separator's template. 975 | |Using global template. 976 | 977 | |=== 978 | 979 | === PHP Car 980 | 981 | * Displaying only when needed 982 | * PHP version display 983 | 984 | **Callword**: `php` 985 | 986 | **Template variables**: 987 | 988 | * `.Icon`: the car's icon 989 | * `.Info`: the PHP version text 990 | 991 | **Template colours**: 992 | 993 | * `c`: the car's colour 994 | * `cs`: the car symbol's colour 995 | 996 | *Options* 997 | 998 | |=== 999 | |Environment variable |Description |Default value 1000 | 1001 | |BULLETTRAIN_CAR_PHP_SHOW 1002 | |Whether the car needs to be shown all the time. 1003 | |false 1004 | 1005 | |BULLETTRAIN_CAR_PHP_TEMPLATE 1006 | |The car's template. 1007 | |`{{.Icon \| printf "%s " \| cs}}{{.Info \| c}}` 1008 | 1009 | |BULLETTRAIN_CAR_PHP_PAINT 1010 | |Colour override for the car't paint. 1011 | |white:69 1012 | 1013 | |BULLETTRAIN_CAR_PHP_SYMBOL_ICON 1014 | |Icon displayed on the car. 1015 | |`\uE608`  1016 | 1017 | |BULLETTRAIN_CAR_PHP_SYMBOL_PAINT 1018 | |Colour override for the car's symbol. 1019 | |black:69 1020 | 1021 | |BULLETTRAIN_CAR_PHP_SEPARATOR_PAINT 1022 | |Colour override for the car's right-hand side separator paint. 1023 | |Using default painting algorythm. 1024 | 1025 | |BULLETTRAIN_CAR_PHP_SEPARATOR_SYMBOL 1026 | |Override the car's right-hand side separator symbol. 1027 | |Using global symbol. 1028 | 1029 | |BULLETTRAIN_CAR_PHP_SEPARATOR_TEMPLATE 1030 | |Defines the car separator's template. 1031 | |Using global template. 1032 | 1033 | |=== 1034 | 1035 | === Kubernetes Car 1036 | 1037 | Originally from: https://github.com/devkanro 1038 | 1039 | * Displaying only when kubectl installed 1040 | * Kubernetes context display 1041 | 1042 | **Callword**: `k8s` 1043 | 1044 | **Template variables**: 1045 | 1046 | * `.Icon`: the car's icon 1047 | * `.Context`: the Kubernetes context text 1048 | 1049 | **Template colours**: 1050 | 1051 | * `c`: the car's colour 1052 | 1053 | *Options* 1054 | 1055 | |=== 1056 | |Environment variable |Description |Default value 1057 | 1058 | |BULLETTRAIN_CAR_K8S_SHOW 1059 | |Whether the car needs to be shown all the time. 1060 | |true 1061 | 1062 | |BULLETTRAIN_CAR_K8S_TEMPLATE 1063 | |The car's template. 1064 | |`{{.Icon \| printf " %s " \| c}}{{.Context \| c}}` 1065 | 1066 | |BULLETTRAIN_CAR_K8S_PAINT 1067 | |Colour override for the car's paint. 1068 | |white+h:yellow 1069 | 1070 | |BULLETTRAIN_CAR_K8S_SYMBOL_ICON 1071 | |Icon displayed on the car. 1072 | |`\uFD31` ﴱ 1073 | 1074 | |BULLETTRAIN_CAR_K8S_SEPARATOR_PAINT 1075 | |Colour override for the car's right-hand side separator paint. 1076 | |Using default painting algorythm. 1077 | 1078 | |BULLETTRAIN_CAR_K8S_SEPARATOR_SYMBOL 1079 | |Override the car's right-hand side separator symbol. 1080 | |Using global symbol. 1081 | 1082 | |BULLETTRAIN_CAR_K8S_SEPARATOR_TEMPLATE 1083 | |Defines the car separator's template. 1084 | |Using global template. 1085 | 1086 | |=== 1087 | 1088 | == Development 1089 | 1090 | == Managing dependencies 1091 | 1092 | We use `go dep` as it is now production ready. 1093 | 1094 | https://github.com/golang/dep 1095 | 1096 | == Plugins 1097 | 1098 | We support link:docs/creating-new-cars.md[native Go cars] compiled right into the prompt builder, or ones link:docs/creating-plugin-car.md[written in other languages] . Plugins written in other languages will still benefit (to a degree) from Go's parallel execution. 1099 | 1100 | == Benchmarking 1101 | 1102 | We not only want the prompt to be super sexy but also super snappy. What'd be the point writting it in Go?! :) 1103 | 1104 | So to bluntly benchmark it's speed, build the executable and then sample a 10x batch 5 times like this in ZSH: 1105 | 1106 | .... 1107 | $ go build bullettrain.go 1108 | $ repeat 5 (time (repeat 10 ./bullettrain > /dev/null)) 1109 | ( repeat 10; do; ./bullettrain > /dev/null; done; ) 0.48s user 0.16s system 107% cpu 0.590 total 1110 | ( repeat 10; do; ./bullettrain > /dev/null; done; ) 0.48s user 0.14s system 107% cpu 0.581 total 1111 | ( repeat 10; do; ./bullettrain > /dev/null; done; ) 0.51s user 0.15s system 107% cpu 0.615 total 1112 | ( repeat 10; do; ./bullettrain > /dev/null; done; ) 0.49s user 0.17s system 107% cpu 0.613 total 1113 | ( repeat 10; do; ./bullettrain > /dev/null; done; ) 0.51s user 0.17s system 107% cpu 0.625 total 1114 | .... 1115 | 1116 | Be sure to benchmark your code to make sure you are not introducing a feature that will make the prompt sluggish all of a sudden. 1117 | 1118 | == Support 1119 | 1120 | https://t.me/joinchat/GUPcrhEYq_KJI48VIxxgsQ[Telegram Group] 1121 | 1122 | == FAQ 1123 | 1124 | *Q: Why don't we use BULLETTRAIN_CARS to disable the unwanted cars?* 1125 | 1126 | *A:* As Go is statically linked and every car you need, needs to be compiled into the single executable, setting a single env var won't change the size of it, though will change the speed of execution somewhat. The main reason is that people like to see a variable doing a single job and doing it well. Therefore you are capable of not loading the cars by the `BULLETTRAIN_CARS` variable, but the `_SHOW` suffixed ones are really tasked doing just that. 1127 | 1128 | == Issues 1129 | 1130 | Have a look at https://github.com/bullettrain-sh/bullettrain-go-core/issues?q=is%3Aissue+is%3Aopen+label%3Abug[the issues labeled as bugs] 1131 | 1132 | == Credits 1133 | 1134 | This theme is highly inspired by the following themes: 1135 | 1136 | * https://github.com/jeremyFreeAgent/oh-my-zsh-powerline-theme[Powerline] 1137 | * https://gist.github.com/agnoster/3712874[Agnoster] 1138 | -------------------------------------------------------------------------------- /build-local.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o nounset 4 | set -o errexit 5 | 6 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 7 | cd ${DIR} 8 | 9 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-s -w" -a -o ~/.local/bin/bullettrain 10 | -------------------------------------------------------------------------------- /bullettrain.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "log" 7 | "os" 8 | "os/user" 9 | "regexp" 10 | "strings" 11 | "text/template" 12 | 13 | "github.com/bullettrain-sh/bullettrain-go-core/src/ansi" 14 | "github.com/bullettrain-sh/bullettrain-go-core/src/car/custom" 15 | ) 16 | 17 | type carRenderer interface { 18 | // Render builds and passes the end product of a completely composed car onto 19 | // the channel. 20 | Render(out chan<- string) 21 | 22 | // GetPaint returns the calculated end paint string for the car. 23 | GetPaint() string 24 | 25 | // CanShow decides if this car needs to be displayed. 26 | CanShow() bool 27 | 28 | // GetSeparatorPaint overrides the Fg/Bg colours of the right hand side 29 | // separator through ENV variable. 30 | GetSeparatorPaint() string 31 | 32 | // GetSeparatorSymbol overrides the symbol of the right hand side 33 | // separator through ENV variable. 34 | GetSeparatorSymbol() string 35 | 36 | // GetSeparatorTemplate overrides the template of the right hand side 37 | // separator through ENV variable. 38 | GetSeparatorTemplate() string 39 | } 40 | 41 | type separator string 42 | 43 | // init defines some steps that affects running the program and needs provisioning. 44 | func init() { 45 | if err := os.Setenv("GOGC", "0"); err != nil { 46 | panic(err) 47 | } 48 | 49 | shell := os.Getenv("SHELL") 50 | if strings.HasSuffix(shell, "bash") { 51 | // 52 | } else if strings.HasSuffix(shell, "zsh") { 53 | // 54 | } else { 55 | panic("Unsupported shell") 56 | } 57 | 58 | if d := os.Getenv("BULLETTRAIN_NO_PAINT"); d == "true" { 59 | ansi.DisableColors(true) 60 | } 61 | } 62 | 63 | func main() { 64 | // List of cars available for use. 65 | trailers := carsToRender() 66 | 67 | // Create a channel for each car. 68 | noOfCars := len(trailers) * 2 69 | chans := make([]chan string, noOfCars) 70 | for i := range chans { 71 | chans[i] = make(chan string) 72 | } 73 | 74 | // Spin off a goroutine for each car. 75 | var lastSeparator bool 76 | paintFlipper := flipPaint() 77 | for j, k := 0, 0; j < noOfCars; j, k = j+2, k+1 { 78 | // Render car. 79 | go trailers[k].Render(chans[j]) 80 | 81 | // Render separator. 82 | var newPaint string 83 | if newPaint = trailers[k].GetSeparatorPaint(); newPaint == "" { 84 | lastSeparator = j+2 == noOfCars 85 | if lastSeparator { 86 | newPaint = paintFlipper( 87 | trailers[k].GetPaint(), 88 | "default:default") 89 | } else { 90 | newPaint = paintFlipper( 91 | trailers[k].GetPaint(), 92 | trailers[k+1].GetPaint()) 93 | } 94 | } 95 | 96 | sep := new(separator) 97 | go sep.Render(chans[j+1], newPaint, 98 | trailers[k].GetSeparatorSymbol(), 99 | trailers[k].GetSeparatorTemplate()) 100 | } 101 | 102 | var n bytes.Buffer 103 | n.WriteString(ansi.Reset) 104 | // Gather each goroutine's response through their channels, 105 | // keeping their order. 106 | for _, c := range chans { 107 | n.WriteString(<-c) 108 | } 109 | 110 | if l := os.Getenv("BULLETTRAIN_CARS_SEPARATE_LINE"); l != "false" { 111 | n.WriteRune('\n') 112 | } 113 | 114 | n.WriteString(lineEnding()) 115 | 116 | if d := os.Getenv("BULLETTRAIN_DEBUG"); d == "true" { 117 | fmt.Printf("%+ x", n.String()) 118 | fmt.Println("") 119 | fmt.Printf("%+q", n.String()) 120 | } else { 121 | fmt.Println("") 122 | fmt.Print(n.String()) 123 | } 124 | } 125 | 126 | // pwd returns the current directory path. 127 | func pwd() string { 128 | pwd, _ := os.Getwd() 129 | return pwd 130 | } 131 | 132 | func carsOrder() (o []string) { 133 | if envOrder := os.Getenv("BULLETTRAIN_CARS"); envOrder == "" { 134 | o = strings.Fields(defaultCarOrder) 135 | } else { 136 | o = strings.Fields(envOrder) 137 | } 138 | 139 | return 140 | } 141 | 142 | func carsToRender() []carRenderer { 143 | trailers := trailers(pwd()) 144 | 145 | var carsToRender []carRenderer 146 | for _, car := range carsOrder() { 147 | c, ex := trailers[car] 148 | if ex { 149 | if c.CanShow() { 150 | carsToRender = append(carsToRender, c) 151 | } 152 | } else { 153 | customCar := new(carCustom.Car) 154 | customCar.SetCallword(car) 155 | if customCar.CanShow() { 156 | carsToRender = append(carsToRender, customCar) 157 | } 158 | } 159 | } 160 | 161 | return carsToRender 162 | } 163 | 164 | func lineEnding() string { 165 | u, e := user.Current() 166 | if e != nil { 167 | log.Fatalf("Can't figure out current username: %s\n", e.Error()) 168 | } 169 | 170 | var c, s, t string 171 | if u.Username == "root" { 172 | if s = os.Getenv("BULLETTRAIN_PROMPT_CHAR_ROOT"); s == "" { 173 | s = "#" 174 | } 175 | 176 | if c = os.Getenv("BULLETTRAIN_PROMPT_CHAR_ROOT_PAINT"); c == "" { 177 | c = "red" 178 | } 179 | 180 | if t = os.Getenv("BULLETTRAIN_PROMPT_CHAR_ROOT_TEMPLATE"); t == "" { 181 | t = promptCharTemplate 182 | } 183 | } else { 184 | if s = os.Getenv("BULLETTRAIN_PROMPT_CHAR"); s == "" { 185 | s = "$" 186 | } 187 | 188 | if c = os.Getenv("BULLETTRAIN_PROMPT_CHAR_PAINT"); c == "" { 189 | c = "green" 190 | } 191 | 192 | if t = os.Getenv("BULLETTRAIN_PROMPT_CHAR_TEMPLATE"); t == "" { 193 | t = promptCharTemplate 194 | } 195 | } 196 | 197 | funcMap := template.FuncMap{ 198 | // Pipeline function for colouring. 199 | "c": func(t string) string { return ansi.Color(t, c) }, 200 | } 201 | 202 | tpl := template.Must(template.New("promptChar").Funcs(funcMap).Parse(t)) 203 | d := struct{ Icon string }{Icon: s} 204 | symbolFromTpl := new(bytes.Buffer) 205 | err := tpl.Execute(symbolFromTpl, d) 206 | if err != nil { 207 | log.Fatalf("Can't generate the prompt char template: %s", err.Error()) 208 | } 209 | 210 | return symbolFromTpl.String() + ansi.Reset 211 | } 212 | 213 | // flipPaint flips the FG and BG setup in colour strings of cars for a separator. 214 | // Use it as a closure. 215 | func flipPaint() func(string, string) string { 216 | // foregroundColor+attributes:backgroundColor+attributes 217 | // language=GoRegExp 218 | colourExp := regexp.MustCompile(`\w*\+?\w*:?(\w*)\+?\w?`) 219 | 220 | flipped := func(currentPaint, nextPaint string) string { 221 | currentParts := colourExp.FindStringSubmatch(currentPaint) 222 | nextParts := colourExp.FindStringSubmatch(nextPaint) 223 | 224 | newFg := "default" 225 | if len(currentParts) == 2 && currentParts[1] != "" { 226 | newFg = currentParts[1] 227 | } 228 | 229 | newBg := "default" 230 | if len(nextParts) == 2 && nextParts[1] != "" { 231 | newBg = nextParts[1] 232 | } 233 | 234 | return fmt.Sprintf("%s:%s", newFg, newBg) 235 | } 236 | 237 | return flipped 238 | } 239 | 240 | func (s *separator) Render(out chan<- string, paint, symbolOverride, templateOverride string) { 241 | defer close(out) 242 | 243 | var symbol string 244 | if symbolOverride != "" { 245 | symbol = symbolOverride 246 | } else if symbol = os.Getenv("BULLETTRAIN_SEPARATOR_ICON"); symbol == "" { 247 | symbol = separatorSymbol 248 | } 249 | 250 | var t string 251 | if templateOverride != "" { 252 | t = templateOverride 253 | } else if t = os.Getenv("BULLETTRAIN_SEPARATOR_TEMPLATE"); t == "" { 254 | t = separatorTemplate 255 | } 256 | 257 | funcMap := template.FuncMap{ 258 | // Pipeline function for colouring. 259 | "c": func(t string) string { return ansi.Color(t, paint) }, 260 | } 261 | 262 | tpl := template.Must(template.New("separator").Funcs(funcMap).Parse(t)) 263 | d := struct{ Icon string }{Icon: symbol} 264 | symbolFromTpl := new(bytes.Buffer) 265 | err := tpl.Execute(symbolFromTpl, d) 266 | if err != nil { 267 | log.Fatalf("Can't generate the separator template: %s", err.Error()) 268 | } 269 | 270 | out <- symbolFromTpl.String() 271 | } 272 | -------------------------------------------------------------------------------- /check-race.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o nounset 4 | set -o errexit 5 | 6 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 7 | cd ${DIR} 8 | 9 | CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build --race -a -o ./bullettrain-race-test 10 | -------------------------------------------------------------------------------- /code_of_conduct.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributor Covenant Code of Conduct 3 | 4 | ## Our Pledge 5 | 6 | We as members, contributors, and leaders pledge to make participation in our 7 | community a harassment-free experience for everyone, regardless of age, body 8 | size, visible or invisible disability, ethnicity, sex characteristics, gender 9 | identity and expression, level of experience, education, socio-economic status, 10 | nationality, personal appearance, race, religion, or sexual identity 11 | and orientation. 12 | 13 | We pledge to act and interact in ways that contribute to an open, welcoming, 14 | diverse, inclusive, and healthy community. 15 | 16 | ## Our Standards 17 | 18 | Examples of behavior that contributes to a positive environment for our 19 | community include: 20 | 21 | * Demonstrating empathy and kindness toward other people 22 | * Being respectful of differing opinions, viewpoints, and experiences 23 | * Giving and gracefully accepting constructive feedback 24 | * Accepting responsibility and apologizing to those affected by our mistakes, 25 | and learning from the experience 26 | * Focusing on what is best not just for us as individuals, but for the 27 | overall community 28 | 29 | Examples of unacceptable behavior include: 30 | 31 | * The use of sexualized language or imagery, and sexual attention or 32 | advances of any kind 33 | * Trolling, insulting or derogatory comments, and personal or political attacks 34 | * Public or private harassment 35 | * Publishing others' private information, such as a physical or email 36 | address, without their explicit permission 37 | * Other conduct which could reasonably be considered inappropriate in a 38 | professional setting 39 | 40 | ## Enforcement Responsibilities 41 | 42 | Community leaders are responsible for clarifying and enforcing our standards of 43 | acceptable behavior and will take appropriate and fair corrective action in 44 | response to any behavior that they deem inappropriate, threatening, offensive, 45 | or harmful. 46 | 47 | Community leaders have the right and responsibility to remove, edit, or reject 48 | comments, commits, code, wiki edits, issues, and other contributions that are 49 | not aligned to this Code of Conduct, and will communicate reasons for moderation 50 | decisions when appropriate. 51 | 52 | ## Scope 53 | 54 | This Code of Conduct applies within all community spaces, and also applies when 55 | an individual is officially representing the community in public spaces. 56 | Examples of representing our community include using an official e-mail address, 57 | posting via an official social media account, or acting as an appointed 58 | representative at an online or offline event. 59 | 60 | ## Enforcement 61 | 62 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 63 | reported to the community leaders responsible for enforcement at 64 | vad.viktor+bullettrain-coc@gmail.com. 65 | All complaints will be reviewed and investigated promptly and fairly. 66 | 67 | All community leaders are obligated to respect the privacy and security of the 68 | reporter of any incident. 69 | 70 | ## Enforcement Guidelines 71 | 72 | Community leaders will follow these Community Impact Guidelines in determining 73 | the consequences for any action they deem in violation of this Code of Conduct: 74 | 75 | ### 1. Correction 76 | 77 | **Community Impact**: Use of inappropriate language or other behavior deemed 78 | unprofessional or unwelcome in the community. 79 | 80 | **Consequence**: A private, written warning from community leaders, providing 81 | clarity around the nature of the violation and an explanation of why the 82 | behavior was inappropriate. A public apology may be requested. 83 | 84 | ### 2. Warning 85 | 86 | **Community Impact**: A violation through a single incident or series 87 | of actions. 88 | 89 | **Consequence**: A warning with consequences for continued behavior. No 90 | interaction with the people involved, including unsolicited interaction with 91 | those enforcing the Code of Conduct, for a specified period of time. This 92 | includes avoiding interactions in community spaces as well as external channels 93 | like social media. Violating these terms may lead to a temporary or 94 | permanent ban. 95 | 96 | ### 3. Temporary Ban 97 | 98 | **Community Impact**: A serious violation of community standards, including 99 | sustained inappropriate behavior. 100 | 101 | **Consequence**: A temporary ban from any sort of interaction or public 102 | communication with the community for a specified period of time. No public or 103 | private interaction with the people involved, including unsolicited interaction 104 | with those enforcing the Code of Conduct, is allowed during this period. 105 | Violating these terms may lead to a permanent ban. 106 | 107 | ### 4. Permanent Ban 108 | 109 | **Community Impact**: Demonstrating a pattern of violation of community 110 | standards, including sustained inappropriate behavior, harassment of an 111 | individual, or aggression toward or disparagement of classes of individuals. 112 | 113 | **Consequence**: A permanent ban from any sort of public interaction within 114 | the community. 115 | 116 | ## Attribution 117 | 118 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 119 | version 2.0, available at 120 | [https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0]. 121 | 122 | Community Impact Guidelines were inspired by 123 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 124 | 125 | For answers to common questions about this code of conduct, see the FAQ at 126 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available 127 | at [https://www.contributor-covenant.org/translations][translations]. 128 | 129 | [homepage]: https://www.contributor-covenant.org 130 | [v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html 131 | [Mozilla CoC]: https://github.com/mozilla/diversity 132 | [FAQ]: https://www.contributor-covenant.org/faq 133 | [translations]: https://www.contributor-covenant.org/translations 134 | -------------------------------------------------------------------------------- /defaults.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/bullettrain-sh/bullettrain-go-core/src/car/date" 5 | "github.com/bullettrain-sh/bullettrain-go-core/src/car/directory" 6 | "github.com/bullettrain-sh/bullettrain-go-core/src/car/git" 7 | "github.com/bullettrain-sh/bullettrain-go-core/src/car/golang" 8 | "github.com/bullettrain-sh/bullettrain-go-core/src/car/host" 9 | "github.com/bullettrain-sh/bullettrain-go-core/src/car/kubernetes" 10 | "github.com/bullettrain-sh/bullettrain-go-core/src/car/nodejs" 11 | "github.com/bullettrain-sh/bullettrain-go-core/src/car/openvpn" 12 | "github.com/bullettrain-sh/bullettrain-go-core/src/car/os" 13 | "github.com/bullettrain-sh/bullettrain-go-core/src/car/php" 14 | "github.com/bullettrain-sh/bullettrain-go-core/src/car/python" 15 | "github.com/bullettrain-sh/bullettrain-go-core/src/car/ruby" 16 | "github.com/bullettrain-sh/bullettrain-go-core/src/car/status" 17 | "github.com/bullettrain-sh/bullettrain-go-core/src/car/time" 18 | "github.com/bullettrain-sh/bullettrain-go-core/src/car/user" 19 | ) 20 | 21 | const ( 22 | defaultCarOrder = "os time date user host dir python go ruby nodejs php git status" 23 | separatorSymbol = "\uE0B0" //  24 | // language=GoTemplate 25 | separatorTemplate = `{{.Icon | printf "%s " | c}}` 26 | // language=GoTemplate 27 | promptCharTemplate = `{{.Icon | printf "%s " | c}}` 28 | ) 29 | 30 | // trailers results in the list of cars to be available for use. 31 | func trailers(currentWorkingDir string) map[string]carRenderer { 32 | return map[string]carRenderer{ 33 | "user": &carUser.Car{}, 34 | "host": &carHost.Car{}, 35 | "date": &carDate.Car{}, 36 | "dir": &carDirectory.Car{Pwd: currentWorkingDir}, 37 | "git": &carGit.Car{Pwd: currentWorkingDir}, 38 | "go": &carGo.Car{Pwd: currentWorkingDir}, 39 | "nodejs": &carNodejs.Car{Pwd: currentWorkingDir}, 40 | "os": &carOs.Car{}, 41 | "status": &carStatus.Car{}, 42 | "openvpn": &carOpenvpn.Car{}, 43 | "time": &carTime.Car{}, 44 | "php": &carPhp.Car{Pwd: currentWorkingDir}, 45 | "python": &carPython.Car{Pwd: currentWorkingDir}, 46 | "ruby": &carRuby.Car{Pwd: currentWorkingDir}, 47 | "k8s": &carK8s.Car{Pwd: currentWorkingDir}, 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /docs/creating-new-cars.md: -------------------------------------------------------------------------------- 1 | # How to create a new car in Go 2 | 3 | This document will describe the process to add new car plugin in Go. 4 | 5 | ## Naming 6 | 7 | The car needs to have a fitting package name that matches the current 8 | convention: `carSomeName` The name is prefixed by `car`. 9 | 10 | ## Template: 11 | 12 | You can have a look at the very simple 13 | [timeCar](../src/car/time/time.go), it shows basic implementation of the 14 | `carRenderer` interface. 15 | 16 | When you may want to just permanently change symbol or paint colours, 17 | you can simply change the constants on the top level and compile your 18 | custom version: 19 | 20 | ```go 21 | const ( 22 | carPaint = "black:white" 23 | symbolIcon = "" 24 | symbolPaint = "black:white" 25 | carTemplate = `{{.Icon | printf "%s " | cs}}{{.Time | c}}` 26 | ) 27 | ``` 28 | 29 | To use your car, this is a checklist to be done in 30 | [bullettrain-go-core/defaults.go](../defaults.go): 31 | 32 | * add you package path to the imports 33 | * add your car's instance to the `trailers` function 34 | * add your car's callword to the list in the `defaultCarOrder` constant 35 | 36 | ## Documenting cars 37 | 38 | The `README` should also be as detailed as the ones already existing. 39 | Each and every env variable must be documented with default values and 40 | description. 41 | -------------------------------------------------------------------------------- /docs/creating-plugin-car.md: -------------------------------------------------------------------------------- 1 | # How to create a new car in virtually any language 2 | 3 | This document will describe the process to add new car plugin in 4 | virtually any language. 5 | 6 | Let me drive you through the feature with a simple example. 7 | 8 | Name of the plugin: `demo` 9 | 10 | ## Env variable naming 11 | 12 | prefix: `BULLETTRAIN_CAR_PLUGIN_` 13 | 14 | suffixes: 15 | 16 | | Suffix | Description | Default value | Mandatory | 17 | |:----------------------|:--------------------------------------------------------------------------------------|:-------------------|:----------| 18 | | `_CMD` | Command with it's arguments to be executed to generate text onto STDOUT. | Creator defined. | yes | 19 | | `_SHOW` | Used by the creator to determine if the plugin should be shown in the current render. | true | no | 20 | | `_PAINT` | Colours of the car. | Creator defined. | yes | 21 | | `_TEMPLATE` | Template of the car. | Creator defined. | yes | 22 | | `_SYMBOL_ICON` | Symbol to be displayed on the left hand side of the car. | Creator defined. | yes | 23 | | `_SYMBOL_PAINT` | Colours of the symbol. | Creator defined. | yes | 24 | | `_SEPARATOR_SYMBOL` | Override the right hand side separator's symbol. | Algorythm defined. | no | 25 | | `_SEPARATOR_TEMPLATE` | Override the right hand side separator's template. | Algorythm defined. | no | 26 | | `_SEPARATOR_PAINT` | Override the right hand side separator's colours. | Algorythm defined. | no | 27 | 28 | Example: 29 | 30 | `BULLETTRAIN_CAR_PLUGIN_DEMO_SHOW` 31 | 32 | ## _CMD variable explained 33 | 34 | In our example, the `BULLETTRAIN_CAR_PLUGIN_DEMO_CMD` variable will be 35 | the heart of the 3rd party plugin. Here the user will put a very simple 36 | command with it's parameters if needed. 37 | 38 | **Warning!** No pipes, redirections or space delimited arguments are 39 | supported here to avoid shell subtle differences. 40 | 41 | The command has to generate a simple text to the STDOUT. Error handling 42 | is the responsibility of the 3rd party script. 43 | 44 | **Tip** to create complex scripts, use source files, e.g. `ruby somefile.rb` 45 | 46 | ## _TEMPLATE variuable explained 47 | 48 | 49 | {{.Icon}} 50 | {{.Info}} 51 | 52 | `c` 53 | `cs` 54 | 55 | ### Example ZSH script as a car: 56 | 57 | Car name: `demo_vpn` 58 | 59 | Put it into the list of cars: 60 | 61 | ```shell 62 | export BULLETTRAIN_CARS="os time date user host dir python go ruby nodejs php git demo_vpn status" 63 | ``` 64 | 65 | `demo_vpn.sh`: 66 | 67 | Checks if openvpn has been connected, by checking if any tun device can 68 | be listed. 69 | 70 | ```shell 71 | #!/bin/zsh 72 | 73 | if [[ $(ip tuntap list | wc -l) -gt 0 ]]; then 74 | echo -n "up" 75 | else 76 | echo -n "down" 77 | fi 78 | ``` 79 | 80 | Mandatory env variables: 81 | 82 | ```shell 83 | export BULLETTRAIN_CAR_PLUGIN_DEMO_VPN_CMD="/home/ikon/demo_vpn.sh" 84 | export BULLETTRAIN_CAR_PLUGIN_DEMO_VPN_PAINT="yellow:black" 85 | export BULLETTRAIN_CAR_PLUGIN_DEMO_VPN_SYMBOL_ICON="  " 86 | export BULLETTRAIN_CAR_PLUGIN_DEMO_VPN_SYMBOL_PAINT="yellow:black" 87 | ``` 88 | 89 | After defining the rest of the needed env variables, the output should 90 | be displayed as a new car at the end. 91 | 92 | ![output](../readme_assets/3rdparty_drop-shadow.png) 93 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/bullettrain-sh/bullettrain-go-core 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= 2 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 3 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= 4 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 5 | -------------------------------------------------------------------------------- /readme_assets/3rdparty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vadviktor/bullettrain-go-core/844a5588718cdfbd5c07ac9049b57b83221f63b1/readme_assets/3rdparty.png -------------------------------------------------------------------------------- /readme_assets/3rdparty_drop-shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vadviktor/bullettrain-go-core/844a5588718cdfbd5c07ac9049b57b83221f63b1/readme_assets/3rdparty_drop-shadow.png -------------------------------------------------------------------------------- /readme_assets/ansi256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vadviktor/bullettrain-go-core/844a5588718cdfbd5c07ac9049b57b83221f63b1/readme_assets/ansi256.png -------------------------------------------------------------------------------- /readme_assets/ansi256_drop-shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vadviktor/bullettrain-go-core/844a5588718cdfbd5c07ac9049b57b83221f63b1/readme_assets/ansi256_drop-shadow.png -------------------------------------------------------------------------------- /readme_assets/preview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vadviktor/bullettrain-go-core/844a5588718cdfbd5c07ac9049b57b83221f63b1/readme_assets/preview.jpg -------------------------------------------------------------------------------- /readme_assets/preview_drop-shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vadviktor/bullettrain-go-core/844a5588718cdfbd5c07ac9049b57b83221f63b1/readme_assets/preview_drop-shadow.png -------------------------------------------------------------------------------- /release: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | rm -rfv ./releases/* 4 | 5 | # go tool dist list 6 | 7 | # Raspberry pi 1? 8 | CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=6 go build -ldflags "-s -w" -o ./releases/bullettrain.linux-armv6 9 | sha256sum -b ./releases/bullettrain.linux-armv6 > ./releases/bullettrain.linux-armv6.sha256 10 | 11 | # Raspberry pi 2? 12 | CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 go build -ldflags "-s -w" -o ./releases/bullettrain.linux-armv7 13 | sha256sum -b ./releases/bullettrain.linux-armv7 > ./releases/bullettrain.linux-armv7.sha256 14 | 15 | # Raspberry pi 3 16 | CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags "-s -w" -o ./releases/bullettrain.linux-arm64 17 | sha256sum -b ./releases/bullettrain.linux-arm64 > ./releases/bullettrain.linux-arm64.sha256 18 | 19 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-s -w" -o ./releases/bullettrain.linux-amd64 20 | sha256sum -b ./releases/bullettrain.linux-amd64 > ./releases/bullettrain.linux-amd64.sha256 21 | 22 | ## Mac 23 | CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "-s -w" -o ./releases/bullettrain.darwin-amd64 24 | sha256sum -b ./releases/bullettrain.darwin-amd64 > ./releases/bullettrain.darwin-amd64.sha256 25 | -------------------------------------------------------------------------------- /releases/.gitkeep: -------------------------------------------------------------------------------- 1 | Output directory for release builds. 2 | -------------------------------------------------------------------------------- /src/ansi/ansi.go: -------------------------------------------------------------------------------- 1 | package ansi 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "os" 7 | "strconv" 8 | "strings" 9 | ) 10 | 11 | const ( 12 | black = iota 13 | red 14 | green 15 | yellow 16 | blue 17 | magenta 18 | cyan 19 | white 20 | defaultt = 9 21 | 22 | normalIntensityFG = 30 23 | highIntensityFG = 90 24 | normalIntensityBG = 40 25 | highIntensityBG = 100 26 | 27 | bold = "1;" 28 | italic = "3;" 29 | underline = "4;" 30 | blink = "5;" 31 | inverse = "7;" 32 | strikethrough = "9;" 33 | ) 34 | 35 | var ( 36 | plain bool 37 | 38 | Start string 39 | End string 40 | Reset string 41 | 42 | // Colors maps common color names to their ANSI color code. 43 | Colors = map[string]int{ 44 | "black": black, 45 | "red": red, 46 | "green": green, 47 | "yellow": yellow, 48 | "blue": blue, 49 | "magenta": magenta, 50 | "cyan": cyan, 51 | "white": white, 52 | "default": defaultt, 53 | } 54 | ) 55 | 56 | func init() { 57 | for i := 0; i < 256; i++ { 58 | Colors[strconv.Itoa(i)] = i 59 | } 60 | 61 | shell := os.Getenv("SHELL") 62 | if strings.HasSuffix(shell, "bash") { 63 | Start = "\\[\033[" 64 | End = "\\]" 65 | Reset = "\\[\033[0m\\]" 66 | } else if strings.HasSuffix(shell, "zsh") { 67 | Start = "%{\033[" 68 | End = "%}" 69 | Reset = "%{\033[0m%}" 70 | } 71 | } 72 | 73 | // Gets the ANSI color code for a style. 74 | func colorCode(style string) *bytes.Buffer { 75 | buf := bytes.NewBufferString("") 76 | if plain || style == "" { 77 | return buf 78 | } 79 | if style == "reset" { 80 | buf.WriteString(Reset) 81 | return buf 82 | } else if style == "off" { 83 | return buf 84 | } 85 | 86 | foregroundBackground := strings.Split(style, ":") 87 | foreground := strings.Split(foregroundBackground[0], "+") 88 | fgKey := foreground[0] 89 | fg := Colors[fgKey] 90 | fgStyle := "" 91 | if len(foreground) > 1 { 92 | fgStyle = foreground[1] 93 | } 94 | 95 | var bg, bgStyle string 96 | if len(foregroundBackground) > 1 { 97 | background := strings.Split(foregroundBackground[1], "+") 98 | bg = background[0] 99 | if len(background) > 1 { 100 | bgStyle = background[1] 101 | } 102 | } 103 | 104 | buf.WriteString(Start) 105 | base := normalIntensityFG 106 | if len(fgStyle) > 0 { 107 | if strings.Contains(fgStyle, "b") { 108 | buf.WriteString(bold) 109 | } 110 | if strings.Contains(fgStyle, "B") { 111 | buf.WriteString(blink) 112 | } 113 | if strings.Contains(fgStyle, "u") { 114 | buf.WriteString(underline) 115 | } 116 | if strings.Contains(fgStyle, "I") { 117 | buf.WriteString(italic) 118 | } 119 | if strings.Contains(fgStyle, "i") { 120 | buf.WriteString(inverse) 121 | } 122 | if strings.Contains(fgStyle, "s") { 123 | buf.WriteString(strikethrough) 124 | } 125 | if strings.Contains(fgStyle, "h") { 126 | base = highIntensityFG 127 | } 128 | } 129 | 130 | // if 256-color 131 | n, err := strconv.Atoi(fgKey) 132 | if err == nil { 133 | fmt.Fprintf(buf, "38;5;%d;", n) 134 | } else { 135 | fmt.Fprintf(buf, "%d;", base+fg) 136 | } 137 | 138 | base = normalIntensityBG 139 | if len(bg) > 0 { 140 | if strings.Contains(bgStyle, "h") { 141 | base = highIntensityBG 142 | } 143 | // if 256-color 144 | n, err := strconv.Atoi(bg) 145 | if err == nil { 146 | fmt.Fprintf(buf, "48;5;%d;", n) 147 | } else { 148 | fmt.Fprintf(buf, "%d;", base+Colors[bg]) 149 | } 150 | } 151 | 152 | // remove last ";" 153 | buf.Truncate(buf.Len() - 1) 154 | buf.WriteRune('m') 155 | buf.WriteString(End) 156 | return buf 157 | } 158 | 159 | // Color colors a string based on the ANSI color code for style. 160 | func Color(s, style string) string { 161 | if plain || len(style) < 1 { 162 | return s 163 | } 164 | buf := colorCode(style) 165 | buf.WriteString(s) 166 | buf.WriteString(Reset) 167 | return buf.String() 168 | } 169 | 170 | // DisableColors disables ANSI color codes. The default is false (colors are on). 171 | func DisableColors(disable bool) { 172 | plain = disable 173 | } 174 | -------------------------------------------------------------------------------- /src/car/custom/custom.go: -------------------------------------------------------------------------------- 1 | package carCustom 2 | 3 | import ( 4 | "bytes" 5 | "log" 6 | "os" 7 | "os/exec" 8 | "strings" 9 | "text/template" 10 | 11 | "github.com/bullettrain-sh/bullettrain-go-core/src/ansi" 12 | ) 13 | 14 | // Custom car 15 | type Car struct { 16 | // Uppercase 17 | callword string 18 | pwd string 19 | } 20 | 21 | // SetCallword makes sure the callword is in the right shape. 22 | func (c *Car) SetCallword(word string) { 23 | c.callword = strings.ToUpper(word) 24 | } 25 | 26 | // GetPaint returns the calculated end paint string for the car. 27 | func (c *Car) GetPaint() string { 28 | return os.Getenv("BULLETTRAIN_CAR_PLUGIN_" + c.callword + "_PAINT") 29 | } 30 | 31 | // CanShow decides if this car needs to be displayed. 32 | func (c *Car) CanShow() bool { 33 | s := true 34 | if e := os.Getenv("BULLETTRAIN_CAR_PLUGIN_" + c.callword + "_SHOW"); e == "false" { 35 | s = false 36 | } 37 | 38 | return s 39 | } 40 | 41 | // Render builds and passes the end product of a completely composed car onto 42 | // the channel. 43 | func (c *Car) Render(out chan<- string) { 44 | defer close(out) 45 | 46 | var stuff string 47 | carSymbol := os.Getenv("BULLETTRAIN_CAR_PLUGIN_" + c.callword + "_SYMBOL_ICON") 48 | carSymbolPaint := os.Getenv("BULLETTRAIN_CAR_PLUGIN_" + c.callword + "_SYMBOL_PAINT") 49 | carTemplate := os.Getenv("BULLETTRAIN_CAR_PLUGIN_" + c.callword + "_TEMPLATE") 50 | 51 | cmdElem := strings.Fields( 52 | os.Getenv("BULLETTRAIN_CAR_PLUGIN_" + c.callword + "_CMD")) 53 | 54 | var cmd *exec.Cmd 55 | if len(cmdElem) < 2 { 56 | cmd = exec.Command(cmdElem[0]) 57 | } else { 58 | cmd = exec.Command(cmdElem[0], cmdElem[1:]...) 59 | } 60 | 61 | cmdOut, err := cmd.CombinedOutput() 62 | if err == nil { 63 | stuff = string(cmdOut) 64 | } else { 65 | stuff = err.Error() 66 | } 67 | 68 | funcMap := template.FuncMap{ 69 | // Pipeline functions for colouring. 70 | "c": func(t string) string { return ansi.Color(t, c.GetPaint()) }, 71 | "cs": func(t string) string { return ansi.Color(t, carSymbolPaint) }, 72 | } 73 | 74 | tpl := template.Must(template.New(c.callword).Funcs(funcMap).Parse(carTemplate)) 75 | data := struct { 76 | Icon string 77 | Info string 78 | }{Icon: carSymbol, Info: stuff} 79 | fromTpl := new(bytes.Buffer) 80 | tplErr := tpl.Execute(fromTpl, data) 81 | if tplErr != nil { 82 | log.Fatalf("Can't generate the user template: %s", tplErr.Error()) 83 | } 84 | 85 | out <- fromTpl.String() 86 | } 87 | 88 | // GetSeparatorPaint overrides the Fg/Bg colours of the right hand side 89 | // separator through ENV variables. 90 | func (c *Car) GetSeparatorPaint() string { 91 | return os.Getenv("BULLETTRAIN_CAR_PLUGIN_" + c.callword + "_SEPARATOR_PAINT") 92 | } 93 | 94 | // GetSeparatorSymbol overrides the symbol of the right hand side 95 | // separator through ENV variables 96 | func (c *Car) GetSeparatorSymbol() string { 97 | return os.Getenv("BULLETTRAIN_CAR_PLUGIN_" + c.callword + "_SEPARATOR_SYMBOL") 98 | } 99 | 100 | // GetSeparatorTemplate overrides the template of the right hand side 101 | // separator through ENV variable. 102 | func (c *Car) GetSeparatorTemplate() string { 103 | return os.Getenv("BULLETTRAIN_CAR_PLUGIN_" + c.callword + "_SEPARATOR_TEMPLATE") 104 | } 105 | -------------------------------------------------------------------------------- /src/car/date/date.go: -------------------------------------------------------------------------------- 1 | package carDate 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "log" 7 | "os" 8 | "text/template" 9 | "time" 10 | 11 | "github.com/bullettrain-sh/bullettrain-go-core/src/ansi" 12 | ) 13 | 14 | const ( 15 | carPaint = "white:black" 16 | symbolIcon = "\uF073" //  17 | symbolPaint = "red:black" 18 | // language=GoTemplate 19 | carTemplate = `{{.Icon | printf "%s" | cs}}{{.Date | c}}` 20 | ) 21 | 22 | // Date car 23 | type Car struct { 24 | paint string 25 | } 26 | 27 | // GetPaint returns the calculated end paint string for the car. 28 | func (c *Car) GetPaint() string { 29 | if c.paint = os.Getenv("BULLETTRAIN_CAR_DATE_PAINT"); c.paint == "" { 30 | c.paint = carPaint 31 | } 32 | 33 | return c.paint 34 | } 35 | 36 | // CanShow decides if this car needs to be displayed. 37 | func (c *Car) CanShow() bool { 38 | s := false 39 | if e := os.Getenv("BULLETTRAIN_CAR_DATE_SHOW"); e == "true" { 40 | s = true 41 | } 42 | 43 | return s 44 | } 45 | 46 | // Render builds and passes the end product of a completely composed car onto 47 | // the channel. 48 | func (c *Car) Render(out chan<- string) { 49 | defer close(out) 50 | 51 | var dateSymbol string 52 | if dateSymbol = os.Getenv("BULLETTRAIN_CAR_DATE_SYMBOL_ICON"); dateSymbol == "" { 53 | dateSymbol = symbolIcon 54 | } 55 | 56 | var dateSymbolPaint string 57 | if dateSymbolPaint = os.Getenv("BULLETTRAIN_CAR_DATE_SYMBOL_PAINT"); dateSymbolPaint == "" { 58 | dateSymbolPaint = symbolPaint 59 | } 60 | 61 | y, m, d := time.Now().Date() 62 | dateText := fmt.Sprintf("%02d-%02d-%02d", y, m, d) 63 | 64 | var t string 65 | if t = os.Getenv("BULLETTRAIN_CAR_DATE_TEMPLATE"); t == "" { 66 | t = carTemplate 67 | } 68 | 69 | funcMap := template.FuncMap{ 70 | // Pipeline functions for colouring. 71 | "c": func(t string) string { return ansi.Color(t, c.GetPaint()) }, 72 | "cs": func(t string) string { return ansi.Color(t, dateSymbolPaint) }, 73 | } 74 | 75 | tpl := template.Must(template.New("date").Funcs(funcMap).Parse(t)) 76 | data := struct { 77 | Icon string 78 | Date string 79 | }{Icon: dateSymbol, Date: dateText} 80 | dateFromTpl := new(bytes.Buffer) 81 | err := tpl.Execute(dateFromTpl, data) 82 | if err != nil { 83 | log.Fatalf("Can't generate the date template: %s", err.Error()) 84 | } 85 | 86 | out <- dateFromTpl.String() 87 | } 88 | 89 | // GetSeparatorPaint overrides the Fg/Bg colours of the right hand side 90 | // separator through ENV variables. 91 | func (c *Car) GetSeparatorPaint() string { 92 | return os.Getenv("BULLETTRAIN_CAR_DATE_SEPARATOR_PAINT") 93 | } 94 | 95 | // GetSeparatorSymbol overrides the symbol of the right hand side 96 | // separator through ENV variables 97 | func (c *Car) GetSeparatorSymbol() string { 98 | return os.Getenv("BULLETTRAIN_CAR_DATE_SEPARATOR_SYMBOL") 99 | } 100 | 101 | // GetSeparatorTemplate overrides the template of the right hand side 102 | // separator through ENV variable. 103 | func (c *Car) GetSeparatorTemplate() string { 104 | return os.Getenv("BULLETTRAIN_CAR_DATE_SEPARATOR_TEMPLATE") 105 | } 106 | -------------------------------------------------------------------------------- /src/car/directory/dir.go: -------------------------------------------------------------------------------- 1 | package carDirectory 2 | 3 | import ( 4 | "os" 5 | "strconv" 6 | "strings" 7 | 8 | "github.com/bullettrain-sh/bullettrain-go-core/src/ansi" 9 | ) 10 | 11 | const ( 12 | carPaint = "white:blue" 13 | separatorSymbol = "/" 14 | ellipsisSymbol = "*" 15 | depthIndicator = "..." 16 | ) 17 | 18 | // Directory car 19 | type Car struct { 20 | paint string 21 | // Current directory 22 | Pwd string 23 | } 24 | 25 | // GetPaint returns the calculated end paint string for the car. 26 | func (c *Car) GetPaint() string { 27 | if c.paint = os.Getenv("BULLETTRAIN_CAR_DIRECTORY_PAINT"); c.paint == "" { 28 | c.paint = carPaint 29 | } 30 | 31 | return c.paint 32 | } 33 | 34 | // CanShow decides if this car needs to be displayed. 35 | func (c *Car) CanShow() bool { 36 | s := true 37 | if e := os.Getenv("BULLETTRAIN_CAR_DIRECTORY_SHOW"); e == "false" { 38 | s = false 39 | } 40 | 41 | return s 42 | } 43 | 44 | // Render builds and passes the end product of a completely composed car onto 45 | // the channel. 46 | func (c *Car) Render(out chan<- string) { 47 | defer close(out) 48 | 49 | out <- ansi.Color(rebuildDirForRender(c.Pwd), c.GetPaint()) 50 | } 51 | 52 | // rebuildDirForRender contains the main logic to rebuild the current path 53 | // according to the env variable settings, returning the final string version 54 | // that is ready to by printed to the screen. 55 | func rebuildDirForRender(directory string) string { 56 | var sep string 57 | if sep = os.Getenv("BULLETTRAIN_CAR_DIRECTORY_PATH_SEPARATOR"); sep == "" { 58 | sep = separatorSymbol 59 | } 60 | 61 | home := os.Getenv("HOME") 62 | if home != "" && strings.HasPrefix(directory, home) { 63 | directory = strings.Replace(directory, home, "~", 1) 64 | } 65 | 66 | directoryParts := strings.Split(directory, string(os.PathSeparator)) 67 | l := len(directoryParts) 68 | 69 | frontMaxLength := 2 70 | if e := os.Getenv("BULLETTRAIN_CAR_DIRECTORY_FRONT_MAX_LENGTH"); e != "" { 71 | if ml, err := strconv.ParseInt(e, 10, 32); err == nil { 72 | frontMaxLength = int(ml) 73 | } 74 | } 75 | 76 | tailMaxLength := 2 77 | if e := os.Getenv("BULLETTRAIN_CAR_DIRECTORY_TAIL_MAX_LENGTH"); e != "" { 78 | if ml, err := strconv.ParseInt(e, 10, 32); err == nil { 79 | tailMaxLength = int(ml) 80 | } 81 | } 82 | 83 | if l > frontMaxLength+tailMaxLength && frontMaxLength > 0 && tailMaxLength > 0 { 84 | var partsReconstruct []string 85 | 86 | abbreviateMode := "acronym" 87 | if e := os.Getenv("BULLETTRAIN_CAR_DIRECTORY_ABBREVIATE_MODE"); e != "" { 88 | abbreviateMode = e 89 | } 90 | 91 | partsReconstruct = append(partsReconstruct, directoryParts[0:frontMaxLength]...) 92 | 93 | switch abbreviateMode { 94 | case "merge": 95 | var di string 96 | if di = os.Getenv("BULLETTRAIN_CAR_DIRECTORY_DEPTH_INDICATOR"); di == "" { 97 | di = depthIndicator 98 | } 99 | partsReconstruct = append(partsReconstruct, di) 100 | default: 101 | var es string 102 | if es = os.Getenv("BULLETTRAIN_CAR_DIRECTORY_ELLIPSIS"); es == "" { 103 | es = ellipsisSymbol 104 | } 105 | 106 | for _, part := range directoryParts[frontMaxLength : len(directoryParts)-tailMaxLength] { 107 | if len(part) > 1 { 108 | partsReconstruct = append(partsReconstruct, part[:1]+es) 109 | } else { 110 | partsReconstruct = append(partsReconstruct, part) 111 | } 112 | } 113 | } 114 | 115 | partsReconstruct = append(partsReconstruct, directoryParts[len(directoryParts)-tailMaxLength:]...) 116 | 117 | return strings.Join(partsReconstruct, sep) 118 | } else { 119 | return strings.Join(directoryParts, sep) 120 | } 121 | } 122 | 123 | // GetSeparatorPaint overrides the Fg/Bg colours of the right hand side 124 | // separator through ENV variables. 125 | func (c *Car) GetSeparatorPaint() string { 126 | return os.Getenv("BULLETTRAIN_CAR_DIRECTORY_SEPARATOR_PAINT") 127 | } 128 | 129 | // GetSeparatorSymbol overrides the symbol of the right hand side 130 | // separator through ENV variables. 131 | func (c *Car) GetSeparatorSymbol() string { 132 | return os.Getenv("BULLETTRAIN_CAR_DIRECTORY_SEPARATOR_SYMBOL") 133 | } 134 | 135 | // GetSeparatorTemplate overrides the template of the right hand side 136 | // separator through ENV variable. 137 | func (c *Car) GetSeparatorTemplate() string { 138 | return os.Getenv("BULLETTRAIN_CAR_DIRECTORY_SEPARATOR_TEMPLATE") 139 | } 140 | -------------------------------------------------------------------------------- /src/car/directory/dir_test.go: -------------------------------------------------------------------------------- 1 | package carDirectory 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "testing" 7 | ) 8 | 9 | const failTpl = "Unexpected directory format.\nEXPECTED: %s\nGOT: %s" 10 | 11 | func setup() { 12 | os.Setenv("BULLETTRAIN_CAR_DIRECTORY_PATH_SEPARATOR", "\uF105") //  13 | os.Setenv("BULLETTRAIN_CAR_DIRECTORY_DEPTH_INDICATOR", "\uF141") //  14 | } 15 | 16 | func TestFullPath(t *testing.T) { 17 | setup() 18 | os.Setenv("BULLETTRAIN_CAR_DIRECTORY_FRONT_MAX_LENGTH", "-1") 19 | directory := filepath.Clean("/usr/share/doc/vpn/easy") 20 | expected := "usrsharedocvpneasy" 21 | 22 | actual := rebuildDirForRender(directory) 23 | 24 | if actual != expected { 25 | t.Logf(failTpl, expected, actual) 26 | t.Fail() 27 | } 28 | } 29 | 30 | func TestCustomPathSeparator(t *testing.T) { 31 | setup() 32 | sep := "|" 33 | os.Setenv("BULLETTRAIN_CAR_DIRECTORY_PATH_SEPARATOR", sep) 34 | os.Setenv("BULLETTRAIN_CAR_DIRECTORY_FRONT_MAX_LENGTH", "-1") 35 | directory := filepath.Clean("/usr/share/doc/vpn/easy") 36 | expected := "|usr|share|doc|vpn|easy" 37 | 38 | actual := rebuildDirForRender(directory) 39 | 40 | if actual != expected { 41 | t.Logf(failTpl, expected, actual) 42 | t.Fail() 43 | } 44 | } 45 | 46 | func TestMergeMode(t *testing.T) { 47 | setup() 48 | os.Setenv("BULLETTRAIN_CAR_DIRECTORY_FRONT_MAX_LENGTH", "2") 49 | os.Setenv("BULLETTRAIN_CAR_DIRECTORY_TAIL_MAX_LENGTH", "1") 50 | os.Setenv("BULLETTRAIN_CAR_DIRECTORY_ABBREVIATE_MODE", "merge") 51 | directory := filepath.Clean("/usr/share/doc/vpn/easy") 52 | expected := "usr...easy" 53 | 54 | actual := rebuildDirForRender(directory) 55 | 56 | if actual != expected { 57 | t.Logf(failTpl, expected, actual) 58 | t.Fail() 59 | } 60 | } 61 | 62 | func TestAcronymMode(t *testing.T) { 63 | setup() 64 | os.Setenv("BULLETTRAIN_CAR_DIRECTORY_FRONT_MAX_LENGTH", "2") 65 | os.Setenv("BULLETTRAIN_CAR_DIRECTORY_TAIL_MAX_LENGTH", "1") 66 | directory := filepath.Clean("/usr/share/doc/vpn/easy") 67 | expected := "usrs*d*v*easy" 68 | 69 | actual := rebuildDirForRender(directory) 70 | 71 | if actual != expected { 72 | t.Logf(failTpl, expected, actual) 73 | t.Fail() 74 | } 75 | } 76 | 77 | func TestCustomAcronymSymbol(t *testing.T) { 78 | setup() 79 | os.Setenv("BULLETTRAIN_CAR_DIRECTORY_ELLIPSIS", "~") 80 | os.Setenv("BULLETTRAIN_CAR_DIRECTORY_FRONT_MAX_LENGTH", "2") 81 | os.Setenv("BULLETTRAIN_CAR_DIRECTORY_TAIL_MAX_LENGTH", "1") 82 | directory := filepath.Clean(filepath.Clean("/usr/share/doc/vpn/easy")) 83 | expected := "usrs~d~v~easy" 84 | 85 | actual := rebuildDirForRender(directory) 86 | 87 | if actual != expected { 88 | t.Logf(failTpl, expected, actual) 89 | t.Fail() 90 | } 91 | } 92 | 93 | func TestCustomDepthIndicator(t *testing.T) { 94 | setup() 95 | os.Setenv("BULLETTRAIN_CAR_DIRECTORY_ABBREVIATE_MODE", "merge") 96 | os.Setenv("BULLETTRAIN_CAR_DIRECTORY_DEPTH_INDICATOR", "+++") 97 | directory := filepath.Clean(filepath.Clean("/usr/share/doc/vpn/easy")) 98 | expected := "usr+++vpneasy" 99 | 100 | actual := rebuildDirForRender(directory) 101 | 102 | if actual != expected { 103 | t.Logf(failTpl, expected, actual) 104 | t.Fail() 105 | } 106 | } 107 | 108 | func TestHomeDirectoryShorthand(t *testing.T) { 109 | setup() 110 | os.Setenv("BULLETTRAIN_CAR_DIRECTORY_FRONT_MAX_LENGTH", "-1") 111 | os.Setenv("HOME", filepath.Clean("/home/ikon")) 112 | directory := filepath.Clean("/home/ikon/doc/vpn/easy") 113 | expected := "~docvpneasy" 114 | 115 | actual := rebuildDirForRender(directory) 116 | 117 | if actual != expected { 118 | t.Logf(failTpl, expected, actual) 119 | t.Fail() 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/car/git/git.go: -------------------------------------------------------------------------------- 1 | package carGit 2 | 3 | import ( 4 | "bytes" 5 | "log" 6 | "os" 7 | "os/exec" 8 | "strings" 9 | "text/template" 10 | 11 | "github.com/bullettrain-sh/bullettrain-go-core/src/ansi" 12 | ) 13 | 14 | const ( 15 | carPaint = "black:white" 16 | gitSymbolPaint = "red:white" 17 | gitSymbolIcon = "\uE702" //  18 | gitDirtyPaint = "red:white" 19 | gitDirtyIcon = "\uF00D" //  20 | gitCleanPaint = "green:white" 21 | gitCleanIcon = "\uF632" //  22 | carTemplate = `{{.Icon | printf "%s " | cs}}{{.Name | c}}{{.StatusIcon | printf " %s" | csi}}` 23 | ) 24 | 25 | // Car for Git 26 | type Car struct { 27 | paint string 28 | // Current directory 29 | Pwd string 30 | } 31 | 32 | func statusIconInfo(pwd string) (symbol, colour string) { 33 | var dirtyIcon string 34 | if dirtyIcon = os.Getenv("BULLETTRAIN_CAR_GIT_DIRTY_ICON"); dirtyIcon == "" { 35 | dirtyIcon = gitDirtyIcon 36 | } 37 | 38 | var dirtyPaint string 39 | if dirtyPaint = os.Getenv("BULLETTRAIN_CAR_GIT_DIRTY_PAINT"); dirtyPaint == "" { 40 | dirtyPaint = gitDirtyPaint 41 | } 42 | 43 | var cleanIcon string 44 | if cleanIcon = os.Getenv("BULLETTRAIN_CAR_GIT_CLEAN_ICON"); cleanIcon == "" { 45 | cleanIcon = gitCleanIcon 46 | } 47 | 48 | var cleanPaint string 49 | if cleanPaint = os.Getenv("BULLETTRAIN_CAR_GIT_CLEAN_PAINT"); cleanPaint == "" { 50 | cleanPaint = gitCleanPaint 51 | } 52 | 53 | cmd := exec.Command("git", "-C", pwd, "status", "--porcelain") 54 | out, err := cmd.Output() 55 | if err != nil { 56 | return "", "" 57 | } 58 | 59 | if len(out) > 0 { 60 | return dirtyIcon, dirtyPaint 61 | } else { 62 | return cleanIcon, cleanPaint 63 | } 64 | } 65 | 66 | // GetPaint returns the calculated end paint string for the car. 67 | func (c *Car) GetPaint() string { 68 | if c.paint = os.Getenv("BULLETTRAIN_CAR_GIT_PAINT"); c.paint == "" { 69 | c.paint = carPaint 70 | } 71 | 72 | return c.paint 73 | } 74 | 75 | // CanShow decides if this car needs to be displayed. 76 | func (c *Car) CanShow() bool { 77 | cmd := exec.Command("git", "-C", c.Pwd, "rev-parse", "--git-dir") 78 | cmdOut, _ := cmd.Output() 79 | if string(cmdOut) != "" { 80 | return true 81 | } 82 | 83 | return false 84 | } 85 | 86 | func currentHeadName(pwd string) string { 87 | cmd := exec.Command("git", "-C", pwd, "symbolic-ref", "HEAD") 88 | ref, err := cmd.Output() 89 | if err != nil { 90 | cmd := exec.Command("git", "-C", pwd, "describe", "--tags", "--exact-match", "HEAD") 91 | ref, err = cmd.Output() 92 | } 93 | if err != nil { 94 | cmd := exec.Command("git", "-C", pwd, "rev-parse", "--short", "HEAD") 95 | ref, err = cmd.Output() 96 | } 97 | if err != nil { 98 | return strings.TrimRight(err.Error(), "\n") 99 | } 100 | 101 | ref = []byte(strings.Replace(string(ref), "refs/heads/", "", 1)) 102 | 103 | if len(ref) == 0 { 104 | return "" 105 | } 106 | 107 | return strings.TrimRight(string(ref), "\n") 108 | } 109 | 110 | // Render builds and passes the end product of a completely composed car onto 111 | // the channel. 112 | func (c *Car) Render(out chan<- string) { 113 | defer close(out) // Always close the channel! 114 | 115 | var symbolIcon string 116 | if symbolIcon = os.Getenv("BULLETTRAIN_CAR_GIT_SYMBOL_ICON"); symbolIcon == "" { 117 | symbolIcon = gitSymbolIcon 118 | } 119 | 120 | var symbolPaint string 121 | if symbolPaint = os.Getenv("BULLETTRAIN_CAR_GIT_SYMBOL_PAINT"); symbolPaint == "" { 122 | symbolPaint = gitSymbolPaint 123 | } 124 | 125 | statusIcon, statusIconColour := statusIconInfo(c.Pwd) 126 | 127 | var s string 128 | if s = os.Getenv("BULLETTRAIN_CAR_GIT_TEMPLATE"); s == "" { 129 | s = carTemplate 130 | } 131 | 132 | funcMap := template.FuncMap{ 133 | // Pipeline functions for colouring. 134 | "c": func(t string) string { return ansi.Color(t, c.GetPaint()) }, 135 | "cs": func(t string) string { return ansi.Color(t, symbolPaint) }, 136 | "csi": func(t string) string { return ansi.Color(t, statusIconColour) }, 137 | } 138 | 139 | tpl := template.Must(template.New("user").Funcs(funcMap).Parse(s)) 140 | data := struct { 141 | Icon string 142 | Name string 143 | StatusIcon string 144 | }{Icon: symbolIcon, 145 | Name: currentHeadName(c.Pwd), 146 | StatusIcon: statusIcon} 147 | fromTpl := new(bytes.Buffer) 148 | err := tpl.Execute(fromTpl, data) 149 | if err != nil { 150 | log.Fatalf("Can't generate the user template: %s", err.Error()) 151 | } 152 | 153 | out <- fromTpl.String() 154 | } 155 | 156 | // GetSeparatorPaint overrides the Fg/Bg colours of the right hand side 157 | // separator through ENV variables. 158 | func (c *Car) GetSeparatorPaint() string { 159 | return os.Getenv("BULLETTRAIN_CAR_GIT_SEPARATOR_PAINT") 160 | } 161 | 162 | // GetSeparatorSymbol overrides the symbol of the right hand side 163 | // separator through ENV variables. 164 | func (c *Car) GetSeparatorSymbol() string { 165 | return os.Getenv("BULLETTRAIN_CAR_GIT_SEPARATOR_SYMBOL") 166 | } 167 | 168 | // GetSeparatorTemplate overrides the template of the right hand side 169 | // separator through ENV variable. 170 | func (c *Car) GetSeparatorTemplate() string { 171 | return os.Getenv("BULLETTRAIN_CAR_GIT_SEPARATOR_TEMPLATE") 172 | } 173 | -------------------------------------------------------------------------------- /src/car/golang/golang.go: -------------------------------------------------------------------------------- 1 | package carGo 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "log" 7 | "os" 8 | "os/exec" 9 | "path/filepath" 10 | "regexp" 11 | "strings" 12 | "text/template" 13 | 14 | "github.com/bullettrain-sh/bullettrain-go-core/src/ansi" 15 | ) 16 | 17 | const ( 18 | carPaint = "black:123" 19 | goSymbolPaint = "black:123" 20 | goSymbolIcon = "\uE627" //  21 | // language=GoTemplate 22 | carTemplate = `{{.Icon | printf "%s " | cs}}{{.Info | c}}` 23 | ) 24 | 25 | // Car for Go 26 | type Car struct { 27 | paint string 28 | // Current directory 29 | Pwd string 30 | } 31 | 32 | // GetPaint returns the calculated end paint string for the car. 33 | func (c *Car) GetPaint() string { 34 | if c.paint = os.Getenv("BULLETTRAIN_CAR_GO_PAINT"); c.paint == "" { 35 | c.paint = carPaint 36 | } 37 | 38 | return c.paint 39 | } 40 | 41 | // CanShow decides if this car needs to be displayed. 42 | func (c *Car) CanShow() bool { 43 | if e := os.Getenv("BULLETTRAIN_CAR_GO_SHOW"); e == "true" { 44 | return true 45 | } 46 | 47 | var d string 48 | if d = c.Pwd; d == "" { 49 | return false 50 | } 51 | 52 | // Show when .go files exist in current directory 53 | p := fmt.Sprintf("%s%s*.go", d, string(os.PathSeparator)) 54 | f, _ := filepath.Glob(p) 55 | if f != nil { 56 | return true 57 | } 58 | 59 | return false 60 | } 61 | 62 | // Render builds and passes the end product of a completely composed car onto 63 | // the channel. 64 | func (c *Car) Render(out chan<- string) { 65 | defer close(out) // Always close the channel! 66 | 67 | var symbolIcon string 68 | if symbolIcon = os.Getenv("BULLETTRAIN_CAR_GO_SYMBOL_ICON"); symbolIcon == "" { 69 | symbolIcon = goSymbolIcon 70 | } 71 | 72 | var symbolPaint string 73 | if symbolPaint = os.Getenv("BULLETTRAIN_CAR_GO_SYMBOL_PAINT"); symbolPaint == "" { 74 | symbolPaint = goSymbolPaint 75 | } 76 | 77 | cmd := exec.Command("go", "version") 78 | cmdOut, err := cmd.CombinedOutput() 79 | var version string 80 | if err == nil { 81 | // language=GoRegExp 82 | re := regexp.MustCompile(`([0-9.]+)`) 83 | versionArr := re.FindStringSubmatch(string(cmdOut)) 84 | if len(versionArr) > 0 { 85 | version = versionArr[1] 86 | } 87 | } else { 88 | version = strings.Replace(string(cmdOut), "\n", " ", -1) 89 | } 90 | 91 | var s string 92 | if s = os.Getenv("BULLETTRAIN_CAR_GO_TEMPLATE"); s == "" { 93 | s = carTemplate 94 | } 95 | 96 | funcMap := template.FuncMap{ 97 | // Pipeline functions for colouring. 98 | "c": func(t string) string { return ansi.Color(t, c.GetPaint()) }, 99 | "cs": func(t string) string { return ansi.Color(t, symbolPaint) }, 100 | } 101 | 102 | tpl := template.Must(template.New("go").Funcs(funcMap).Parse(s)) 103 | data := struct { 104 | Icon string 105 | Info string 106 | }{Icon: symbolIcon, Info: version} 107 | fromTpl := new(bytes.Buffer) 108 | err = tpl.Execute(fromTpl, data) 109 | if err != nil { 110 | log.Fatalf("Can't generate the go template: %s", err.Error()) 111 | } 112 | 113 | out <- fromTpl.String() 114 | } 115 | 116 | // GetSeparatorPaint overrides the Fg/Bg colours of the right hand side 117 | // separator through ENV variables. 118 | func (c *Car) GetSeparatorPaint() string { 119 | return os.Getenv("BULLETTRAIN_CAR_GO_SEPARATOR_PAINT") 120 | } 121 | 122 | // GetSeparatorSymbol overrides the symbol of the right hand side 123 | // separator through ENV variables. 124 | func (c *Car) GetSeparatorSymbol() string { 125 | return os.Getenv("BULLETTRAIN_CAR_GO_SEPARATOR_SYMBOL") 126 | } 127 | 128 | // GetSeparatorTemplate overrides the template of the right hand side 129 | // separator through ENV variable. 130 | func (c *Car) GetSeparatorTemplate() string { 131 | return os.Getenv("BULLETTRAIN_CAR_GO_SEPARATOR_TEMPLATE") 132 | } 133 | -------------------------------------------------------------------------------- /src/car/host/host.go: -------------------------------------------------------------------------------- 1 | package carHost 2 | 3 | import ( 4 | "bytes" 5 | "log" 6 | "os" 7 | "text/template" 8 | 9 | "github.com/bullettrain-sh/bullettrain-go-core/src/ansi" 10 | ) 11 | 12 | const ( 13 | carPaint = "black:white" 14 | // language=GoTemplate 15 | carTemplate = `{{.Host | c}}` 16 | ) 17 | 18 | // Host car 19 | type Car struct { 20 | paint string 21 | } 22 | 23 | // GetPaint returns the calculated end paint string for the car. 24 | func (c *Car) GetPaint() string { 25 | if c.paint = os.Getenv("BULLETTRAIN_CAR_HOST_PAINT"); c.paint == "" { 26 | c.paint = carPaint 27 | } 28 | 29 | return c.paint 30 | } 31 | 32 | // CanShow decides if this car needs to be displayed. 33 | func (c *Car) CanShow() bool { 34 | s := true 35 | if e := os.Getenv("BULLETTRAIN_CAR_HOST_SHOW"); e == "false" { 36 | s = false 37 | } 38 | 39 | return s 40 | } 41 | 42 | // Render builds and passes the end product of a completely composed car onto 43 | // the channel. 44 | func (c *Car) Render(out chan<- string) { 45 | defer close(out) 46 | 47 | hostname, _ := os.Hostname() 48 | 49 | var s string 50 | if s = os.Getenv("BULLETTRAIN_CAR_HOST_TEMPLATE"); s == "" { 51 | s = carTemplate 52 | } 53 | 54 | funcMap := template.FuncMap{ 55 | // Pipeline functions for colouring. 56 | "c": func(t string) string { return ansi.Color(t, c.GetPaint()) }, 57 | } 58 | 59 | tpl := template.Must(template.New("host").Funcs(funcMap).Parse(s)) 60 | data := struct{ Host string }{Host: hostname} 61 | fromTpl := new(bytes.Buffer) 62 | err := tpl.Execute(fromTpl, data) 63 | if err != nil { 64 | log.Fatalf("Can't generate the host template: %s", err.Error()) 65 | } 66 | 67 | out <- fromTpl.String() 68 | } 69 | 70 | // GetSeparatorPaint overrides the Fg/Bg colours of the right hand side 71 | // separator through ENV variables. 72 | func (c *Car) GetSeparatorPaint() string { 73 | return os.Getenv("BULLETTRAIN_CAR_HOST_SEPARATOR_PAINT") 74 | } 75 | 76 | // GetSeparatorSymbol overrides the symbol of the right hand side 77 | // separator through ENV variables. 78 | func (c *Car) GetSeparatorSymbol() string { 79 | return os.Getenv("BULLETTRAIN_CAR_HOST_SEPARATOR_SYMBOL") 80 | } 81 | 82 | // GetSeparatorTemplate overrides the template of the right hand side 83 | // separator through ENV variable. 84 | func (c *Car) GetSeparatorTemplate() string { 85 | return os.Getenv("BULLETTRAIN_CAR_HOST_SEPARATOR_TEMPLATE") 86 | } 87 | -------------------------------------------------------------------------------- /src/car/kubernetes/kubernetes.go: -------------------------------------------------------------------------------- 1 | package carK8s 2 | 3 | import ( 4 | "bytes" 5 | "log" 6 | "os" 7 | "os/exec" 8 | "strings" 9 | "text/template" 10 | 11 | "github.com/bullettrain-sh/bullettrain-go-core/src/ansi" 12 | ) 13 | 14 | const ( 15 | carPaint = "white+h:yellow" 16 | helmSymbol = "\uFD31" // ﴱ 17 | carTemplate = `{{.Icon | printf " %s " | c}}{{.Context | c}}` 18 | ) 19 | 20 | type Car struct { 21 | paint string 22 | // Current directory 23 | Pwd string 24 | } 25 | 26 | // GetPaint returns the calculated end paint string for the car. 27 | func (c *Car) GetPaint() string { 28 | if c.paint = os.Getenv("BULLETTRAIN_CAR_K8S_PAINT"); c.paint == "" { 29 | c.paint = carPaint 30 | } 31 | 32 | return c.paint 33 | } 34 | 35 | // CanShow decides if this car needs to be displayed. 36 | func (c *Car) CanShow() bool { 37 | path, _ := exec.LookPath("kubectl") 38 | 39 | if path == "" { 40 | return false 41 | } 42 | 43 | s := true 44 | if e := os.Getenv("BULLETTRAIN_CAR_K8S_SHOW"); e == "false" { 45 | s = false 46 | } 47 | 48 | return s 49 | } 50 | 51 | // Render builds and passes the end product of a completely composed car onto 52 | // the channel. 53 | func (c *Car) Render(out chan<- string) { 54 | defer close(out) 55 | 56 | cmd := exec.Command("kubectl", "config", "current-context") 57 | output, err := cmd.Output() 58 | var context string 59 | if err == nil { 60 | context = strings.Trim(string(output), "\n") 61 | } else { 62 | context = "ERR!" 63 | } 64 | 65 | var symbol string 66 | if symbol = os.Getenv("BULLETTRAIN_CAR_K8S_SYMBOL_ICON"); symbol == "" { 67 | symbol = helmSymbol 68 | } 69 | 70 | var s string 71 | if s = os.Getenv("BULLETTRAIN_CAR_K8S_TEMPLATE"); s == "" { 72 | s = carTemplate 73 | } 74 | 75 | funcMap := template.FuncMap{ 76 | // Pipeline functions for colouring. 77 | "c": func(t string) string { return ansi.Color(t, c.GetPaint()) }, 78 | } 79 | 80 | tpl := template.Must(template.New("k8s").Funcs(funcMap).Parse(s)) 81 | data := struct { 82 | Icon string 83 | Context string 84 | }{Icon: symbol, Context: context} 85 | contextFromTpl := new(bytes.Buffer) 86 | err = tpl.Execute(contextFromTpl, data) 87 | if err != nil { 88 | log.Fatalf("Can't generate the time template: %s", err.Error()) 89 | } 90 | 91 | out <- contextFromTpl.String() 92 | } 93 | 94 | // GetSeparatorPaint overrides the Fg/Bg colours of the right hand side 95 | // separator through ENV variables. 96 | func (c *Car) GetSeparatorPaint() string { 97 | return os.Getenv("BULLETTRAIN_CAR_K8S_SEPARATOR_PAINT") 98 | } 99 | 100 | // GetSeparatorSymbol overrides the symbol of the right hand side 101 | // separator through ENV variables. 102 | func (c *Car) GetSeparatorSymbol() string { 103 | return os.Getenv("BULLETTRAIN_CAR_K8S_SEPARATOR_SYMBOL") 104 | } 105 | 106 | // GetSeparatorTemplate overrides the template of the right hand side 107 | // separator through ENV variable. 108 | func (c *Car) GetSeparatorTemplate() string { 109 | return os.Getenv("BULLETTRAIN_CAR_K8S_SEPARATOR_TEMPLATE") 110 | } 111 | -------------------------------------------------------------------------------- /src/car/nodejs/nodejs.go: -------------------------------------------------------------------------------- 1 | package carNodejs 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "log" 7 | "os" 8 | "os/exec" 9 | "path/filepath" 10 | "regexp" 11 | "strings" 12 | "text/template" 13 | 14 | "github.com/bullettrain-sh/bullettrain-go-core/src/ansi" 15 | ) 16 | 17 | const ( 18 | carPaint = "white:green" 19 | pythonSymbolPaint = "black:green" 20 | pythonSymbolIcon = "\uF898" //  21 | // language=GoTemplate 22 | carTemplate = `{{.Icon | printf "%s " | cs}}{{.Info | c}}` 23 | ) 24 | 25 | // Car for Nodejs 26 | type Car struct { 27 | paint string 28 | // Current directory 29 | Pwd string 30 | } 31 | 32 | // GetPaint returns the calculated end paint string for the car. 33 | func (c *Car) GetPaint() string { 34 | if c.paint = os.Getenv("BULLETTRAIN_CAR_NODEJS_PAINT"); c.paint == "" { 35 | c.paint = carPaint 36 | } 37 | 38 | return c.paint 39 | } 40 | 41 | // CanShow decides if this car needs to be displayed. 42 | func (c *Car) CanShow() bool { 43 | if e := os.Getenv("BULLETTRAIN_CAR_NODEJS_SHOW"); e == "true" { 44 | return true 45 | } 46 | 47 | var d string 48 | if d = c.Pwd; d == "" { 49 | return false 50 | } 51 | 52 | // Show when .js files exist in current directory 53 | jsPattern := fmt.Sprintf("%s%s*.js", d, string(os.PathSeparator)) 54 | jsFiles, _ := filepath.Glob(jsPattern) 55 | if jsFiles != nil { 56 | return true 57 | } 58 | 59 | // Show when .nvmrc file exist in current directory 60 | versionFiles, _ := filepath.Glob(fmt.Sprintf("%s%s.nvmrc", 61 | d, string(os.PathSeparator))) 62 | if versionFiles != nil { 63 | return true 64 | } 65 | 66 | return false 67 | } 68 | 69 | // Render builds and passes the end product of a completely composed car onto 70 | // the channel. 71 | func (c *Car) Render(out chan<- string) { 72 | defer close(out) // Always close the channel! 73 | 74 | var symbolIcon string 75 | if symbolIcon = os.Getenv("BULLETTRAIN_CAR_NODEJS_SYMBOL"); symbolIcon == "" { 76 | symbolIcon = pythonSymbolIcon 77 | } 78 | 79 | var symbolPaint string 80 | if symbolPaint = os.Getenv("BULLETTRAIN_CAR_NODEJS_SYMBOL_PAINT"); symbolPaint == "" { 81 | symbolPaint = pythonSymbolPaint 82 | } 83 | 84 | var s string 85 | if s = os.Getenv("BULLETTRAIN_CAR_USER_TEMPLATE"); s == "" { 86 | s = carTemplate 87 | } 88 | 89 | funcMap := template.FuncMap{ 90 | // Pipeline functions for colouring. 91 | "c": func(t string) string { return ansi.Color(t, c.GetPaint()) }, 92 | "cs": func(t string) string { return ansi.Color(t, symbolPaint) }, 93 | } 94 | 95 | var version string 96 | cmd := exec.Command("node", "--version") 97 | cmdOut, err := cmd.CombinedOutput() 98 | if err == nil { 99 | // language=GoRegExp 100 | re := regexp.MustCompile(`([0-9.]+)`) 101 | versionArr := re.FindStringSubmatch(string(cmdOut)) 102 | if len(versionArr) > 0 { 103 | version = versionArr[1] 104 | } 105 | } else { 106 | version = strings.Replace(string(cmdOut), "\n", " ", -1) 107 | } 108 | 109 | tpl := template.Must(template.New("nodejs").Funcs(funcMap).Parse(s)) 110 | data := struct { 111 | Icon string 112 | Info string 113 | }{Icon: symbolIcon, Info: version} 114 | fromTpl := new(bytes.Buffer) 115 | err = tpl.Execute(fromTpl, data) 116 | if err != nil { 117 | log.Fatalf("Can't generate the nodejs template: %s", err.Error()) 118 | } 119 | 120 | out <- fromTpl.String() 121 | } 122 | 123 | // GetSeparatorPaint overrides the Fg/Bg colours of the right hand side 124 | // separator through ENV variables. 125 | func (c *Car) GetSeparatorPaint() string { 126 | return os.Getenv("BULLETTRAIN_CAR_NODEJS_SEPARATOR_PAINT") 127 | } 128 | 129 | // GetSeparatorSymbol overrides the symbol of the right hand side 130 | // separator through ENV variables. 131 | func (c *Car) GetSeparatorSymbol() string { 132 | return os.Getenv("BULLETTRAIN_CAR_NODEJS_SEPARATOR_SYMBOL") 133 | } 134 | 135 | // GetSeparatorTemplate overrides the template of the right hand side 136 | // separator through ENV variable. 137 | func (c *Car) GetSeparatorTemplate() string { 138 | return os.Getenv("BULLETTRAIN_CAR_NODEJS_SEPARATOR_TEMPLATE") 139 | } 140 | -------------------------------------------------------------------------------- /src/car/openvpn/openvpn.go: -------------------------------------------------------------------------------- 1 | package carOpenvpn 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "log" 7 | "os" 8 | "os/exec" 9 | "regexp" 10 | "text/template" 11 | 12 | "github.com/bullettrain-sh/bullettrain-go-core/src/ansi" 13 | ) 14 | 15 | const ( 16 | carPaint = "208:black" 17 | symbolPaint = "208:black" 18 | symbolIcon = "" 19 | symbolLocked = "" 20 | symbolLockedPaint = "green:black" 21 | symbolUnlocked = "" 22 | symbolUnlockedPaint = "red:black" 23 | // language=GoTemplate 24 | carTemplate = `{{.Icon | printf "%s " | cs}}{{range $name, $status := .Statuses}}{{printStatus $name $status}}{{end}}` 25 | ) 26 | 27 | // Car for Openvpn 28 | type Car struct { 29 | paint string 30 | } 31 | 32 | // GetPaint returns the calculated end paint string for the car. 33 | func (c *Car) GetPaint() string { 34 | if c.paint = os.Getenv("BULLETTRAIN_CAR_OPENVPN_PAINT"); c.paint == "" { 35 | c.paint = carPaint 36 | } 37 | 38 | return c.paint 39 | } 40 | 41 | // CanShow decides if this car needs to be displayed. 42 | func (c *Car) CanShow() bool { 43 | return true 44 | } 45 | 46 | func vpnStatuses() map[string]string { 47 | var serviceHandle string 48 | serviceHandle = "openvpn-client@" 49 | 50 | cmd := exec.Command("systemctl", "list-units", "-a", 51 | fmt.Sprintf("%s*", serviceHandle)) 52 | out, err := cmd.Output() 53 | if err != nil { 54 | log.Fatalf("Failed to get systemd statuses: %s", err.Error()) 55 | } 56 | 57 | // language=GoRegExp 58 | re := regexp.MustCompile(fmt.Sprintf( 59 | `%s(.*)\.service.*(running|failed|dead|auto-restart)`, serviceHandle)) 60 | matches := re.FindAllStringSubmatch(string(out), -1) 61 | statuses := make(map[string]string) 62 | for _, match := range matches { 63 | statuses[match[1]] = match[2] 64 | } 65 | 66 | return statuses 67 | } 68 | 69 | // Render builds and passes the end product of a completely composed car onto 70 | // the channel. 71 | func (c *Car) Render(out chan<- string) { 72 | defer close(out) // Always close the channel! 73 | 74 | var si string 75 | if si = os.Getenv("BULLETTRAIN_CAR_OPENVPN_SYMBOL_ICON"); si == "" { 76 | si = symbolIcon 77 | } 78 | 79 | var siu string 80 | if siu = os.Getenv("BULLETTRAIN_CAR_OPENVPN_SYMBOL_ICON_UNLOCKED"); siu == "" { 81 | siu = symbolUnlocked 82 | } 83 | 84 | var spu string 85 | if spu = os.Getenv("BULLETTRAIN_CAR_OPENVPN_SYMBOL_PAINT_UNLOCKED"); spu == "" { 86 | spu = symbolUnlockedPaint 87 | } 88 | 89 | var sil string 90 | if sil = os.Getenv("BULLETTRAIN_CAR_OPENVPN_SYMBOL_ICON_LOCKED"); sil == "" { 91 | sil = symbolLocked 92 | } 93 | 94 | var spl string 95 | if spl = os.Getenv("BULLETTRAIN_CAR_OPENVPN_SYMBOL_PAINT_LOCKED"); spl == "" { 96 | spl = symbolLockedPaint 97 | } 98 | 99 | var sp string 100 | if sp = os.Getenv("BULLETTRAIN_CAR_OPENVPN_SYMBOL_PAINT"); sp == "" { 101 | sp = symbolPaint 102 | } 103 | 104 | funcMap := template.FuncMap{ 105 | // Pipeline functions for colouring. 106 | "c": func(t string) string { return ansi.Color(t, c.GetPaint()) }, 107 | "cs": func(t string) string { return ansi.Color(t, sp) }, 108 | "printStatus": func(name string, status string) string { 109 | statusIcon := siu 110 | statusColour := spu 111 | if status == "running" { 112 | statusIcon = sil 113 | statusColour = spl 114 | } 115 | 116 | carPaint := c.GetPaint() 117 | return fmt.Sprintf("%s%s%s", 118 | ansi.Color(statusIcon, statusColour), 119 | ansi.Color(name, carPaint), 120 | ansi.Color(" ", carPaint)) 121 | }, 122 | } 123 | 124 | tpl := template.Must( 125 | template.New("openvpn").Funcs(funcMap).Parse(carTemplate)) 126 | data := struct { 127 | Icon string 128 | Statuses map[string]string 129 | }{Icon: si, Statuses: vpnStatuses()} 130 | fromTpl := new(bytes.Buffer) 131 | err := tpl.Execute(fromTpl, data) 132 | if err != nil { 133 | log.Fatalf("Can't generate the openvpn template: %s", err.Error()) 134 | } 135 | 136 | out <- fromTpl.String() 137 | } 138 | 139 | // GetSeparatorPaint overrides the Fg/Bg colours of the right hand side 140 | // separator through ENV variables. 141 | func (c *Car) GetSeparatorPaint() string { 142 | return os.Getenv("BULLETTRAIN_CAR_OPENVPN_SEPARATOR_PAINT") 143 | } 144 | 145 | // GetSeparatorSymbol overrides the symbol of the right hand side 146 | // separator through ENV variables. 147 | func (c *Car) GetSeparatorSymbol() string { 148 | return os.Getenv("BULLETTRAIN_CAR_OPENVPN_SEPARATOR_SYMBOL") 149 | } 150 | 151 | // GetSeparatorTemplate overrides the template of the right hand side 152 | // separator through ENV variable. 153 | func (c *Car) GetSeparatorTemplate() string { 154 | return os.Getenv("BULLETTRAIN_CAR_OPENVPN_SEPARATOR_TEMPLATE") 155 | } 156 | -------------------------------------------------------------------------------- /src/car/os/os.go: -------------------------------------------------------------------------------- 1 | package carOs 2 | 3 | import ( 4 | "bytes" 5 | "io/ioutil" 6 | "log" 7 | "os" 8 | "regexp" 9 | "runtime" 10 | "text/template" 11 | 12 | "github.com/bullettrain-sh/bullettrain-go-core/src/ansi" 13 | ) 14 | 15 | const ( 16 | carPaint = "white:cyan" 17 | symbolPaint = "white:cyan" 18 | // language=GoTemplate 19 | carTemplate = `{{.Icon | printf "%s " | cs}}{{.Name | c}}` 20 | ) 21 | 22 | // Os car 23 | type Car struct { 24 | paint string 25 | } 26 | 27 | // GetPaint returns the calculated end paint string for the car. 28 | func (c *Car) GetPaint() string { 29 | if c.paint = os.Getenv("BULLETTRAIN_CAR_OS_PAINT"); c.paint == "" { 30 | c.paint = carPaint 31 | } 32 | 33 | return c.paint 34 | } 35 | 36 | // CanShow decides if this car needs to be displayed. 37 | func (c *Car) CanShow() bool { 38 | s := false 39 | if e := os.Getenv("BULLETTRAIN_CAR_OS_SHOW"); e == "true" { 40 | s = true 41 | } 42 | 43 | return s 44 | } 45 | 46 | func symbol(osName string) string { 47 | osSymbols := map[string]string{ 48 | "alpine": "\uF300", //  49 | "arch": "\uF303", //  50 | "centos": "\uF304", //  51 | "coreos": "\uF305", //  52 | "darwin": "\uF302", //  53 | "debian": "\uF306", //  54 | "elementary": "\uF309", //  55 | "fedora": "\uF30A", //  56 | "freebsd": "\uF30C", //  57 | "gentoo": "\uF30D", //  58 | "linuxmint": "\uF30F", //  59 | "mageia": "\uF310", //  60 | "mandriva": "\uF311", //  61 | "manjaro": "\uF312", //  62 | "nixos": "\uF313", //  63 | "opensuse": "\uF314", //  64 | "raspbian": "\uF315", //  65 | "redhat": "\uF316", //  66 | "sabayon": "\uF317", //  67 | "slackware": "\uF318", //  68 | "ubuntu": "\uF31C", //  69 | "tux": "\uF83C", //  70 | } 71 | 72 | var symbol string 73 | if symbol = os.Getenv("BULLETTRAIN_CAR_OS_SYMBOL_ICON"); symbol == "" { 74 | var present bool 75 | symbol, present = osSymbols[osName] 76 | if !present { 77 | symbol = osSymbols["tux"] 78 | } 79 | } 80 | 81 | return symbol 82 | } 83 | 84 | func FindOutOs() string { 85 | // We know it's a Mac. 86 | if runtime.GOOS == "darwin" { 87 | return "darwin" 88 | } 89 | 90 | fName := "/etc/os-release" 91 | fBody, fErr := ioutil.ReadFile(fName) 92 | if fErr != nil { 93 | log.Fatalln("/etc/os-release could not be read.") 94 | } 95 | 96 | // language=GoRegExp 97 | flavourExp := regexp.MustCompile(`ID=([a-z]+)`) 98 | flavourParts := flavourExp.FindStringSubmatch(string(fBody)) 99 | 100 | flavour := "tux" 101 | if len(flavourParts) == 2 && flavourParts[1] != "" { 102 | flavour = flavourParts[1] 103 | } 104 | 105 | return flavour 106 | } 107 | 108 | // Render builds and passes the end product of a completely composed car onto 109 | // the channel. 110 | func (c *Car) Render(out chan<- string) { 111 | defer close(out) 112 | 113 | var osSymbolPaint string 114 | if osSymbolPaint = os.Getenv("BULLETTRAIN_CAR_OS_SYMBOL_PAINT"); osSymbolPaint == "" { 115 | osSymbolPaint = symbolPaint 116 | } 117 | 118 | var s string 119 | if s = os.Getenv("BULLETTRAIN_CAR_OS_TEMPLATE"); s == "" { 120 | s = carTemplate 121 | } 122 | 123 | funcMap := template.FuncMap{ 124 | // Pipeline functions for colouring. 125 | "c": func(t string) string { return ansi.Color(t, c.GetPaint()) }, 126 | "cs": func(t string) string { return ansi.Color(t, osSymbolPaint) }, 127 | } 128 | 129 | osName := FindOutOs() 130 | tpl := template.Must(template.New("os").Funcs(funcMap).Parse(s)) 131 | data := struct { 132 | Icon string 133 | Name string 134 | }{Icon: symbol(osName), Name: osName} 135 | fromTpl := new(bytes.Buffer) 136 | err := tpl.Execute(fromTpl, data) 137 | if err != nil { 138 | log.Fatalf("Can't generate the OS template: %s", err.Error()) 139 | } 140 | 141 | out <- fromTpl.String() 142 | } 143 | 144 | // GetSeparatorPaint overrides the Fg/Bg colours of the right hand side 145 | // separator through ENV variables. 146 | func (c *Car) GetSeparatorPaint() string { 147 | return os.Getenv("BULLETTRAIN_CAR_OS_SEPARATOR_PAINT") 148 | } 149 | 150 | // GetSeparatorSymbol overrides the symbol of the right hand side 151 | // separator through ENV variables. 152 | func (c *Car) GetSeparatorSymbol() string { 153 | return os.Getenv("BULLETTRAIN_CAR_OS_SEPARATOR_SYMBOL") 154 | } 155 | 156 | // GetSeparatorTemplate overrides the template of the right hand side 157 | // separator through ENV variable. 158 | func (c *Car) GetSeparatorTemplate() string { 159 | return os.Getenv("BULLETTRAIN_CAR_OS_SEPARATOR_TEMPLATE") 160 | } 161 | -------------------------------------------------------------------------------- /src/car/php/php.go: -------------------------------------------------------------------------------- 1 | package carPhp 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "log" 7 | "os" 8 | "os/exec" 9 | "path/filepath" 10 | "regexp" 11 | "strings" 12 | "text/template" 13 | 14 | "github.com/bullettrain-sh/bullettrain-go-core/src/ansi" 15 | ) 16 | 17 | const ( 18 | carPaint = "white:69" 19 | pythonSymbolPaint = "black:69" 20 | pythonSymbolIcon = "\uE608" //  21 | // language=GoTemplate 22 | carTemplate = `{{.Icon | printf "%s " | cs}}{{.Info | c}}` 23 | ) 24 | 25 | // Car for PHP 26 | type Car struct { 27 | paint string 28 | // Current directory 29 | Pwd string 30 | } 31 | 32 | // GetPaint returns the calculated end paint string for the car. 33 | func (c *Car) GetPaint() string { 34 | if c.paint = os.Getenv("BULLETTRAIN_CAR_PHP_PAINT"); c.paint == "" { 35 | c.paint = carPaint 36 | } 37 | 38 | return c.paint 39 | } 40 | 41 | // CanShow decides if this car needs to be displayed. 42 | func (c *Car) CanShow() bool { 43 | if e := os.Getenv("BULLETTRAIN_CAR_PHP_SHOW"); e == "true" { 44 | return true 45 | } 46 | 47 | var d string 48 | if d = c.Pwd; d == "" { 49 | return false 50 | } 51 | 52 | // Show when .php files exist in current directory 53 | phpPattern := fmt.Sprintf("%s%s*.php", d, string(os.PathSeparator)) 54 | phpFiles, _ := filepath.Glob(phpPattern) 55 | if phpFiles != nil { 56 | return true 57 | } 58 | 59 | return false 60 | } 61 | 62 | // Render builds and passes the end product of a completely composed car onto 63 | // the channel. 64 | func (c *Car) Render(out chan<- string) { 65 | defer close(out) // Always close the channel! 66 | 67 | var symbolIcon string 68 | if symbolIcon = os.Getenv("BULLETTRAIN_CAR_PHP_SYMBOL"); symbolIcon == "" { 69 | symbolIcon = pythonSymbolIcon 70 | } 71 | 72 | var symbolPaint string 73 | if symbolPaint = os.Getenv("BULLETTRAIN_CAR_PHP_SYMBOL_PAINT"); symbolPaint == "" { 74 | symbolPaint = pythonSymbolPaint 75 | } 76 | 77 | var s string 78 | if s = os.Getenv("BULLETTRAIN_CAR_USER_TEMPLATE"); s == "" { 79 | s = carTemplate 80 | } 81 | 82 | funcMap := template.FuncMap{ 83 | // Pipeline functions for colouring. 84 | "c": func(t string) string { return ansi.Color(t, c.GetPaint()) }, 85 | "cs": func(t string) string { return ansi.Color(t, symbolPaint) }, 86 | } 87 | 88 | var version string 89 | cmd := exec.Command("php", "--version") 90 | cmdOut, err := cmd.CombinedOutput() 91 | if err == nil { 92 | // language=GoRegExp 93 | re := regexp.MustCompile(`([0-9.]+)`) 94 | versionArr := re.FindStringSubmatch(string(cmdOut)) 95 | if len(versionArr) > 0 { 96 | version = versionArr[1] 97 | } 98 | } else { 99 | version = strings.Replace(string(cmdOut), "\n", " ", -1) 100 | } 101 | 102 | tpl := template.Must(template.New("php").Funcs(funcMap).Parse(s)) 103 | data := struct { 104 | Icon string 105 | Info string 106 | }{Icon: symbolIcon, Info: version} 107 | fromTpl := new(bytes.Buffer) 108 | err = tpl.Execute(fromTpl, data) 109 | if err != nil { 110 | log.Fatalf("Can't generate the php template: %s", err.Error()) 111 | } 112 | 113 | out <- fromTpl.String() 114 | } 115 | 116 | // GetSeparatorPaint overrides the Fg/Bg colours of the right hand side 117 | // separator through ENV variables. 118 | func (c *Car) GetSeparatorPaint() string { 119 | return os.Getenv("BULLETTRAIN_CAR_PHP_SEPARATOR_PAINT") 120 | } 121 | 122 | // GetSeparatorSymbol overrides the symbol of the right hand side 123 | // separator through ENV variables. 124 | func (c *Car) GetSeparatorSymbol() string { 125 | return os.Getenv("BULLETTRAIN_CAR_PHP_SEPARATOR_SYMBOL") 126 | } 127 | 128 | // GetSeparatorTemplate overrides the template of the right hand side 129 | // separator through ENV variable. 130 | func (c *Car) GetSeparatorTemplate() string { 131 | return os.Getenv("BULLETTRAIN_CAR_PHP_SEPARATOR_TEMPLATE") 132 | } 133 | -------------------------------------------------------------------------------- /src/car/python/python.go: -------------------------------------------------------------------------------- 1 | package carPython 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "log" 7 | "os" 8 | "os/exec" 9 | "path" 10 | "path/filepath" 11 | "strings" 12 | "text/template" 13 | 14 | "github.com/bullettrain-sh/bullettrain-go-core/src/ansi" 15 | ) 16 | 17 | const ( 18 | carPaint = "black:220" 19 | pythonSymbolPaint = "32:220" 20 | pythonSymbolIcon = "\uE235" //  21 | virtualenvSymbolIcon = "\xf0\x9f\x90\x8d" // 🐍 22 | virtualenvSymbolPaint = "32:220" 23 | // language=GoTemplate 24 | carTemplate = `{{.VersionIcon | printf "%s " | cs}}{{.Version | printf "%s " | c}}{{.VenvIcon | printf "%s " | cvs}}{{.Venv | c}}` 25 | ) 26 | 27 | // Car for Python and virtualenv 28 | type Car struct { 29 | paint string 30 | // Current directory 31 | Pwd string 32 | } 33 | 34 | // GetPaint returns the calculated end paint string for the car. 35 | func (c *Car) GetPaint() string { 36 | if c.paint = os.Getenv("BULLETTRAIN_CAR_PYTHON_PAINT"); c.paint == "" { 37 | c.paint = carPaint 38 | } 39 | 40 | return c.paint 41 | } 42 | 43 | // CanShow decides if this car needs to be displayed. 44 | func (c *Car) CanShow() bool { 45 | if e := os.Getenv("BULLETTRAIN_CAR_PYTHON_SHOW"); e == "true" { 46 | return true 47 | } 48 | 49 | var d string 50 | if d = c.Pwd; d == "" { 51 | return false 52 | } 53 | 54 | // Show when .py files exist in current directory 55 | pyPattern := fmt.Sprintf("%s%s*.py", d, string(os.PathSeparator)) 56 | pyFiles, _ := filepath.Glob(pyPattern) 57 | if pyFiles != nil { 58 | return true 59 | } 60 | 61 | // Show when .python-version file exist in current directory 62 | versionFiles, _ := filepath.Glob(fmt.Sprintf("%s%s.python-version", 63 | d, string(os.PathSeparator))) 64 | if versionFiles != nil { 65 | return true 66 | } 67 | 68 | return false 69 | } 70 | 71 | // getPythonVersion gets the available version number for a python executable 72 | // 73 | // Use it to check if python2 responds, python3 responds or only python does. 74 | func getPythonVersion(pythonExecutable string) string { 75 | cmdPython := exec.Command(pythonExecutable, "--version") 76 | resultPython, errPython := cmdPython.CombinedOutput() 77 | if errPython == nil { 78 | return strings.TrimSpace(strings.TrimLeft( 79 | string(resultPython), "Python ")) 80 | } else { 81 | return "" 82 | } 83 | } 84 | 85 | func collectPythonVersions() []string { 86 | pythonVersions := make([]string, 0) 87 | 88 | var p string 89 | if p = getPythonVersion("python2"); p != "" { 90 | pythonVersions = append(pythonVersions, p) 91 | } 92 | if p = getPythonVersion("python3"); p != "" { 93 | pythonVersions = append(pythonVersions, p) 94 | } 95 | if len(pythonVersions) == 0 { 96 | if p = getPythonVersion("python"); p != "" { 97 | pythonVersions = append(pythonVersions, p) 98 | } 99 | } 100 | 101 | return pythonVersions 102 | } 103 | 104 | func pythonVirtualenv() string { 105 | if e := os.Getenv("VIRTUAL_ENV"); e != "" { 106 | return path.Base(e) 107 | } 108 | 109 | return "" 110 | } 111 | 112 | // Render builds and passes the end product of a completely composed car onto 113 | // the channel. 114 | // 115 | // Car version managers can expose multiple Python versions too. 116 | // Python version managers analyzed first, then system Pythons are looked at. 117 | // Empty string is returned when no interpreter could be reached. 118 | func (c *Car) Render(out chan<- string) { 119 | defer close(out) // Always close the channel! 120 | 121 | var ps string 122 | if ps = os.Getenv("BULLETTRAIN_CAR_PYTHON_SYMBOL_ICON"); ps == "" { 123 | ps = pythonSymbolIcon 124 | } 125 | 126 | var ssp string 127 | if ssp = os.Getenv("BULLETTRAIN_CAR_PYTHON_SYMBOL_PAINT"); ssp == "" { 128 | ssp = pythonSymbolPaint 129 | } 130 | 131 | var vs string 132 | if vs = os.Getenv("BULLETTRAIN_CAR_PYTHON_VIRTUALENV_SYMBOL_ICON"); vs == "" { 133 | vs = virtualenvSymbolIcon 134 | } 135 | 136 | var vsp string 137 | if vsp = os.Getenv("BULLETTRAIN_CAR_PYTHON_VIRTUALENV_SYMBOL_PAINT"); vsp == "" { 138 | vsp = virtualenvSymbolPaint 139 | } 140 | 141 | var s string 142 | if s = os.Getenv("BULLETTRAIN_CAR_PYTHON_TEMPLATE"); s == "" { 143 | s = carTemplate 144 | } 145 | 146 | funcMap := template.FuncMap{ 147 | // Pipeline functions for colouring. 148 | "c": func(t string) string { return ansi.Color(t, c.GetPaint()) }, 149 | "cs": func(t string) string { return ansi.Color(t, ssp) }, 150 | "cvs": func(t string) string { return ansi.Color(t, vsp) }, 151 | } 152 | 153 | tpl := template.Must(template.New("python").Funcs(funcMap).Parse(s)) 154 | data := struct { 155 | VersionIcon string 156 | Version string 157 | VenvIcon string 158 | Venv string 159 | }{ 160 | VersionIcon: pythonSymbolIcon, 161 | Version: strings.Join(collectPythonVersions(), " "), 162 | VenvIcon: virtualenvSymbolIcon, 163 | Venv: pythonVirtualenv(), 164 | } 165 | fromTpl := new(bytes.Buffer) 166 | err := tpl.Execute(fromTpl, data) 167 | if err != nil { 168 | log.Fatalf("Can't generate the python template: %s", err.Error()) 169 | } 170 | 171 | out <- fromTpl.String() 172 | } 173 | 174 | // GetSeparatorPaint overrides the Fg/Bg colours of the right hand side 175 | // separator through ENV variables. 176 | func (c *Car) GetSeparatorPaint() string { 177 | return os.Getenv("BULLETTRAIN_CAR_PYTHON_SEPARATOR_PAINT") 178 | } 179 | 180 | // GetSeparatorSymbol overrides the symbol of the right hand side 181 | // separator through ENV variables. 182 | func (c *Car) GetSeparatorSymbol() string { 183 | return os.Getenv("BULLETTRAIN_CAR_PYTHON_SEPARATOR_SYMBOL") 184 | } 185 | 186 | // GetSeparatorTemplate overrides the template of the right hand side 187 | // separator through ENV variable. 188 | func (c *Car) GetSeparatorTemplate() string { 189 | return os.Getenv("BULLETTRAIN_CAR_PYTHON_SEPARATOR_TEMPLATE") 190 | } 191 | -------------------------------------------------------------------------------- /src/car/ruby/ruby.go: -------------------------------------------------------------------------------- 1 | package carRuby 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "log" 7 | "os" 8 | "os/exec" 9 | "path/filepath" 10 | "regexp" 11 | "strings" 12 | "text/template" 13 | 14 | "github.com/bullettrain-sh/bullettrain-go-core/src/ansi" 15 | ) 16 | 17 | const ( 18 | carPaint = "white:red" 19 | symbolPaint = "white:red" 20 | symbolIcon = "\uE23E" //  21 | // language=GoTemplate 22 | carTemplate = `{{.Icon | printf "%s " | cs}}{{.Info | c}}` 23 | ) 24 | 25 | // Car for Ruby 26 | type Car struct { 27 | paint string 28 | // Current directory 29 | Pwd string 30 | } 31 | 32 | // GetPaint returns the calculated end paint string for the car. 33 | func (c *Car) GetPaint() string { 34 | if c.paint = os.Getenv("BULLETTRAIN_CAR_RUBY_PAINT"); c.paint == "" { 35 | c.paint = carPaint 36 | } 37 | 38 | return c.paint 39 | } 40 | 41 | // CanShow decides if this car needs to be displayed. 42 | func (c *Car) CanShow() bool { 43 | if e := os.Getenv("BULLETTRAIN_CAR_RUBY_SHOW"); e == "true" { 44 | return true 45 | } 46 | 47 | var d string 48 | if d = c.Pwd; d == "" { 49 | return false 50 | } 51 | 52 | // Show when .rb files exist in current directory 53 | rbPattern := fmt.Sprintf("%s%s*.rb", d, string(os.PathSeparator)) 54 | rbFile, _ := filepath.Glob(rbPattern) 55 | if rbFile != nil { 56 | return true 57 | } 58 | // Show when .ruby-version files exist in current directory 59 | rvPattern := fmt.Sprintf("%s%s*.ruby-version", d, string(os.PathSeparator)) 60 | rvFile, _ := filepath.Glob(rvPattern) 61 | if rvFile != nil { 62 | return true 63 | } 64 | 65 | return false 66 | } 67 | 68 | // Render builds and passes the end product of a completely composed car onto 69 | // the channel. 70 | func (c *Car) Render(out chan<- string) { 71 | defer close(out) // Always close the channel! 72 | 73 | var si string 74 | if si = os.Getenv("BULLETTRAIN_CAR_RUBY_SYMBOL_ICON"); si == "" { 75 | si = symbolIcon 76 | } 77 | 78 | var sp string 79 | if sp = os.Getenv("BULLETTRAIN_CAR_RUBY_SYMBOL_PAINT"); sp == "" { 80 | sp = symbolPaint 81 | } 82 | 83 | var s string 84 | if s = os.Getenv("BULLETTRAIN_CAR_RUBY_TEMPLATE"); s == "" { 85 | s = carTemplate 86 | } 87 | 88 | funcMap := template.FuncMap{ 89 | // Pipeline functions for colouring. 90 | "c": func(t string) string { return ansi.Color(t, c.GetPaint()) }, 91 | "cs": func(t string) string { return ansi.Color(t, sp) }, 92 | } 93 | 94 | var version string 95 | cmd := exec.Command("ruby", "--version") 96 | cmdOut, err := cmd.CombinedOutput() 97 | if err == nil { 98 | // language=GoRegExp 99 | re := regexp.MustCompile(`([0-9.]+)`) 100 | versionArr := re.FindStringSubmatch(string(cmdOut)) 101 | if len(versionArr) > 0 { 102 | version = versionArr[1] 103 | } 104 | } else { 105 | version = strings.Replace(string(cmdOut), "\n", " ", -1) 106 | } 107 | 108 | tpl := template.Must(template.New("ruby").Funcs(funcMap).Parse(s)) 109 | data := struct { 110 | Icon string 111 | Info string 112 | }{Icon: si, Info: version} 113 | fromTpl := new(bytes.Buffer) 114 | err = tpl.Execute(fromTpl, data) 115 | if err != nil { 116 | log.Fatalf("Can't generate the ruby template: %s", err.Error()) 117 | } 118 | 119 | out <- fromTpl.String() 120 | } 121 | 122 | // GetSeparatorPaint overrides the Fg/Bg colours of the right hand side 123 | // separator through ENV variables. 124 | func (c *Car) GetSeparatorPaint() string { 125 | return os.Getenv("BULLETTRAIN_CAR_RUBY_SEPARATOR_PAINT") 126 | } 127 | 128 | // GetSeparatorSymbol overrides the symbol of the right hand side 129 | // separator through ENV variables. 130 | func (c *Car) GetSeparatorSymbol() string { 131 | return os.Getenv("BULLETTRAIN_CAR_RUBY_SEPARATOR_SYMBOL") 132 | } 133 | 134 | // GetSeparatorTemplate overrides the template of the right hand side 135 | // separator through ENV variable. 136 | func (c *Car) GetSeparatorTemplate() string { 137 | return os.Getenv("BULLETTRAIN_CAR_RUBY_SEPARATOR_TEMPLATE") 138 | } 139 | -------------------------------------------------------------------------------- /src/car/status/status.go: -------------------------------------------------------------------------------- 1 | package carStatus 2 | 3 | import ( 4 | "bytes" 5 | "log" 6 | "os" 7 | "text/template" 8 | 9 | "github.com/bullettrain-sh/bullettrain-go-core/src/ansi" 10 | ) 11 | 12 | const ( 13 | carPaint = "255:160" 14 | symbolIcon = "\uF490" //  15 | symbolPaint = "220:160" 16 | // language=GoTemplate 17 | carTemplate = `{{.Icon | printf "%s " | cs}}{{.Code | c}}` 18 | ) 19 | 20 | // Status Car 21 | type Car struct { 22 | paint string 23 | } 24 | 25 | // GetPaint returns the calculated end paint string for the car. 26 | func (c *Car) GetPaint() string { 27 | if c.paint = os.Getenv("BULLETTRAIN_CAR_STATUS_PAINT"); c.paint == "" { 28 | c.paint = carPaint 29 | } 30 | 31 | return c.paint 32 | } 33 | 34 | // CanShow decides if this car needs to be displayed. 35 | func (c *Car) CanShow() bool { 36 | return len(os.Args) > 1 && os.Args[1] != "" && os.Args[1] != "0" 37 | } 38 | 39 | // Render builds and passes the end product of a completely composed car onto 40 | // the channel. 41 | func (c *Car) Render(out chan<- string) { 42 | defer close(out) 43 | 44 | var statusSymbol string 45 | if statusSymbol = os.Getenv("BULLETTRAIN_CAR_STATUS_SYMBOL_ICON"); statusSymbol == "" { 46 | statusSymbol = symbolIcon 47 | } 48 | 49 | var statusSymbolPaint string 50 | if statusSymbolPaint = os.Getenv("BULLETTRAIN_CAR_STATUS_SYMBOL_PAINT"); statusSymbolPaint == "" { 51 | statusSymbolPaint = symbolPaint 52 | } 53 | 54 | var s string 55 | if s = os.Getenv("BULLETTRAIN_CAR_STATUS_TEMPLATE"); s == "" { 56 | s = carTemplate 57 | } 58 | 59 | funcMap := template.FuncMap{ 60 | // Pipeline functions for colouring. 61 | "c": func(t string) string { return ansi.Color(t, c.GetPaint()) }, 62 | "cs": func(t string) string { return ansi.Color(t, statusSymbolPaint) }, 63 | } 64 | 65 | tpl := template.Must(template.New("status").Funcs(funcMap).Parse(s)) 66 | data := struct { 67 | Icon string 68 | Code string 69 | }{Icon: statusSymbol, Code: os.Args[1]} 70 | fromTpl := new(bytes.Buffer) 71 | err := tpl.Execute(fromTpl, data) 72 | if err != nil { 73 | log.Fatalf("Can't generate the user template: %s", err.Error()) 74 | } 75 | 76 | out <- fromTpl.String() 77 | } 78 | 79 | // GetSeparatorPaint overrides the Fg/Bg colours of the right hand side 80 | // separator through ENV variables. 81 | func (c *Car) GetSeparatorPaint() string { 82 | return os.Getenv("BULLETTRAIN_CAR_STATUS_SEPARATOR_PAINT") 83 | } 84 | 85 | // GetSeparatorSymbol overrides the symbol of the right hand side 86 | // separator through ENV variables. 87 | func (c *Car) GetSeparatorSymbol() string { 88 | return os.Getenv("BULLETTRAIN_CAR_STATUS_SEPARATOR_SYMBOL") 89 | } 90 | 91 | // GetSeparatorTemplate overrides the template of the right hand side 92 | // separator through ENV variable. 93 | func (c *Car) GetSeparatorTemplate() string { 94 | return os.Getenv("BULLETTRAIN_CAR_STATUS_SEPARATOR_TEMPLATE") 95 | } 96 | -------------------------------------------------------------------------------- /src/car/time/time.go: -------------------------------------------------------------------------------- 1 | package carTime 2 | 3 | import ( 4 | "bytes" 5 | "log" 6 | "os" 7 | "text/template" 8 | "time" 9 | 10 | "github.com/bullettrain-sh/bullettrain-go-core/src/ansi" 11 | ) 12 | 13 | const ( 14 | carPaint = "black:white" 15 | symbolIcon = "\uF43A" //  16 | symbolPaint = "black:white" 17 | // language=GoTemplate 18 | carTemplate = `{{.Icon | printf " %s " | cs}}{{.Time | c}}` 19 | ) 20 | 21 | // Time Car 22 | type Car struct { 23 | paint string 24 | } 25 | 26 | // GetPaint returns the calculated end paint string for the car. 27 | func (c *Car) GetPaint() string { 28 | if c.paint = os.Getenv("BULLETTRAIN_CAR_TIME_PAINT"); c.paint == "" { 29 | c.paint = carPaint 30 | } 31 | 32 | return c.paint 33 | } 34 | 35 | // CanShow decides if this car needs to be displayed. 36 | func (c *Car) CanShow() bool { 37 | s := false 38 | if e := os.Getenv("BULLETTRAIN_CAR_TIME_SHOW"); e == "true" { 39 | s = true 40 | } 41 | 42 | return s 43 | } 44 | 45 | // Render builds and passes the end product of a completely composed car onto 46 | // the channel. 47 | func (c *Car) Render(out chan<- string) { 48 | defer close(out) 49 | 50 | var timeSymbol string 51 | if timeSymbol = os.Getenv("BULLETTRAIN_CAR_TIME_SYMBOL_ICON"); timeSymbol == "" { 52 | timeSymbol = symbolIcon 53 | } 54 | 55 | var timeSymbolPaint string 56 | if timeSymbolPaint = os.Getenv("BULLETTRAIN_CAR_TIME_SYMBOL_PAINT"); timeSymbolPaint == "" { 57 | timeSymbolPaint = symbolPaint 58 | } 59 | 60 | n := time.Now() 61 | t := n.Format("15:04:05") 62 | if h := os.Getenv("BULLETTRAIN_CAR_TIME_12HR"); h == "true" { 63 | t = n.Format("3:04:05") 64 | } 65 | 66 | var s string 67 | if s = os.Getenv("BULLETTRAIN_CAR_TIME_TEMPLATE"); s == "" { 68 | s = carTemplate 69 | } 70 | 71 | funcMap := template.FuncMap{ 72 | // Pipeline functions for colouring. 73 | "c": func(t string) string { return ansi.Color(t, c.GetPaint()) }, 74 | "cs": func(t string) string { return ansi.Color(t, timeSymbolPaint) }, 75 | } 76 | 77 | tpl := template.Must(template.New("time").Funcs(funcMap).Parse(s)) 78 | data := struct { 79 | Icon string 80 | Time string 81 | }{Icon: timeSymbol, Time: t} 82 | timeFromTpl := new(bytes.Buffer) 83 | err := tpl.Execute(timeFromTpl, data) 84 | if err != nil { 85 | log.Fatalf("Can't generate the time template: %s", err.Error()) 86 | } 87 | 88 | out <- timeFromTpl.String() 89 | } 90 | 91 | // GetSeparatorPaint overrides the Fg/Bg colours of the right hand side 92 | // separator through ENV variables. 93 | func (c *Car) GetSeparatorPaint() string { 94 | return os.Getenv("BULLETTRAIN_CAR_TIME_SEPARATOR_PAINT") 95 | } 96 | 97 | // GetSeparatorSymbol overrides the symbol of the right hand side 98 | // separator through ENV variables. 99 | func (c *Car) GetSeparatorSymbol() string { 100 | return os.Getenv("BULLETTRAIN_CAR_TIME_SEPARATOR_SYMBOL") 101 | } 102 | 103 | // GetSeparatorTemplate overrides the template of the right hand side 104 | // separator through ENV variable. 105 | func (c *Car) GetSeparatorTemplate() string { 106 | return os.Getenv("BULLETTRAIN_CAR_TIME_SEPARATOR_TEMPLATE") 107 | } 108 | -------------------------------------------------------------------------------- /src/car/user/user.go: -------------------------------------------------------------------------------- 1 | package carUser 2 | 3 | import ( 4 | "bytes" 5 | "log" 6 | "os" 7 | "os/user" 8 | "text/template" 9 | 10 | "github.com/bullettrain-sh/bullettrain-go-core/src/ansi" 11 | ) 12 | 13 | const ( 14 | carPaint = "black:white" 15 | // language=GoTemplate 16 | carTemplate = `{{.User | c}}` 17 | ) 18 | 19 | // User car 20 | type Car struct { 21 | paint string 22 | } 23 | 24 | // GetPaint returns the calculated end paint string for the car. 25 | func (c *Car) GetPaint() string { 26 | if c.paint = os.Getenv("BULLETTRAIN_CAR_USER_PAINT"); c.paint == "" { 27 | c.paint = carPaint 28 | } 29 | 30 | return c.paint 31 | } 32 | 33 | // CanShow decides if this car needs to be displayed. 34 | func (c *Car) CanShow() bool { 35 | s := true 36 | if e := os.Getenv("BULLETTRAIN_CAR_USER_SHOW"); e == "false" { 37 | s = false 38 | } 39 | 40 | return s 41 | } 42 | 43 | // Render builds and passes the end product of a completely composed car onto 44 | // the channel. 45 | func (c *Car) Render(out chan<- string) { 46 | defer close(out) 47 | 48 | var username string 49 | u, e := user.Current() 50 | if e == nil { 51 | username = u.Username 52 | } 53 | 54 | var s string 55 | if s = os.Getenv("BULLETTRAIN_CAR_USER_TEMPLATE"); s == "" { 56 | s = carTemplate 57 | } 58 | 59 | funcMap := template.FuncMap{ 60 | // Pipeline functions for colouring. 61 | "c": func(t string) string { return ansi.Color(t, c.GetPaint()) }, 62 | } 63 | 64 | tpl := template.Must(template.New("user").Funcs(funcMap).Parse(s)) 65 | data := struct{ User string }{User: username} 66 | fromTpl := new(bytes.Buffer) 67 | err := tpl.Execute(fromTpl, data) 68 | if err != nil { 69 | log.Fatalf("Can't generate the user template: %s", err.Error()) 70 | } 71 | 72 | out <- fromTpl.String() 73 | } 74 | 75 | // GetSeparatorPaint overrides the Fg/Bg colours of the right hand side 76 | // separator through ENV variables. 77 | func (c *Car) GetSeparatorPaint() string { 78 | return os.Getenv("BULLETTRAIN_CAR_USER_SEPARATOR_PAINT") 79 | } 80 | 81 | // GetSeparatorSymbol overrides the symbol of the right hand side 82 | // separator through ENV variables. 83 | func (c *Car) GetSeparatorSymbol() string { 84 | return os.Getenv("BULLETTRAIN_CAR_USER_SEPARATOR_SYMBOL") 85 | } 86 | 87 | // GetSeparatorTemplate overrides the template of the right hand side 88 | // separator through ENV variable. 89 | func (c *Car) GetSeparatorTemplate() string { 90 | return os.Getenv("BULLETTRAIN_CAR_USER_SEPARATOR_TEMPLATE") 91 | } 92 | --------------------------------------------------------------------------------