Repository: icyphox/nicy Branch: master Commit: 530072eeebb4 Files: 14 Total size: 18.9 KB Directory structure: gitextract_0obo7rmq/ ├── .github/ │ └── workflows/ │ └── main.yml ├── .gitignore ├── .travis.yml ├── examples/ │ ├── config.nims │ ├── fish.nim │ ├── power.nim │ ├── pure.nim │ ├── simple.nim │ └── test.nim ├── license ├── nicy.nimble ├── readme.md └── src/ ├── nicy.nim └── nicypkg/ └── functions.nim ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/main.yml ================================================ name: website on: [push] #on: # push: # tags: # - 'v*.*.*' jobs: publish: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v1 - name: Set output id: vars run: | echo "::set-output name=tag::${GITHUB_REF:10}" echo "::set-output name=sha::$(git rev-parse HEAD)" - name: Cache choosenim id: cache-choosenim uses: actions/cache@v1 with: path: ~/.choosenim key: ${{ runner.os }}-choosenim-stable - name: Cache nimble id: cache-nimble uses: actions/cache@v1 with: path: ~/.nimble key: ${{ runner.os }}-nimble-stable - uses: jiro4989/setup-nim-action@v1.0.2 with: nim-version: 'stable' - name: Build and test run: | nimble build -Y #nimble test -Y - name: Build doc env: RELEASE_COMMIT: ${{ steps.vars.outputs.sha }} run: | nimble doc --git.url:https://github.com/$GITHUB_REPOSITORY --git.commit:$RELEASE_COMMIT src/nicy.nim nimble doc --git.url:https://github.com/$GITHUB_REPOSITORY --git.commit:$RELEASE_COMMIT src/nicypkg/functions.nim # Deploying documentation of the latest version. mkdir -p ./public mv nicy.html functions.html nimdoc.out.css ./public/ cd ./public/ ln -s ./nicy.html index.html - name: Deploy if: success() uses: crazy-max/ghaction-github-pages@v1.3.0 with: target_branch: gh-pages build_dir: ./public env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ================================================ FILE: .gitignore ================================================ nicy examples/fish examples/pure examples/simple examples/test examples/power *.html *.css ================================================ FILE: .travis.yml ================================================ language: c cache: ccache cache: directories: - .cache matrix: include: - os: linux env: CHANNEL=stable compiler: gcc - os: linux env: CHANNEL=devel compiler: gcc - os: osx env: CHANNEL=stable compiler: clang allow_failures: - env: CHANNEL=devel - os: osx fast_finish: true env: global: - PROGNAME="$(basename ${TRAVIS_BUILD_DIR})" - NIMFILE="src/${PROGNAME}.nim" - BINFILE="src/${PROGNAME}" - ASSETFILE="${PROGNAME}-${TRAVIS_TAG}-linux64" install: - export CHOOSENIM_NO_ANALYTICS=1 - curl https://nim-lang.org/choosenim/init.sh -sSf > init.sh - sh init.sh -y - export PATH=~/.nimble/bin:$PATH - echo "export PATH=~/.nimble/bin:$PATH" >> ~/.profile - choosenim $CHANNEL script: - cd "${TRAVIS_BUILD_DIR}" - nim c "${NIMFILE}" - "${BINFILE}" before_deploy: - cd "${TRAVIS_BUILD_DIR}" - cp "${BINFILE}" "${ASSETFILE}" deploy: provider: releases api_key: "${GITHUB_OAUTH_TOKEN}" file: "${ASSETFILE}" skip_cleanup: true on: tags: true ================================================ FILE: examples/config.nims ================================================ switch("path", "$projectDir/../src") ================================================ FILE: examples/fish.nim ================================================ # fish’s default prompt '~>' import nicy, strformat let prompt = color("> ", green) tilde = tilde(getCwd()) echo fmt"{tilde}{prompt}" ================================================ FILE: examples/power.nim ================================================ # power by @xyb import nicypkg/functions, strformat let prompt = uidsymbol(root = color("#", fg = red, bg = white), user = color("$", green)) nl = "\n" cwd = color(tilde(getCwd()), cyan) ahead = color("⬆ ", yellow) behind = color("⬇ ", yellow) untracked = color("?", yellow) changed = color("✎", yellow) staged = color("✔ ", yellow) conflicted = color("✼", yellow) stash = color("⎘", yellow) gs = newGitStats() gitStatus = gs.status(ahead, behind, untracked, changed, staged, conflicted, stash) gitBranch = color(gs.branch(), yellow) git = gitBranch & gitStatus # the prompt echo fmt"{nl}{virtualenv()}{cwd}{git}{nl}{prompt} " ================================================ FILE: examples/pure.nim ================================================ # pure by @sindresorhus (kinda) import nicy, strformat let prompt = color("❯ ", magenta) tilde = color(tilde(getCwd()), cyan) git = color(gitBranch() & gitStatus("*", ""), red) nl = "\n" echo fmt"{nl}{tilde}{git}{nl}{prompt}" ================================================ FILE: examples/simple.nim ================================================ # ‘user@host $’ prompt import nicy, strformat let user = color(user(), green) host = color(host(), red) prompt = color("$ ", cyan) at = color("@", yellow) echo fmt"{user}{at}{host} {prompt}" ================================================ FILE: examples/test.nim ================================================ import nicy, strformat let prompt = uidsymbol("#", "$") nl = "\n" echo fmt"{nl}{prompt}" ================================================ FILE: license ================================================ MIT License Copyright (c) 2018 Anirudh Oppiliappan 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: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 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. ================================================ FILE: nicy.nimble ================================================ # Package version = "2.5.1" author = "Anirudh" description = "A nice and icy ZSH prompt in Nim" license = "MIT" srcDir = "src" installExt = @["nim"] bin = @["nicy"] # Dependencies requires "nim >= 0.18.0" ================================================ FILE: readme.md ================================================

> A nice and icy zsh and bash prompt in Nim [![Build Status](https://travis-ci.org/icyphox/nicy.svg?branch=master)](https://travis-ci.org/icyphox/nicy) ![scrot](https://x.icyphox.sh/SltdI.png) ### Why? I’ve always wanted to minimize my reliance on frameworks like [oh-my-zsh](https://github.com/robbyrussell/oh-my-zsh), so I figured, why not write my own ZSH prompt in my new favourite language? Turned out to be a really fun exercise. ### Highlights - Written in Nim 👑 - Fast (in theory, since Nim compiles to C) - Pretty defaults. - Plugin-like system for prompt customization, in case you didn’t like the pretty defaults. - Support both zsh and bash. - Fun, I guess. ## Installation **Note**: It’s probably a good idea to uninstall `oh-my-zsh`, or any other plugin framework you’re using altogether. It may cause conflicts. ```console $ nimble install nicy ``` Don’t know what that is? New to Nim? Check out the Nim [docs](https://nim-lang.org/documentation.html). `nimble` is packaged with Nim by default. ## Quick start Add this to your `~/.zshrc`. If you installed via `nimble`, set `PROMPT` to `$(~/.nimble/bin/nicy)`. ```zsh autoload -Uz add-zsh-hook _nicy_prompt() { PROMPT=$("/path/to/nicy") } add-zsh-hook precmd _nicy_prompt ``` Make sure you disable all other themes. Nicy supports BASH also, and you could simply add it to your `~/.bashrc` or `~/.bash_profile`: ```bash function nicy_prompt_command { PS1=$(/path/to/nicy) } PROMPT_COMMAND="nicy_prompt_command" ``` ## Configuration If you want to configure `nicy` as it is, you’ll have to edit the `src/nicy.nim` file and recompile. Messy, I know. ### Build your own prompt Alternatively, you can just as easily write your own prompt in Nim using `nicy`’s built-in API. Refer to the [Examples](#Examples) section for some insight. Once you’re done, compile it and add a similar function to your `.zshrc` as above, replacing `PROMPT` with the path to your own binary. ### Examples ```nim # ‘user@host $’ prompt import nicy, strformat let user = color(user(), green) host = color(host(), red) prompt = color("$ ", cyan) at = color("@", yellow) echo fmt"{user}{at}{host} {prompt}" ``` ```nim # fish’s default prompt '~>' import nicy, strformat let prompt = color("> ", green) tilde = tilde(getCwd()) echo fmt"{tilde}{prompt}" ``` ```nim # pure by @sindresorhus (kinda) import nicy, strformat let prompt = color("❯ ", magenta) tilde = color(tilde(getCwd()), cyan) git = color(gitBranch() & gitStatus("*", ""), red) nl = "\n" echo fmt"{tilde}{git}{nl}{prompt}" ``` ```nim # switching by return code import nicy, strformat let prompt = returnCondition(ok = "👍", ng = "👎") & " " tilde = color(tilde(getCwd()), cyan) git = color(gitBranch() & gitStatus("*", ""), red) nl = "\n" echo fmt"{tilde}{git}{nl}{prompt}" # ~ master # 👍 ``` If you like to know more details about git status, you may want to try the following powerful example which is including the number of untracked, modified, staged, conflicted and the number of commits your local branch is ahead, behind, etc: ``` nim c -d:release examples/power.nim ``` ### API **`zeroWidth(s: string): string`** Returns the given string wrapped in zsh zero-width codes. Useful for prompt alignment and cursor positioning. All procs below return strings wrapped by this. **`foreground(s: string, color: Color): string`** Returns the given string, colorized. Possible colors are `black`, `red`, `green`, `blue`, `cyan`, `yellow`, `magenta`, `white`. **`background(s, color: string): string`** Returns the given string with its background colorized. Same possible colors as above. **`bold(s: string): string`** Makes the given string bold. **`underline(s: string): string`** Adds an underline to the given string. **`italics(s: string): string`** Italicizes the given text. **May not work in some terminal emulators!** **`reverse(s: string): string`** Swaps the foreground/background colors for the given string. **`reset(s: string): string`** Resets all attributes. Useful for disabling all styling. **`color*(s: string, fg, bg = Color.none, b, u, r = false): string`** Convenience proc that sets all attributes to a given string. `fg`: foreground, `bg`: background, `b`: bold, `u`: underline, `r`: reverse **`horizontalRule(c: char): string`** Returns a string of characters `c`, having the length of the current terminal width. Useful for positioning right-side prompts. **`tilde(path: string): string`** If `path` starts with `/home/user`, it is replaced by a `~/`. **`getCwd(): string`** Returns the full path of the current working directory, or returns the string `[not found]` if current path doesn’t exist. (eg: `rm -rf ../curpath`) **`virtualenv(): string`** Returns the current virtualenv name if in one. **`gitBranch(): string`** Returns the current git branch, if in a git directory. **`gitStatus(dirty, clean: string): string`** Returns either `dirty` or `clean` if in a git repository. For example, return `•` if clean and `×` if dirty. **`user(): string`** Returns the current username. **`host(): string`** Returns the current hostname. **`returnCondition*(ok: string, ng: string, delimiter = "."): string`** If the return code is `0` then returns `ok` string, otherwise `ng`. **`returnCondition*(ok: proc(): string, ng: proc(): string, delimiter = "."): string`** Returns result of `ok` proc or `ng` proc. If the return code is `0` then this proc calls `ok` proc, otherwise calls `ng` proc. **`shellName*: string`** Contains the name of shell in which your prompt progoram is running. Currently it may be `zsh` or `bash`. You can specify it during compilation using the switch `-d:zsh` or `-d:bash`, or you can let the program detect it automatically, which may slow it down by a few milliseconds. #### GitStats API **`newGitStats*(): GitStats`** Returns a `GitStats` object which contains the name of the local branch, the name of remote reference, number of commits your local branch is ahead or behind remote ref, number of untracked, modified, staged, conflicted, and the number of stashed changes. **`branch*(gs: GitStats, detachedPrefix = "", postfix = " "): string`** Returns the current git branch name. **`status*(gs: GitStats, ahead, behind, untracked, changed, staged, conflicted, stash: string, separator, postfix = " "): string`** Returns the git status string. **`dirty*(gs: GitStats): bool`** Returns whether the current directory has been changed. ## Contributing Bad code? New feature in mind? Open an issue. Better still, learn [Nim](https://nim-lang.org/documentation.html) and shoot a PR :sparkles: ## License MIT © [Anirudh Oppiliappan](https://icyphox.sh) ================================================ FILE: src/nicy.nim ================================================ import nicypkg/functions, strformat export functions when isMainModule: let prompt = color("› ", magenta) nl = "\n" gitBranch = color(gitBranch(), yellow) cwd = color(tilde(getCwd()), cyan) dirty = color("×", red) clean = color("•", green) let git = gitBranch & gitStatus(dirty, clean) # the prompt echo fmt"{nl}{virtualenv()}{cwd}{git}{nl}{prompt}" ================================================ FILE: src/nicypkg/functions.nim ================================================ import nre, os, osproc, posix, strformat, strutils, tables, terminal type Color* = enum none = -1 black = 0 red = 1 green = 2 yellow = 3 blue = 4 magenta = 5 cyan = 6 white = 7 GitStats* = object branchName*: string detached*: bool localRef*: string remoteRef*: string ahead*: int behind*: int untracked*: int conflicted*: int changed*: int staged*: int stash*: int when defined(bash): # shell switch during compilation using "-d:bash" proc isBash(): bool = result = true elif defined(zsh): # or "-d:zsh" proc isBash(): bool = result = false else: # or detect shell automatic proc isBash(): bool = result = false let ppid = getppid() let (o, err) = execCmdEx(fmt"ps -p {ppid} -oargs=") if err == 0: let name = o.strip() if name.endswith("bash"): result = true let shellName* = # make sure the shell detection runs once only if isBash(): "bash" else: "zsh" # default proc zeroWidth*(s: string): string = if shellName == "bash": return fmt"\[{s}\]" else: # zsh, default return fmt"%{{{s}%}}" proc foreground*(s: string, color: Color): string = let c = "\x1b[" & $(ord(color)+30) & "m" result = fmt"{zeroWidth($c)}{s}" proc background*(s: string, color: Color): string = let c = "\x1b[" & $(ord(color)+40) & "m" result = fmt"{zeroWidth($c)}{s}" proc bold*(s: string): string = const b = "\x1b[1m" result = fmt"{zeroWidth(b)}{s}" proc underline*(s: string): string = const u = "\x1b[4m" result = fmt"{zeroWidth(u)}{s}" proc italics*(s: string): string = const i = "\x1b[3m" result = fmt"{zeroWidth(i)}{s}" proc reverse*(s: string): string = const rev = "\x1b[7m" result = fmt"{zeroWidth(rev)}{s}" proc reset*(s: string): string = const res = "\x1b[0m" result = fmt"{s}{zeroWidth(res)}" proc color*(s: string, fg, bg = Color.none, b, u, r = false): string = if s.len == 0: return result = s if fg != Color.none: result = foreground(result, fg) if bg != Color.none: result = background(result, bg) if b: result = bold(result) if u: result = underline(result) if r: result = reverse(result) result = reset(result) proc horizontalRule*(c = '-'): string = for _ in 1 .. terminalWidth(): result &= c result &= zeroWidth("\n") proc tilde*(path: string): string = # donated by @misterbianco let home = getHomeDir() if path.startsWith(home): result = "~/" & path.split(home)[1] else: result = path proc getCwd*(): string = result = try: getCurrentDir() & " " except OSError: "[not found]" proc virtualenv*(): string = let env = getEnv("VIRTUAL_ENV") result = extractFilename(env) & " " if env.len == 0: result = "" proc gitBranch*(): string = let (o, err) = execCmdEx("git status") if err == 0: let firstLine = o.split("\n")[0].split(" ") result = firstLine[^1] & " " else: result = "" proc gitStatus*(dirty, clean: string): string = let (o, err) = execCmdEx("git status --porcelain") result = if err == 0: if o.len != 0: dirty else: clean else: "" proc user*(): string = result = $getpwuid(getuid()).pw_name proc host*(): string = const size = 64 result = newString(size) discard gethostname(cstring(result), size) proc uidsymbol*(root, user: string): string = result = if getuid() == 0: root else: user proc returnCondition*(ok: string, ng: string, delimiter = "."): string = result = fmt"%(?{delimiter}{ok}{delimiter}{ng})" proc returnCondition*(ok: proc(): string, ng: proc(): string, delimiter = "."): string = result = returnCondition(ok(), ng(), delimiter) proc getGitDetachedBranch(): string = let (o, err) = execCmdEx("git describe --tags --always") if err == 0: result = o.strip() proc getStashCount(): int = let (o, err) = execCmdEx("git stash list") if err == 0: result = o.count("\n") proc newGitStats*(): GitStats = let (o, err) = execCmdEx("git status --porcelain -b") if err == 0: let pattern = re"^## (?P\S+?)(\.{3}(?P\S+?)( \[(ahead (?P\d+)(, )?)?(behind (?P\d+))?\])?)?$" let lines = o.split("\n") let firstLine = lines[0] let matched = firstLine.match(pattern) if matched.isSome: let status = matched.get.captures.toTable result.localRef = status["local"] result.remoteRef = status.getOrDefault("remote", "") result.ahead = parseInt(status.getOrDefault("ahead", "0")) result.behind = parseInt(status.getOrDefault("behind", "0")) result.branchName = status["local"] else: result.branchName = getGitDetachedBranch() if result.branchName.len > 0: result.detached = true else: result.branchName = "Big Bang" result.detached = false for line in lines[1..^1]: if line.len < 2: continue let code = line[0..<2] if code == "??": result.untracked.inc elif code in @["DD", "AU", "UD", "UA", "DU", "AA", "UU"]: result.conflicted.inc else: if code[1] != ' ': result.changed.inc if code[0] != ' ': result.staged.inc result.stash = getStashCount() proc dirty*(gs: GitStats): bool = (gs.untracked + gs.changed + gs.staged + gs.conflicted) > 1 proc branch*(gs: GitStats, detachedPrefix = "", postfix = " "): string = if gs.branchName.len > 0: result = gs.branchName & postfix if gs.detached and detachedPrefix.len > 0: result = detachedPrefix & result proc status*(gs: GitStats, ahead, behind, untracked, changed, staged, conflicted, stash: string, separator, postfix = " "): string = var parts = newSeq[string]() template add(gs: GitStats, field: untyped, value: string, ss: seq[string]) = if gs.`field` > 0: if gs.`field` > 1: ss.add($gs.`field` & value) else: ss.add(value) if gs.branchName.len > 0: add(gs, ahead, ahead, parts) add(gs, behind, behind, parts) add(gs, untracked, untracked, parts) add(gs, changed, changed, parts) add(gs, staged, staged, parts) add(gs, conflicted, conflicted, parts) add(gs, stash, stash, parts) result = parts.join(separator) if result.len > 0 and postfix.len > 0: result &= postfix