Rust implementation (#197)

This commit is contained in:
Denis Isidoro 2020-03-04 18:01:23 -03:00 committed by GitHub
parent 684702e770
commit c5cd85bb41
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
75 changed files with 1550 additions and 1955 deletions

View file

@ -1,26 +0,0 @@
# Clojure CircleCI 2.0 configuration file
#
# Check https://circleci.com/docs/2.0/language-clojure/ for more details
#
version: 2
job_defaults: &defaults
docker:
- image: frolvlad/alpine-bash
working_directory: ~/repo
environment:
ENVIRONMENT: "test"
TERM: "dumb"
jobs:
tests:
<<: *defaults
steps:
- checkout
- run: ./test/run
workflows:
version: 2
run-tests:
jobs:
- tests

42
.github/workflows/cd.yml vendored Normal file
View file

@ -0,0 +1,42 @@
name: Publish
on:
push:
tags:
- '*'
jobs:
publish:
name: Publish for ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
# This should work with only the `include`s but it currently doesn't because of this bug:
# https://github.community/t5/How-to-use-Git-and-GitHub/GitHub-Actions-Matrix-options-dont-work-as-documented/td-p/29558
target: [x86_64-osx, x86_64-unknown-linux-musl, armv7-unknown-linux-musleabihf, armv7-linux-androideabi, aarch64-linux-android]
include:
- os: macos-latest
target: x86_64-osx
- os: ubuntu-latest
target: x86_64-unknown-linux-musl
- os: ubuntu-latest
target: armv7-unknown-linux-musleabihf
- os: ubuntu-latest
target: armv7-linux-androideabi
- os: ubuntu-latest
target: aarch64-linux-android
steps:
- uses: hecrj/setup-rust-action@v1.3.1
with:
rust-version: stable
- uses: actions/checkout@v1
- name: Build
run: scripts/action release ${{ matrix.target }}
- name: Upload binaries to release
uses: svenstaro/upload-release-action@v1-release
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: target/tar/navi.tar.gz
asset_name: navi-${{ matrix.target }}.tar.gz
tag: ${{ github.ref }}

79
.github/workflows/ci.yml vendored Normal file
View file

@ -0,0 +1,79 @@
# Based on https://github.com/actions-rs/meta/blob/master/recipes/quickstart.md
#
# While our "example" application has the platform-specific code,
# for simplicity we are compiling and testing everything on the Ubuntu environment only.
# For multi-OS testing see the `cross.yml` workflow.
on: [push]
name: Quickstart
jobs:
check:
name: Check
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- name: Run cargo check
uses: actions-rs/cargo@v1
continue-on-error: false
with:
command: check
test:
name: Test Suite
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- name: Run cargo test
uses: actions-rs/cargo@v1
continue-on-error: false
with:
command: test
lints:
name: Lints
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
components: rustfmt, clippy
- name: Run cargo fmt
uses: actions-rs/cargo@v1
continue-on-error: false
with:
command: fmt
args: --all -- --check
- name: Run cargo clippy
uses: actions-rs/cargo@v1
continue-on-error: false
with:
command: clippy
args: -- -D warnings

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/target
**/*.rs.bk

320
Cargo.lock generated Normal file
View file

@ -0,0 +1,320 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "aho-corasick"
version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ansi_term"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"hermit-abi 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "bitflags"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "clap"
version = "2.33.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
"vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "heck"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "hermit-abi"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "libc"
version = "0.2.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "memchr"
version = "2.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "navi"
version = "2.0.0"
dependencies = [
"regex 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
"structopt 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
"termion 1.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "numtoa"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "proc-macro-error"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro-error-attr 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"rustversion 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "proc-macro-error-attr"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"rustversion 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)",
"syn-mid 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "proc-macro2"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "quote"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "redox_syscall"
version = "0.1.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "redox_termios"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "regex"
version = "1.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"aho-corasick 0.7.8 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"regex-syntax 0.6.14 (registry+https://github.com/rust-lang/crates.io-index)",
"thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "regex-syntax"
version = "0.6.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "rustversion"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "strsim"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "structopt"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"structopt-derive 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "structopt-derive"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro-error 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "syn"
version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "syn-mid"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "termion"
version = "1.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
"numtoa 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "thread_local"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "unicode-segmentation"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unicode-width"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unicode-xid"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "vec_map"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[metadata]
"checksum aho-corasick 0.7.8 (registry+https://github.com/rust-lang/crates.io-index)" = "743ad5a418686aad3b87fd14c43badd828cf26e214a00f92a384291cf22e1811"
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
"checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9"
"checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
"checksum hermit-abi 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "1010591b26bbfe835e9faeabeb11866061cc7dcebffd56ad7d0942d0e61aefd8"
"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
"checksum libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)" = "eb147597cdf94ed43ab7a9038716637d2d1bf2bc571da995d0028dec06bd3018"
"checksum memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
"checksum numtoa 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef"
"checksum proc-macro-error 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)" = "052b3c9af39c7e5e94245f820530487d19eb285faedcb40e0c3275132293f242"
"checksum proc-macro-error-attr 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)" = "d175bef481c7902e63e3165627123fff3502f06ac043d3ef42d08c1246da9253"
"checksum proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "6c09721c6781493a2a492a96b5a5bf19b65917fe6728884e7c44dd0c60ca3435"
"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe"
"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
"checksum regex 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "322cf97724bea3ee221b78fe25ac9c46114ebb51747ad5babd51a2fc6a8235a8"
"checksum regex-syntax 0.6.14 (registry+https://github.com/rust-lang/crates.io-index)" = "b28dfe3fe9badec5dbf0a79a9cccad2cfc2ab5484bdb3e44cbd1ae8b3ba2be06"
"checksum rustversion 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b3bba175698996010c4f6dce5e7f173b6eb781fce25d2cfc45e27091ce0b79f6"
"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
"checksum structopt 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "a1bcbed7d48956fcbb5d80c6b95aedb553513de0a1b451ea92679d999c010e98"
"checksum structopt-derive 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "095064aa1f5b94d14e635d0a5684cf140c43ae40a0fd990708d38f5d669e5f64"
"checksum syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)" = "123bd9499cfb380418d509322d7a6d52e5315f064fe4b3ad18a53d6b92c07859"
"checksum syn-mid 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a"
"checksum termion 1.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "c22cec9d8978d906be5ac94bceb5a010d885c626c4c8855721a4dbd20e3ac905"
"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
"checksum thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
"checksum unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0"
"checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479"
"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

13
Cargo.toml Normal file
View file

@ -0,0 +1,13 @@
[package]
name = "navi"
version = "2.0.0"
authors = ["Denis Isidoro <denis_isidoro@live.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
regex = "1.3.4"
structopt = "0.3"
termion = "1.5.5"
# lazy_static = "1.4.0"

View file

@ -1,14 +1,8 @@
install:
scripts/install
install:
scripts/make install
uninstall:
scripts/uninstall
release:
scripts/release
update:
scripts/update
lint:
scripts/lint
scripts/make uninstall

View file

@ -1,4 +1,6 @@
# navi <img src="https://user-images.githubusercontent.com/3226564/65362934-b4432500-dbdf-11e9-8f75-815fbc5cbf8f.png" alt="icon" height="28px"/> [![CircleCI](https://circleci.com/gh/denisidoro/navi.svg?style=svg)](https://circleci.com/gh/denisidoro/navi) ![GitHub release (latest by date including pre-releases)](https://img.shields.io/github/v/release/denisidoro/navi?include_prereleases)
> :information_source: This project has recently been rewritten from bash to Rust. If you're facing any issue after updating, please check [this thread](https://github.com/denisidoro/navi/issues/201).
> :information_source: This whole codebase is being rewritten in [Rust](https://www.rust-lang.org/). If you want to contribute, please refer to [this issue](https://github.com/denisidoro/navi/issues/154). :+1:

View file

@ -24,4 +24,7 @@ $ device: adb devices --- --headers 1 --column 1
# Start emulator
"$ANDROID_HOME/tools/emulator" -avd <emulator> -netdelay none -netspeed full
# TODO: remove this
echo "$(ls -la)"
$ emulator: "$ANDROID_HOME/tools/emulator" -list-avds

View file

@ -15,12 +15,13 @@ netstat -tn 2>/dev/null \
| sort -nr \
| head
# Find external, public IP address
dig +short myip.opendns.com @resolver1.opendns.com
# Find primary, local IP address
ifconfig \
| grep -Eo 'inet (addr:)?([0-9]*\.){3}[0-9]*' \
| grep -Eo '([0-9]*\.){3}[0-9]*' \
| grep -v '127.0.0.1' \
| tail -n1
# Find external, public IP address
dig +short myip.opendns.com @resolver1.opendns.com

15
navi
View file

@ -2,10 +2,15 @@
set -euo pipefail
export NAVI_HOME="$(cd "$(dirname "$0")" && pwd)"
source "${NAVI_HOME}/src/main.sh"
cd "$NAVI_HOME"
VERSION="0.18.3"
NAVI_ENV="${NAVI_ENV:-prod}"
release_bin="${NAVI_HOME}/target/release/navi"
debug_bin="${NAVI_HOME}/target/debug/navi"
opts::eval "$@"
main "$@"
if [ -f "$release_bin" ]; then
"$release_bin" "$@"
elif [ -f "$debug_bin" ]; then
"$debug_bin" "$@"
else
cargo run -- "$@"
fi

View file

@ -1,3 +0,0 @@
#!/usr/bin/env bash
bind '"\C-g": " \C-u \C-a\C-k`printf \"\\e\" && NAVI_USE_FZF_ALL_INPUTS=true navi --print`\e\C-e\C-y\C-a\C-d\C-y\ey\C-h\C-e\C-b"'

View file

@ -1,13 +0,0 @@
function navi-widget -d "Show cheat sheets"
begin
stty sane
env NAVI_USE_FZF_ALL_INPUTS=true navi --print (commandline) | perl -pe 'chomp if eof' | read -lz result
and commandline -- $result
end
commandline -f repaint
end
bind \cg navi-widget
if bind -M insert > /dev/null 2>&1
bind -M insert \cg navi-widget
end

View file

@ -1,12 +0,0 @@
_navi_path=$(dirname $0:A)
_call_navi() {
local buff="$BUFFER"
zle kill-whole-line
local cmd="$(NAVI_USE_FZF_ALL_INPUTS=true "${_navi_path}/navi" --print <> /dev/tty)"
zle -U "${buff}${cmd}"
}
zle -N _call_navi
bindkey '^g' _call_navi

50
scripts/action Executable file
View file

@ -0,0 +1,50 @@
#!/usr/bin/env bash
set -euo pipefail
##? action release
export NAVI_HOME="$(cd "$(dirname "$0")/.." && pwd)"
source "${NAVI_HOME}/scripts/install"
release() {
TAR_DIR="${NAVI_HOME}/target/tar"
target="${1:-}"
if [[ $target == *"osx"* ]]; then
echoerr "OSX cross-compile is impossible. Fallbacking to cargo..."
target=""
fi
cd "$NAVI_HOME"
rm -rf "${NAVI_HOME}/target" 2> /dev/null || true
if [ -n "$target" ]; then
cargo install cross 2> /dev/null || true
cross build --release --locked --target "$target"
bin_folder="${target}/release"
else
cargo build --release --locked
bin_folder="release"
fi
bin_path="${NAVI_HOME}/target/${bin_folder}/navi"
chmod +x "$bin_path"
mkdir -p "$TAR_DIR" 2> /dev/null || true
cp -r "${NAVI_HOME}/cheats" "$TAR_DIR"
cp -r "${NAVI_HOME}/shell" "$TAR_DIR"
cp "$bin_path" "$TAR_DIR"
cd "${NAVI_HOME}/target/tar"
tar -czf navi.tar.gz *
}
cmd="$1"
shift
case "$cmd" in
"release") release "$@" ;;
esac

33
scripts/brew Executable file
View file

@ -0,0 +1,33 @@
#!/usr/bin/env bash
set -euo pipefail
##? brew formula
export NAVI_HOME="$(cd "$(dirname "$0")/.." && pwd)"
source "${NAVI_HOME}/scripts/install"
gen_formula() {
version="$(latest_version_released)"
header "version: ${version}"
header "sha_for linux-amd64..."
sha_linux="$(sha_for_asset_on_github "$version" "linux-amd64")"
header "sha_for macos-amd64..."
sha_osx="$(sha_for_asset_on_github "$version" "macos-amd64")"
header "rb..."
curl -s https://raw.githubusercontent.com/denisidoro/homebrew-tools/master/navirs.rb \
| sed -E "s/version ['\"].*/version '${version}'/" \
| awk '!x{x=sub("sha256","sha_osx")}7' \
| awk '!x{x=sub("sha256","sha_linux")}7' \
| sed -E "s/sha_osx.*/sha256 \"${sha_osx}\"/" \
| sed -E "s/sha_linux.*/sha256 \"${sha_linux}\"/"
}
cmd="$1"
shift
case "$cmd" in
"formula") gen_formula "$@" ;;
esac

View file

@ -1,18 +1,11 @@
#!/usr/bin/env bash
set -euo pipefail
debian="${1:-false}"
export NAVI_HOME="$(cd "$(dirname "$0")/.." && pwd)"
if $debian; then
image="debian"
setup_cmd="apt update; apt install -y git curl;"
else
image="ellerbrock/alpine-bash-git"
fi
cd "$NAVI_HOME"
docker run \
-it \
--entrypoint /bin/bash \
-v "$(pwd):/navi" \
"$image" \
-c "${setup_cmd:-}git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf && yes | ~/.fzf/install; export PATH=$PATH:/navi; bash"
-e HOMEBREW_NO_AUTO_UPDATE=1 \
-it linuxbrew/alpine \
bash -c 'brew install denisidoro/tools/navirs; bash'

8
scripts/fix Executable file
View file

@ -0,0 +1,8 @@
#!/usr/bin/env bash
set -euo pipefail
export NAVI_HOME="$(cd "$(dirname "$0")/.." && pwd)"
cargo +nightly fix --clippy -Z unstable-options 2> /dev/null || true
cargo fix 2> /dev/null || true
cargo fmt 2> /dev/null || true

View file

@ -1,15 +1,126 @@
#!/usr/bin/env bash
set -euo pipefail
export NAVI_HOME="$(cd "$(dirname "$0")/.." && pwd)"
FIRST_INSTALL_DIR="/opt/navi"
SECOND_INSTALL_DIR="${HOME}/.navi"
script() {
echo "#!/usr/bin/env bash"
echo "${NAVI_HOME}/navi" '"$@"'
get_install_dir() {
local -r useless_folder="${FIRST_INSTALL_DIR}/useless"
local folder
mkdir -p "$useless_folder" 2>/dev/null \
&& folder="$FIRST_INSTALL_DIR" \
|| folder="$SECOND_INSTALL_DIR"
rm -r "$useless_folder" 2>/dev/null
echo "$folder"
}
folder="${1:-/usr/local/bin}"
bin="${folder}/navi"
echoerr() {
echo "$@" 1>&2
}
script > "$bin"
chmod +x "$bin"
command_exists() {
type "$1" &>/dev/null
}
sha256() {
if command_exists sha256sum; then
sha256sum
elif command_exists shasum; then
shasum -a 256
elif command_exists openssl; then
openssl dgst -sha256
else
echoerr "Unable to calculate sha256!"
exit 43
fi
}
header() {
echoerr "$*"
echoerr
}
latest_version_released() {
curl -s 'https://api.github.com/repos/denisidoro/navi/releases/latest' \
| grep -Eo 'releases/tag/v([0-9\.]+)' \
| sed 's|releases/tag/v||'
}
asset_url() {
local -r version="$1"
local -r variant="$2"
echo "https://github.com/denisidoro/navi/releases/download/v${version}/navi-${variant}.tar.gz"
}
download_asset() {
local -r url="$(asset_url "$@")"
mkdir -p "$DEFAULT_INSTALL_DIR"
cd "$DEFAULT_INSTALL_DIR"
rm -f navi.tar.gz
echoerr "Downloading $url"
curl -L "$url" -o navi.tar.gz
tar xvzf navi.tar.gz
rm -f navi.tar.gz
ln -s "${DEFAULT_INSTALL_DIR}/navi" "/usr/bin/navi" \
|| ln -s "${DEFAULT_INSTALL_DIR}/navi" "/usr/local/bin/navi"
}
sha_for_asset_on_github() {
local -r url="$(asset_url "$@")"
curl -sL "$url" | sha256 | awk '{print $1}'
}
get_target() {
local -r unamea="$(uname -a)"
local -r archi="$(uname -sm)"
local is_android
[[ $unamea = *Android* ]] && is_android=true || is_android=false
local target
case "$archi" in
Darwin*) target="x86_64-osx" ;;
Linux\ *64) target="x86_64-unknown-linux-musl" ;;
*arm*) target="armv7-unknown-linux-musleabihf" ;;
*) target="" ;;
esac
echo "$target"
}
no_binary_warning() {
echoerr "There's no precompiled binary for your platform: $(uname -a)"
}
get_shell() {
echo $SHELL | xargs basename
}
install_navi() {
export DEFAULT_INSTALL_DIR="$(get_install_dir)"
local -r target="$(get_target)"
if [[ -n "$target" ]]; then
local -r version="$(latest_version_released)"
download_asset "$version" "$target"
elif command_exists cargo; then
no_binary_warning
echoerr "Building sources..."
git clone https://github.com/denisidoro/navi "$DEFAULT_INSTALL_DIR"
cd "$DEFAULT_INSTALL_DIR"
make install
else
no_binary_warning
echoerr "You don't have the necessary tools to build it"
echoerr "Please open an issue at https://github.com/denisidoro/navi"
echoerr "Aborting..."
exit 33
fi
local -r shell="$(get_shell)"
echoerr -e "Finished. To call navi, restart your shell or reload the config file:\n source ~/.${shell}rc"
echoerr -e "\nTo add the Ctrl-G keybinding, add the following to ~/.${shell}rc:\n source \"$(navi widget ${shell})\""
return 0
}
(return 0 2>/dev/null) || install_navi "$@"

View file

@ -1,17 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
# please refer to https://github.com/denisidoro/dotfiles/blob/master/scripts/code/beautify
export NAVI_HOME="$(cd "$(dirname "$0")/.." && pwd)"
files_to_lint() {
find . -iname '*.sh'
find scripts/*
echo "${NAVI_HOME}/test/run"
echo "${NAVI_HOME}/navi"
}
for f in $(files_to_lint); do
dot code beautify "$f"
done

27
scripts/make Executable file
View file

@ -0,0 +1,27 @@
#!/usr/bin/env bash
set -euo pipefail
##? make install
##? make uninstall
export NAVI_HOME="$(cd "$(dirname "$0")/.." && pwd)"
source "${NAVI_HOME}/scripts/install"
install() {
"${NAVI_HOME}/scripts/action" release
ln -s "${NAVI_HOME}/target/tar/navi" /usr/bin/navi \
|| ln -s "${NAVI_HOME}/target/tar/navi" /usr/local/bin/navi
}
uninstall() {
rm -rf "${NAVI_HOME}/target"
rm /usr/local/bin/navi || rm /usr/bin/navi
}
cmd="$1"
shift
case "$cmd" in
"install") install "$@" ;;
"uninstall") uninstall "$@" ;;
esac

View file

@ -1,7 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
export NAVI_HOME="$(cd "$(dirname "$0")/.." && pwd)"
export NAVI_PATH="${NAVI_HOME}/test:${NAVI_HOME}/cheats"
"${NAVI_HOME}/navi" "$@"

View file

@ -1,52 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
export NAVI_HOME="$(cd "$(dirname "$0")/.." && pwd)"
source "${NAVI_HOME}/src/main.sh"
sha256() {
if command_exists sha256sum; then
sha256sum
elif command_exists shasum; then
shasum -a 256
elif command_exists openssl; then
openssl dgst -sha256
else
echoerr "Unable to calculate sha256!"
exit 43
fi
}
header() {
echo "$*"
echo
}
cd "$NAVI_HOME"
header "git pull"
git pull
version="$(grep VERSION "${NAVI_HOME}/navi" | grep -Eo '[0-9\.]+')"
tag="v${version}"
tar="https://github.com/denisidoro/navi/archive/${tag}.tar.gz"
formula="/usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/Formula/navi.rb"
header "Attempting to release version ${tag}..."
read -p "Press enter to continue"
header "git tag"
git tag -a "$tag" -m "$tag"
header "git push"
git push --follow-tags
header "rm formula"
rm "$formula" 2>/dev/null || true
header "tar sha256"
sha="$(curl -sL "$tar" | sha256 | awk '{print $1}')"
header "output"
echo " url \"${tar}\""
echo " sha256 \"${sha}\""

9
scripts/run Executable file
View file

@ -0,0 +1,9 @@
#!/usr/bin/env bash
set -euo pipefail
export NAVI_HOME="$(cd "$(dirname "$0")/.." && pwd)"
cd "$NAVI_HOME"
rm -rf "target/release/navi" 2> /dev/null || true
rm -rf "target/debug/navi" 2> /dev/null || true
cargo run -- "$@"

9
scripts/tag Executable file
View file

@ -0,0 +1,9 @@
#!/usr/bin/env bash
set -euo pipefail
export NAVI_HOME="$(cd "$(dirname "$0")/.." && pwd)"
version="$1"
git tag -a "v${version}" -m 'WIP version written in Rust. Use 1.0.0 instead'
git push origin --tags

View file

@ -1,5 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
BIN="/usr/local/bin/navi"
rm -rf "$BIN" || true

View file

@ -1,9 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
export NAVI_HOME="$(cd "$(dirname "$0")/.." && pwd)"
cd "$NAVI_HOME"
git pull
"${NAVI_HOME}/scripts/install" || true

11
shell/navi.plugin.bash Normal file
View file

@ -0,0 +1,11 @@
#!/usr/bin/env bash
__call_navi() {
local -r f="$(mktemp || echo "${HOME}/.naviresult")"
navi --save "$f" </dev/tty >/dev/tty
local -r r="$(cat "$f")"
rm "$f" 2> /dev/null || true
echo "$r"
}
bind '"\C-g": " \C-b\C-k \C-u`__call_navi`\e\C-e\C-a\C-y\C-h\C-e\e \C-y\ey\C-x\C-x\C-f"'

23
shell/navi.plugin.fish Normal file
View file

@ -0,0 +1,23 @@
#!/usr/bin/env fish
function __call_navi
set -l f (mktemp || echo "${HOME}/.naviresult")
navi --save "$f" </dev/tty >/dev/tty
set -l r (cat "$f")
rm "$f" 2> /dev/null || true
echo "$r"
end
function navi-widget -d "Show cheat sheets"
begin
stty sane
__call_navi | perl -pe 'chomp if eof' | read -lz result
and commandline -- $result
end
commandline -f repaint
end
bind \cg navi-widget
if bind -M insert > /dev/null 2>&1
bind -M insert \cg navi-widget
end

15
shell/navi.plugin.zsh Normal file
View file

@ -0,0 +1,15 @@
#!/usr/bin/env zsh
_call_navi() {
local -r buff="$BUFFER"
local -r f="$(mktemp || echo "${HOME}/.naviresult")" 2>/dev/null
navi --save "$f" </dev/tty >/dev/tty
local -r r="$(cat "$f")" 2>/dev/null
rm "$f" 2> /dev/null || true
zle kill-whole-line
zle -U "${buff}${r}"
}
zle -N _call_navi
bindkey '^g' _call_navi

View file

@ -1,117 +0,0 @@
#!/usr/bin/env bash
ARG_REGEX="<[a-zA-Z_]+([- ]?\w+)*>"
arg::dict() {
local -r input="$(cat)"
local -r fn="$(echo "$input" | awk -F'---' '{print $1}')"
local -r opts="$(echo "$input" | awk -F'---' '{print $2}')"
dict::new fn "$fn" opts "$opts"
}
arg::escape() {
echo "$*" \
| tr '-' '_' \
| tr ' ' '_'
}
arg::interpolate() {
local -r arg="$1"
local -r value="$(echo "$2" | tr "$ESCAPE_CHAR_3" '\n')"
local -r lines="$(echo "$value" | wc -l)"
if [ $lines -lt 2 ]; then
local -r words="$(echo "$value" | wc -w | xargs)"
if [[ $words > 1 ]]; then
sed "s|<${arg}>|\"${value}\"|g"
else
sed "s|<${arg}>|${value}|g"
fi
else
local -r newvalue="$(echo "$value" \
| xargs -I% echo '"%"' \
| tr '\n' ' ')"
sed "s|<${arg}>|${newvalue}|g"
fi
}
arg::next() {
grep -Eo "$ARG_REGEX" \
| head -n1 \
| tr -d '<' \
| tr -d '>'
}
arg::deserialize() {
local arg="${1:-}"
arg="${arg:1:${#arg}-2}"
echo "$arg" \
| tr "${ESCAPE_CHAR}" " " \
| tr "${ESCAPE_CHAR_2}" "'" \
| tr "${ESCAPE_CHAR_3}" '"'
}
arg::serialize_code() {
printf "tr ' ' '${ESCAPE_CHAR}'"
printf " | "
printf "tr \"'\" '${ESCAPE_CHAR_2}'"
printf " | "
printf "tr '\"' '${ESCAPE_CHAR_3}'"
}
arg::pick() {
local -r arg="$1"
local -r cheat="$2"
local -r prefix="$ ${arg}:"
local -r length="$(echo "$prefix" | str::length)"
local -r arg_dict="$(echo "$cheat" | grep "$prefix" | str::sub $((length + 1)) | arg::dict)"
local -r fn="$(dict::get "$arg_dict" fn)"
local -r args_str="$(dict::get "$arg_dict" opts)"
local arg_name=""
for arg_str in $args_str; do
if [ -z $arg_name ]; then
arg_name="$(echo "$arg_str" | str::sub 2)"
else
eval "local $arg_name"='$arg_str'
arg_name=""
fi
done
if [ -n "$fn" ]; then
local suggestions="$(eval "$fn" 2>/dev/null)"
local args
args+=("--prompt"); args+=("${arg}: ")
args+=("--header-lines"); args+=("${headers:-0}")
if ${multi:-false}; then
args+=("-m")
fi
if [ -n "$suggestions" ]; then
echo "$suggestions" \
| ui::fzf ${args[@]:-} \
| str::column "${column:-}" "${separator:-}" \
| tr '\n' "$ESCAPE_CHAR_3"
fi
elif ${NAVI_USE_FZF_ALL_INPUTS:-false}; then
echo "" | ui::fzf --prompt "$arg: " --print-query --no-select-1 --height 1
else
printf "\033[0;36m${arg}:\033[0;0m " > /dev/tty
read -r value
ui::clear_previous_line > /dev/tty
printf "$value"
fi
}

147
src/cheat.rs Normal file
View file

@ -0,0 +1,147 @@
use crate::display;
use crate::filesystem;
use crate::option::Config;
use regex::Regex;
use std::collections::HashMap;
use std::fs;
use std::io::Write;
pub struct SuggestionOpts {
pub header_lines: u8,
pub column: Option<u8>,
pub multi: bool,
}
pub type Value = (String, Option<SuggestionOpts>);
fn gen_snippet(snippet: &str, line: &str) -> String {
if snippet.is_empty() {
line.to_string()
} else {
format!("{}{}", &snippet[..snippet.len() - 2], line)
}
}
fn parse_opts(text: &str) -> SuggestionOpts {
let mut header_lines: u8 = 0;
let mut column: Option<u8> = None;
let mut multi = false;
let mut parts = text.split(' ');
while let Some(p) = parts.next() {
match p {
"--multi" => multi = true,
"--header" | "--header-lines" => {
header_lines = parts.next().unwrap().parse::<u8>().unwrap()
}
"--column" => column = Some(parts.next().unwrap().parse::<u8>().unwrap()),
_ => (),
}
}
SuggestionOpts {
header_lines,
column,
multi,
}
}
fn parse_variable_line(line: &str) -> (&str, &str, Option<SuggestionOpts>) {
let re = Regex::new(r"^\$\s*([^:]+):(.*)").unwrap();
let caps = re.captures(line).unwrap();
let variable = caps.get(1).unwrap().as_str().trim();
let mut command_plus_opts = caps.get(2).unwrap().as_str().split("---");
let command = command_plus_opts.next().unwrap();
let opts = match command_plus_opts.next() {
Some(o) => Some(parse_opts(o)),
None => None,
};
(variable, command, opts)
}
fn read_file(
path: &str,
variables: &mut HashMap<String, Value>,
stdin: &mut std::process::ChildStdin,
) {
let mut tags = String::from("");
let mut comment = String::from("");
let mut snippet = String::from("");
let (tag_width, comment_width) = display::widths();
if let Ok(lines) = filesystem::read_lines(path) {
for l in lines {
let line = l.unwrap();
// tag
if line.starts_with('%') {
tags = String::from(&line[2..]);
}
// comment
else if line.starts_with('#') {
comment = String::from(&line[2..]);
}
// variable
else if line.starts_with('$') {
let (variable, command, opts) = parse_variable_line(&line[..]);
variables.insert(
format!("{};{}", tags, variable),
(String::from(command), opts),
);
}
// snippet with line break
else if line.ends_with('\\') {
snippet = if !snippet.is_empty() {
format!("{}{}", &snippet[..snippet.len() - 2], line)
} else {
line
}
}
// blank
else if line.is_empty() {
}
// snippet
else {
let full_snippet = gen_snippet(&snippet, &line);
match stdin.write(
display::format_line(
&tags[..],
&comment[..],
&full_snippet[..],
tag_width,
comment_width,
)
.as_bytes(),
) {
Ok(_) => snippet = String::from(""),
Err(_) => break,
}
}
}
}
}
pub fn read_all(config: &Config, stdin: &mut std::process::ChildStdin) -> HashMap<String, Value> {
let mut variables: HashMap<String, Value> = HashMap::new();
let fallback = format!("{}/cheats", filesystem::exe_path_string());
let folders_str = config.path.as_ref().unwrap_or(&fallback);
let folders = folders_str.split(':');
for folder in folders {
if let Ok(paths) = fs::read_dir(folder) {
for path in paths {
read_file(
path.unwrap().path().into_os_string().to_str().unwrap(),
&mut variables,
stdin,
);
}
}
}
variables
}

View file

@ -1,114 +0,0 @@
#!/usr/bin/env bash
cheat::find() {
for path in $(echo "$NAVI_PATH" | tr ':' '\n'); do
find -L "${path/#\~/$HOME}" -iname '*.cheat'
done
}
cheat::export_cache() {
if [ -z "${NAVI_CACHE:-}" ]; then
export NAVI_CACHE="$*"
fi
}
cheat::join_lines() {
if command_exists perl; then
perl -0pe 's/\\\n *//g'
else
tr '\n' "$ESCAPE_CHAR" \
| sed -E 's/\\'$(printf "$ESCAPE_CHAR")' *//g' \
| tr "$ESCAPE_CHAR" '\n'
fi
}
cheat::read_all() {
for cheat in $(cheat::find); do
echo
cat "$cheat"
echo
done
}
cheat::memoized_read_all() {
if [ -n "${NAVI_CACHE:-}" ]; then
echo "$NAVI_CACHE"
return
fi
local -r cheats="$(cheat::read_all)"
echo "$cheats" \
| cheat::join_lines
}
cheat::prettify() {
local -r print="$(dict::get "$OPTIONS" print)"
local -r comment_width="$(style::comment_width)"
local -r snippet_width="$(style::snippet_width)"
local -r tag_width="$(style::tag_width)"
local -r comment_color="$(style::comment_color)"
local -r snippet_color="$(style::snippet_color)"
local -r tag_color="$(style::tag_color)"
local -r columns="$(ui::width || echo 0)"
awk \
-v COMMENT_COLOR=$comment_color \
-v SNIPPET_COLOR=$snippet_color \
-v TAG_COLOR=$tag_color \
-v COMMENT_MAX=$((columns * comment_width / 100)) \
-v SNIPPET_MAX=$((columns * snippet_width / 100)) \
-v TAG_MAX=$((columns * tag_width / 100)) \
-v SEP="$ESCAPE_CHAR_3" \
'function color(c,s,max) {
if (max > 0 && length(s) > max) {
s=substr(s, 0, max)
s=s"…"
}
printf("\033[%dm%s", c, s)
}
/^%/ { tags=substr($0, 3); next }
/^#/ { comment=substr($0, 3); next }
/^\$/ { next }
BEGIN { ORS="" }
NF {
print color(COMMENT_COLOR, comment, COMMENT_MAX)
print color(0, SEP, 0)
print color(SNIPPET_COLOR, $0, SNIPPET_MAX)
print color(0, SEP, 0)
print color(TAG_COLOR, tags, TAG_MAX);
print color(0, SEP, 0)
print color(DEFAULT, "\033", 0);
print "\n"
next
}'
}
cheat::until_percentage() {
awk 'BEGIN { count=0; }
/^%/ { if (count >= 1) exit;
else { count++; print $0; next; } }
{ print $0 }'
}
cheat::from_tags() {
local -r cheats="$1"
local -r tags="$2"
echo "$cheats" \
| grep "% ${tags}" -A99999 \
| cheat::until_percentage
}
cheat::from_selection() {
local -r cheats="$1"
local -r selection="$2"
local -r tags="$(dict::get "$selection" tags)"
cheat::from_tags "$cheats" "$tags"
}

View file

@ -1,18 +0,0 @@
#!/usr/bin/env bash
clip::set() {
local -r input="${1:-}"
if command_exists pbcopy; then
echo "$input" | pbcopy
elif command_exists clip.exe; then
echo "$input" | clip.exe
elif command_exists xclip; then
echo "$input" | xclip -sel clip
elif command_exists xsel; then
echo "$input" | xsel -i -b
else
echo "$input"
die "Unable to set clipboard"
fi
}

View file

@ -1,53 +0,0 @@
#!/usr/bin/env bash
cmd::escape() {
tr '\\' "$ESCAPE_CHAR_3"
}
cmd::unescape() {
tr "$ESCAPE_CHAR_3" '\\'
}
cmd::loop() {
local -r cmd="$1"
local -r cheat="$2"
local arg escaped_arg value escaped_cmd
arg="$(echo "$cmd" | arg::next)"
if [ -z "$arg" ]; then
dict::new cmd "$cmd"
return
fi
escaped_arg="$(arg::escape "$arg")"
escaped_cmd="$(echo "$cmd" | sed "s|<${arg}>|<${escaped_arg}>|g")"
arg="$escaped_arg"
local -r values="$(dict::get "$OPTIONS" values)"
value="$(echo "$values" | coll::get $i)"
[ -z "$value" ] && value="$(arg::pick "$arg" "$cheat")"
dict::new \
cmd "${escaped_cmd:-}" \
value "$value" \
arg "$arg"
}
cmd::finish() {
local -r cmd="$(echo "$1" | cmd::unescape)"
local -r selection="${2:-}"
local -r key="$(selection::key "$selection")"
local -r unresolved_arg="$(echo "$cmd" | arg::next)"
local -r print="$(dict::get "$OPTIONS" print)"
if [[ "$key" = "ctrl-y" ]]; then
clip::set "$cmd"
elif $print || [ -n "$unresolved_arg" ]; then
echo "$cmd" | ui::remove_dep_order
else
eval "$cmd"
fi
}

16
src/cmds/aux.rs Normal file
View file

@ -0,0 +1,16 @@
use std::error::Error;
use std::process;
pub fn abort(operation: &str, issue_number: u32) -> Result<(), Box<dyn Error>> {
eprintln!("This version of navi doesn't support {}.", operation);
eprintln!(
"Please check https://github.com/denisidoro/navi/issues/{} for more info.",
issue_number
);
eprintln!("");
eprintln!("You were probably using the bash implementation of navi and are now using the Rust one, which isn't feature complete yet.");
eprintln!("In the near future, the Rust version will have all previous features.");
eprintln!("");
eprintln!("I'm sorry for the inconvenience.");
process::exit(42)
}

13
src/cmds/best.rs Normal file
View file

@ -0,0 +1,13 @@
use std::error::Error;
use crate::cmds;
use crate::cmds::core::Variant;
use crate::option::Config;
pub fn main(query: String, args: Vec<String>, config: Config) -> Result<(), Box<dyn Error>> {
if !args.is_empty() {
cmds::aux::abort("passing arguments to 'navi best'", 201)
} else {
cmds::core::main(Variant::Filter(query), config)
}
}

163
src/cmds/core.rs Normal file
View file

@ -0,0 +1,163 @@
use crate::cheat;
use crate::cmds;
use crate::display;
use crate::fzf;
use crate::option::Config;
use regex::Regex;
use std::collections::HashMap;
use std::error::Error;
use std::fs;
use std::io::Write;
use std::process;
use std::process::{Command, Stdio};
pub enum Variant {
Core,
Filter(String),
Query(String),
}
fn gen_core_fzf_opts(variant: Variant, config: &Config) -> fzf::Opts {
let mut opts = fzf::Opts {
preview: !config.no_preview,
autoselect: !config.no_autoselect,
overrides: config.fzf_overrides.as_ref(),
copyable: true,
..Default::default()
};
match variant {
Variant::Core => (),
Variant::Filter(f) => opts.filter = Some(f),
Variant::Query(q) => opts.query = Some(q),
}
opts
}
fn extract_from_selections(raw_output: &str) -> (&str, &str, &str) {
let mut lines = raw_output.split('\n');
let key = lines.next().unwrap();
let mut parts = lines.next().unwrap().split('\t');
parts.next();
parts.next();
parts.next();
let tags = parts.next().unwrap();
parts.next();
let snippet = parts.next().unwrap();
(key, tags, snippet)
}
fn prompt_with_suggestions(config: &Config, suggestion: &cheat::Value) -> String {
let child = Command::new("bash")
.stdout(Stdio::piped())
.arg("-c")
.arg(&suggestion.0)
.spawn()
.unwrap();
let suggestions = String::from_utf8(child.wait_with_output().unwrap().stdout).unwrap();
let mut opts = fzf::Opts {
preview: false,
autoselect: !config.no_autoselect,
..Default::default()
};
if let Some(o) = &suggestion.1 {
opts.multi = o.multi;
opts.header_lines = o.header_lines;
opts.nth = o.column;
};
let (output, _) = fzf::call(opts, |stdin| {
stdin.write_all(suggestions.as_bytes()).unwrap();
None
});
String::from_utf8(output.stdout).unwrap()
}
fn prompt_without_suggestions(varname: &str) -> String {
let opts = fzf::Opts {
preview: false,
autoselect: false,
suggestions: false,
prompt: Some(display::variable_prompt(varname)),
..Default::default()
};
let (output, _) = fzf::call(opts, |_stdin| None);
String::from_utf8(output.stdout).unwrap()
}
fn gen_replacement(value: &str) -> String {
if value.contains(' ') {
format!("\"{}\"", &value[..value.len() - 1])
} else {
value[..value.len() - 1].to_string()
}
}
fn replace_variables_from_snippet(
snippet: &str,
tags: &str,
variables: HashMap<String, cheat::Value>,
config: &Config,
) -> String {
let mut interpolated_snippet = String::from(snippet);
let re = Regex::new(r"<(\w[\w\d\-_]*)>").unwrap();
for cap in re.captures_iter(snippet) {
let bracketed_varname = &cap[0];
let varname = &bracketed_varname[1..bracketed_varname.len() - 1];
let k = format!("{};{}", tags, varname);
let value = match variables.get(&k[..]) {
Some(suggestion) => prompt_with_suggestions(&config, suggestion),
None => prompt_without_suggestions(varname),
};
interpolated_snippet =
interpolated_snippet.replace(bracketed_varname, gen_replacement(&value[..]).as_str());
}
interpolated_snippet
}
pub fn main(variant: Variant, config: Config) -> Result<(), Box<dyn Error>> {
let (output, variables) = fzf::call(gen_core_fzf_opts(variant, &config), |stdin| {
Some(cheat::read_all(&config, stdin))
});
match output.status.code() {
Some(0) => {
let raw_output = String::from_utf8(output.stdout)?;
let (key, tags, snippet) = extract_from_selections(&raw_output[..]);
let interpolated_snippet =
replace_variables_from_snippet(snippet, tags, variables.unwrap(), &config);
if key == "ctrl-y" {
cmds::aux::abort("copying snippets to the clipboard", 201)?
} else if config.print {
println!("{}", interpolated_snippet);
} else if let Some(s) = config.save {
fs::write(s, interpolated_snippet)?;
} else {
Command::new("bash")
.arg("-c")
.arg(&interpolated_snippet[..])
.spawn()?;
}
Ok(())
}
Some(130) => process::exit(130),
_ => {
let err = String::from_utf8(output.stderr)?;
panic!("External command failed:\n {}", err)
}
}
}

7
src/cmds/func.rs Normal file
View file

@ -0,0 +1,7 @@
use std::error::Error;
use super::aux;
pub fn main(_func: String, _args: Vec<String>) -> Result<(), Box<dyn Error>> {
aux::abort("calling `navi fn`", 201)
}

8
src/cmds/home.rs Normal file
View file

@ -0,0 +1,8 @@
use std::error::Error;
use crate::filesystem;
pub fn main() -> Result<(), Box<dyn Error>> {
println!("{}", filesystem::exe_path_string());
Ok(())
}

9
src/cmds/mod.rs Normal file
View file

@ -0,0 +1,9 @@
pub mod aux;
pub mod best;
pub mod core;
pub mod func;
pub mod home;
pub mod preview;
pub mod query;
pub mod search;
pub mod shell;

18
src/cmds/preview.rs Normal file
View file

@ -0,0 +1,18 @@
use std::error::Error;
use std::process;
use crate::display;
fn extract_elements(argstr: &str) -> (&str, &str, &str) {
let mut parts = argstr.split('\t').skip(3);
let tags = parts.next().unwrap();
let comment = parts.next().unwrap();
let snippet = parts.next().unwrap();
(tags, comment, snippet)
}
pub fn main(line: String) -> Result<(), Box<dyn Error>> {
let (tags, comment, snippet) = extract_elements(&line[..]);
display::preview(comment, tags, snippet);
process::exit(0)
}

9
src/cmds/query.rs Normal file
View file

@ -0,0 +1,9 @@
use std::error::Error;
use crate::cmds;
use crate::cmds::core::Variant;
use crate::option::Config;
pub fn main(query: String, config: Config) -> Result<(), Box<dyn Error>> {
cmds::core::main(Variant::Query(query), config)
}

8
src/cmds/search.rs Normal file
View file

@ -0,0 +1,8 @@
use std::error::Error;
use super::aux;
use crate::option::Config;
pub fn main(_query: String, _config: Config) -> Result<(), Box<dyn Error>> {
aux::abort("searching for cheats online", 201)
}

15
src/cmds/shell.rs Normal file
View file

@ -0,0 +1,15 @@
use std::error::Error;
use crate::filesystem;
pub fn main(shell: &str) -> Result<(), Box<dyn Error>> {
let file = match shell {
"zsh" => "navi.plugin.zsh",
"fish" => "navi.plugin.fish",
_ => "navi.plugin.bash",
};
println!("{}/shell/{}", filesystem::exe_path_string(), file);
Ok(())
}

View file

@ -1,84 +0,0 @@
#!/usr/bin/env bash
coll::new() {
for x in "$@"; do
echo "$x"
done
}
coll::first() {
head -n1
}
coll::rest() {
tail -n +2
}
coll::map() {
local -r fn="$1"
for x in $(cat); do
"$fn" "$x"
done
}
coll::filter() {
local -r pred="$1"
for x in $(cat); do
"$pred" "$x" && echo "$x" || true
done
}
coll::remove() {
local -r pred="$1"
for x in $(cat); do
"$pred" "$x" || echo "$x"
done
}
coll::without_empty_line() {
local -r input="$(cat)"
local -r words="$(echo "$input" | wc -w | xargs)"
if [[ $words > 0 ]]; then
echo "$input"
fi
}
coll::add() {
cat | coll::without_empty_line
for x in "$@"; do
echo "$x"
done
}
coll::reverse() {
str::reverse_lines "$@"
}
coll::set() {
sort -u
}
coll::get() {
local n="$1"
n=$((n+1))
sed "${n}q;d"
}
# TODO: implement tailrec
coll::reduce() {
local -r fn="$1"
local state="$2"
local -r coll="$(cat)"
local -r x="$(echo "$coll" | coll::first)"
if [ -z "$x" ]; then
echo "$state"
else
local -r new_state="$("$fn" "$state" "$x")"
echo "$coll" | coll::rest | coll::reduce "$fn" "$new_state"
fi
}

View file

@ -1,113 +0,0 @@
#!/usr/bin/env bash
# for an explanation behind this namespace, please check
# https://medium.com/@den.isidoro/dictionaries-in-shell-scripts-61d34e1c91c6
# LIMITATIONS:
# values with non-trivial whitespaces (newlines, subsequent spaces, etc)
# aren't handled very well
dict::new() {
if [ $# = 0 ]; then
echo ""
else
echo "" | dict::assoc "$@" | str::remove_empty_lines
fi
}
dict::dissoc() {
local -r key="$1"
grep -Ev "^[\s]*${key}[^:]*:"
}
dict::escape_value() {
tr '\n' "$ESCAPE_CHAR" | sed 's/\\n/'$(printf "$ESCAPE_CHAR")'/g'
}
str::without_trailing_newline() {
printf "%s" "$(cat)"
echo
}
dict::unescape_value() {
tr "$ESCAPE_CHAR" '\n' | str::without_trailing_newline
}
dict::assoc() {
local -r key="${1:-}"
local -r input="$(cat)"
if [ -z $key ]; then
printf "$(echo "$input" | tr '%' "$ESCAPE_CHAR_2")" | tr "$ESCAPE_CHAR_2" '%'
return
fi
local -r value="$(echo "${2:-}" | dict::escape_value)"
shift 2
echo "$(echo "$input" | dict::dissoc "$key")${key}: ${value}\n" | dict::assoc "$@"
}
dict::get() {
if [ $# = 1 ]; then
local -r input="$(cat)"
local -r key="$1"
else
local -r input="$1"
local -r key="$2"
fi
local -r prefix="${key}[^:]*: "
local -r result="$(echo "$input" | grep -E "^${prefix}")"
local -r matches="$(echo "$result" | wc -l || echo 0)"
if [ $matches -gt 1 ]; then
echo "$result" | dict::unescape_value
else
echo "$result" | sed -E "s/${prefix}//" | dict::unescape_value
fi
}
dict::keys() {
grep -Eo '^[^:]+: ' \
| sed 's/: //g'
}
dict::values() {
awk -F':' '{$1=""; print $0}' \
| cut -c3-
}
dict::zipmap() {
IFS='\n'
local -r keys_str="$1"
local -r values_str="$2"
keys=()
values=()
for key in $keys_str; do
keys+=("$key")
done
for value in $values_str; do
values+=("$value")
done
for ((i=0; i<${#keys[@]}; ++i)); do
if [ -n "${keys[i]}" ]; then
echo "${keys[i]}: ${values[i]}"
fi
done
}
dict::update() {
local -r key="$1"
local -r fn="$2"
local -r input="$(cat)"
local -r value="$(echo "$input" | dict::get "$key")"
local -r updated_value="$("$fn" "$value")"
echo "$input" | dict::assoc "$key" "$updated_value"
}

56
src/display.rs Normal file
View file

@ -0,0 +1,56 @@
use termion::{color, terminal_size};
static COMMENT_COLOR: color::LightCyan = color::LightCyan;
static TAG_COLOR: color::LightGreen = color::LightGreen;
static SNIPPET_COLOR: color::White = color::White;
pub fn widths() -> (usize, usize) {
let full_width = terminal_size().unwrap().0;
let tag_width = full_width * 10 / 100;
let comment_width = full_width * 50 / 100;
(usize::from(tag_width), usize::from(comment_width))
}
pub fn variable_prompt(varname: &str) -> String {
format!("{}: ", varname)
}
pub fn preview(comment: &str, tags: &str, snippet: &str) {
println!(
"{comment_color}{comment} {tag_color}{tags} \n{snippet_color}{snippet}",
comment = format!("# {}", comment),
tags = format!("[{}]", tags),
snippet = snippet,
comment_color = color::Fg(COMMENT_COLOR),
tag_color = color::Fg(TAG_COLOR),
snippet_color = color::Fg(SNIPPET_COLOR),
);
}
fn limit_str(text: &str, length: usize) -> String {
if text.len() > length {
format!("{}", &text[..length - 1])
} else {
format!("{:width$}", text, width = length)
}
}
pub fn format_line(
tags: &str,
comment: &str,
full_snippet: &str,
tag_width: usize,
comment_width: usize,
) -> String {
format!(
"{tag_color}{tags_short}\t{comment_color}{comment_short}\t{snippet_color}{snippet_short}\t{tags}\t{comment}\t{snippet}\t\n",
tags_short = limit_str(tags, tag_width),
comment_short = limit_str(comment, comment_width),
snippet_short = full_snippet,
comment_color = color::Fg(COMMENT_COLOR),
tag_color = color::Fg(TAG_COLOR),
snippet_color = color::Fg(SNIPPET_COLOR),
tags = tags,
comment = comment,
snippet = &full_snippet)
}

View file

@ -1,63 +0,0 @@
An interactive cheatsheet tool for the command-line
Usage:
navi [command] [<args>...] [options]
Commands:
query <cmd> Pre-filter results
search <cmd> Search for cheatsheets on online repositories
best <cmd> <args>... Considers the best match
widget <shell> Prints the path for the widget file to be sourced
Options:
--print Prevent script execution
--path List of :-separated paths to look for cheats
--no-interpolation Prevent argument interpolation
--no-preview Hide command preview window
--no-autoselect Prevents autoselecting single entries in a list
--fzf-overrides Overrides for fzf commands [default: --with-nth 3,1,2 --exact]
--col-widths Set column widths [default: 15,50,0]
--col-colors Set color ANSI codes [default: 90,34,37]
Keybindings:
Ctrl-y Copy snippet to clipboard
Ctrl-j Move down one entry
Ctrl-k Move up one entry
Environment variables:
NAVI_PATH List of :-separated paths to look for cheats
FZF_DEFAULT_OPTS Default fzf options (e.g. '--layout=reverse')
Examples:
navi # default behavior
navi --print # doesn't execute the snippet
navi --path '/some/dir:/other/dir' # uses custom cheats
navi search docker # uses online data
navi query git # filters results by "git"
navi best 'sql create db' root mydb # uses a snippet as a CLI
source "$(navi widget zsh)" # loads the zsh widget
navi --col-widths 10,50,0 # limits the first two columns's width
navi --col-colors 32,32,32 # make all columns green
navi --fzf-overrides '--with-nth 1,2' # shows only the comment and tag columns
navi --fzf-overrides '--nth 1,2' # search will consider only the first two columns
navi --fzf-overrides '--no-exact' # looser search algorithm
More info:
search
http://cheat.sh is used for cheatsheet retrieval
please note that these cheatsheets may not work out of the box
always check the preview window before executing commands!
--col-widths
the number is the percentage relative to the terminal width
0 means that a column will be as wide as necessary
fzf properties
please refer to https://github.com/junegunn/fzf for further details
fzf's --with-nth
1 refers to the comment column; 2, snippet; 3, tag
full docs
please refer to the README at https://github.com/denisidoro/navi

33
src/filesystem.rs Normal file
View file

@ -0,0 +1,33 @@
use std::fs::File;
use std::io::{self, BufRead, BufReader, Lines};
use std::path::Path;
pub fn read_lines<P>(filename: P) -> io::Result<Lines<BufReader<File>>>
where
P: AsRef<Path>,
{
let file = File::open(filename)?;
Ok(io::BufReader::new(file).lines())
}
pub fn exe_string() -> String {
String::from(
std::env::current_exe()
.unwrap()
.as_os_str()
.to_str()
.unwrap(),
)
}
pub fn exe_path_string() -> String {
String::from(
std::env::current_exe()
.unwrap()
.parent()
.unwrap()
.as_os_str()
.to_str()
.unwrap(),
)
}

135
src/fzf.rs Normal file
View file

@ -0,0 +1,135 @@
use crate::cheat;
use crate::filesystem;
use std::collections::HashMap;
use std::process;
use std::process::{Command, Stdio};
pub struct Opts<'a> {
pub query: Option<String>,
pub filter: Option<String>,
pub prompt: Option<String>,
pub preview: bool,
pub autoselect: bool,
pub overrides: Option<&'a String>, // TODO: remove &'a
pub header_lines: u8,
pub multi: bool,
pub copyable: bool,
pub suggestions: bool,
pub nth: Option<u8>,
}
impl Default for Opts<'_> {
fn default() -> Self {
Self {
query: None,
filter: None,
preview: true,
autoselect: true,
overrides: None,
header_lines: 0,
prompt: None,
multi: false,
copyable: false,
suggestions: true,
nth: None,
}
}
}
pub fn call<F>(opts: Opts, stdin_fn: F) -> (process::Output, Option<HashMap<String, cheat::Value>>)
where
F: Fn(&mut process::ChildStdin) -> Option<HashMap<String, cheat::Value>>,
{
let mut c = Command::new("fzf");
c.args(&[
"--preview-window",
"up:2",
"--with-nth",
"1,2,3",
"--delimiter",
"\t",
"--ansi",
"--bind",
"ctrl-j:down,ctrl-k:up",
"--exact",
]);
if opts.autoselect {
c.arg("--select-1");
}
if opts.multi {
c.arg("--multi");
}
if opts.copyable {
c.args(&["--expect", "ctrl-y,enter"]);
}
if opts.preview {
c.args(&[
"--preview",
format!("{} preview {{}}", filesystem::exe_string()).as_str(),
]);
}
if let Some(q) = opts.query {
c.args(&["--query", &q]);
}
if let Some(f) = opts.filter {
c.args(&["--filter", &f]);
}
if let Some(p) = opts.prompt {
c.args(&["--prompt", &p]);
}
if let Some(n) = opts.nth {
c.args(&["--nth", &n.to_string()]);
}
if opts.header_lines > 0 {
c.args(&["--header-lines", format!("{}", opts.header_lines).as_str()]);
}
if let Some(o) = opts.overrides {
o.as_str()
.split(' ')
.map(|s| s.to_string())
.filter(|s| !s.is_empty())
.for_each(|s| {
c.arg(s);
});
}
if !opts.suggestions {
c.args(&["--print-query", "--no-select-1", "--height", "1"]);
}
let child = c
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.spawn();
let mut child = match child {
Ok(x) => x,
Err(_) => {
eprintln!( "navi was unable to call fzf.\nPlease make sure it's correctly installed\nRefer to https://github.com/junegunn/fzf for more info.");
process::exit(33)
}
};
let stdin = child
.stdin
.as_mut()
.ok_or("Child process stdin has not been captured!")
.unwrap();
let result = stdin_fn(stdin);
(child.wait_with_output().unwrap(), result)
}

View file

@ -1,9 +0,0 @@
#!/usr/bin/env bash
health::fzf() {
if ! command_exists fzf && ! [ $NAVI_ENV -eq "test" ]; then
echoerr "You need to install fzf before using navi"
echoerr "Please refer to https://github.com/junegunn/fzf for install instructions"
exit 66
fi
}

36
src/main.rs Normal file
View file

@ -0,0 +1,36 @@
// #[macro_use]
// extern crate lazy_static;
use std::error::Error;
mod cheat;
mod cmds;
mod display;
mod filesystem;
mod fzf;
mod option;
use crate::cmds::core::Variant;
use option::{Command, InternalCommand};
fn main() -> Result<(), Box<dyn Error>> {
match option::internal_command() {
Some(InternalCommand::Preview { line }) => cmds::preview::main(line),
_ => {
let mut config = option::parse();
match config.cmd.as_mut() {
None => cmds::core::main(Variant::Core, config),
Some(c) => match c {
Command::Query { query } => cmds::query::main(query.clone(), config),
Command::Best { query, args } => {
cmds::best::main(query.clone(), args.to_vec(), config)
}
Command::Search { query } => cmds::search::main(query.clone(), config),
Command::Widget { shell } => cmds::shell::main(&shell[..]),
Command::Func { func, args } => cmds::func::main(func.clone(), args.to_vec()),
Command::Home => cmds::home::main(),
},
}
}
}
}

View file

@ -1,148 +0,0 @@
#!/usr/bin/env bash
if ${NAVI_FORCE_GNU:-false} && [ -n "${DOTFILES:-}" ]; then
source "${DOTFILES}/scripts/core/main.sh"
fi
source "${NAVI_HOME}/src/arg.sh"
source "${NAVI_HOME}/src/cheat.sh"
source "${NAVI_HOME}/src/cmd.sh"
source "${NAVI_HOME}/src/clipboard.sh"
source "${NAVI_HOME}/src/coll.sh"
source "${NAVI_HOME}/src/dict.sh"
source "${NAVI_HOME}/src/health.sh"
source "${NAVI_HOME}/src/misc.sh"
source "${NAVI_HOME}/src/opts.sh"
source "${NAVI_HOME}/src/search.sh"
source "${NAVI_HOME}/src/selection.sh"
source "${NAVI_HOME}/src/str.sh"
source "${NAVI_HOME}/src/style.sh"
source "${NAVI_HOME}/src/ui.sh"
handler::main() {
local -r cheats="$(cheat::memoized_read_all)"
cheat::export_cache "$cheats"
local -r selection="$(ui::select "$cheats")"
local -r cheat="$(cheat::from_selection "$cheats" "$selection")"
[ -z "$cheat" ] && die "No valid cheatsheet!"
local -r interpolation="$(dict::get "$OPTIONS" interpolation)"
local cmd="$(selection::snippet "$selection")"
local result arg value
local i=0
while $interpolation; do
result="$(cmd::loop "$cmd" "$cheat")"
arg="$(dict::get "$result" arg)"
value="$(dict::get "$result" value)"
cmd="$(dict::get "$result" cmd)"
[ -z "$arg" ] && break
[ -z "$value" ] && die "Unable to fetch suggestions for '$arg'!"
eval "local $arg"='$value'
cmd="$(echo "$cmd" | arg::interpolate "$arg" "$value")"
i=$((i+1))
done
cmd::finish "$cmd" "$selection"
}
handler::preview() {
local -r query="$1"
local -r cheats="$(cheat::memoized_read_all)"
local -r selection="$(echo "$query" | selection::dict "$cheats")"
local -r cheat="$(cheat::from_selection "$cheats" "$selection")"
[ -n "$cheat" ] && ui::print_preview "$selection"
}
handler::help() {
opts::extract_help "$0"
}
handler::version() {
local -r full="${1:-false}"
echo "${VERSION:-unknown}"
if $full; then
source "${NAVI_HOME}/src/version.sh"
version::code 2>/dev/null \
|| die "unknown code"
fi
}
handler::script() {
"${NAVI_HOME}/scripts/"${SCRIPT_ARGS[@]}
}
handler::fn() {
${SCRIPT_ARGS[@]}
}
handler::home() {
echo "${NAVI_HOME}"
}
handler::widget() {
local widget
local -r print="$(dict::get "$OPTIONS" print)"
case "$SH" in
zsh) widget="${NAVI_HOME}/navi.plugin.zsh" ;;
bash) widget="${NAVI_HOME}/navi.plugin.bash" ;;
fish) widget="${NAVI_HOME}/navi.plugin.fish" ;;
*) die "Invalid shell: $SH" ;;
esac
$print \
&& cat "$widget" \
|| echo "$widget"
}
handler::search() {
local -r query="$(dict::get "$OPTIONS" query)"
search::save "$query" || true
handler::main
}
main() {
case "$(dict::get "$OPTIONS" entry_point)" in
preview)
local -r query="$(dict::get "$OPTIONS" query)"
handler::preview "$query" \
|| echoerr "Unable to find command for '$query'"
;;
search)
health::fzf
handler::search
;;
version)
handler::version false
;;
full-version)
handler::version true
;;
home)
handler::home
;;
script)
handler::script
;;
fn)
handler::fn
;;
help)
handler::help
;;
widget)
handler::widget
;;
*)
health::fzf
handler::main
;;
esac
}

View file

@ -1,49 +0,0 @@
#!/usr/bin/env bash
command_exists() {
local -r cmd="${1:-}"
[ -n $cmd ] && type "$cmd" &> /dev/null
}
platform::existing_command() {
local cmd
for cmd in "$@"; do
if command_exists "$cmd"; then
echo "$cmd"
return 0
fi
done
return 1
}
echoerr() {
echo "$@" 1>&2
}
url::open() {
local -r cmd="$(platform::existing_command "${BROWSER:-}" xdg-open open google-chrome firefox)"
"$cmd" "$@" & disown
}
tap() {
local -r input="$(cat)"
echoerr "$input"
echo "$input"
}
die() {
echoerr "$@"
exit 42
}
or() {
local -r x="$(cat)"
local -r y="${1:-}"
if [ -n "$x" ]; then
echo "$x"
elif [ $# -gt 0 ]; then
shift
echo "$y" | or "$@"
fi
}

82
src/option.rs Normal file
View file

@ -0,0 +1,82 @@
use std::env;
use structopt::StructOpt;
#[derive(Debug, StructOpt)]
#[structopt(after_help = "EXAMPLES:
navi # default behavior
navi --print # doesn't execute the snippet
navi --path '/some/dir:/other/dir' # uses custom cheats
navi search docker # uses online data
navi query git # filters results by \"git\"
navi best 'sql create db' root mydb # uses a snippet as a CLI
source \"$(navi widget zsh)\" # loads the zsh widget
navi --fzf-overrides ' --with-nth 1,2' # shows only the comment and tag columns
navi --fzf-overrides ' --nth 1,2' # search will consider only the first two columns
navi --fzf-overrides ' --no-exact' # looser search algorithm")]
pub struct Config {
/// List of :-separated paths containing .cheat files
#[structopt(short, long, env = "NAVI_PATH")]
pub path: Option<String>,
/// [alpha] Instead of executing a snippet, saves it to a file
#[structopt(short, long)]
pub save: Option<String>,
/// Instead of executing a snippet, prints it to stdout
#[structopt(long)]
pub print: bool,
/// Prevents autoselection in case of single entry
#[structopt(long)]
pub no_autoselect: bool,
/// Hides preview window
#[structopt(long)]
pub no_preview: bool,
// #[structopt(long)]
// pub col_widths: Option<String>,
/// Overrides for fzf commands (must start with an empty space)
#[structopt(long)]
#[structopt(long)]
pub fzf_overrides: Option<String>,
#[structopt(subcommand)]
pub cmd: Option<Command>,
}
#[derive(Debug, StructOpt)]
pub enum Command {
/// Filters results
Query { query: String },
/// Shows navi's home directory
Home,
/// Uses online repositories for cheatsheets
Search { query: String },
/// Autoselects the snippet that best matches the query
Best { query: String, args: Vec<String> },
/// Performs ad-hoc functions provided by navi
Func { func: String, args: Vec<String> },
/// Shows the path for shell widget files
Widget { shell: String },
}
pub enum InternalCommand {
Preview { line: String },
}
pub fn parse() -> Config {
Config::from_args()
}
pub fn internal_command() -> Option<InternalCommand> {
let mut args = env::args();
args.next();
if args.next() == Some(String::from("preview")) {
Some(InternalCommand::Preview {
line: args.next().unwrap(),
})
} else {
None
}
}

View file

@ -1,82 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
opts::extract_help() {
local -r file="${NAVI_HOME}/src/docstring.txt"
cat "$file"
}
opts::eval() {
local wait_for=""
local entry_point="main"
local print=false
local interpolation=true
local preview=true
local path="${NAVI_PATH:-${NAVI_DIR:-${NAVI_HOME}/cheats}}"
local autoselect=true
local best=false
local query=""
local values=""
local col_widths="15,50,0"
local col_colors="90,34,37"
local fzf_overrides="--with-nth 3,1,2 --exact"
local fzf_opts="${FZF_DEFAULT_OPTS:---height 70% --reverse --border --inline-info --cycle}"
case "${1:-}" in
--version|version) entry_point="version"; shift ;;
--full-version|full-version) entry_point="full-version"; shift ;;
--help|help) entry_point="help"; shift ;;
search) entry_point="search"; wait_for="search"; shift ;;
preview) entry_point="preview"; wait_for="preview"; shift ;;
query|q) wait_for="query"; shift ;;
best|b) best=true; wait_for="best"; shift ;;
home) entry_point="home"; shift ;;
script) entry_point="script"; shift; SCRIPT_ARGS="$@" ;;
fn) entry_point="fn"; shift; SCRIPT_ARGS="$@" ;;
widget) entry_point="widget"; shift; wait_for="widget" ;;
esac
for arg in "$@"; do
case $wait_for in
path) path="$arg"; wait_for=""; continue ;;
preview) query="$(arg::deserialize "$arg")"; wait_for=""; continue ;;
search) query="$arg"; wait_for=""; path="${path}:$(search::full_path "$query")"; continue ;;
query|best) query="$arg"; wait_for=""; continue ;;
widget) SH="$arg"; wait_for=""; continue ;;
col-widths) col_widths="$(echo "$arg" | xargs | tr ' ' ',')"; wait_for=""; continue ;;
col-colors) col_colors="$(echo "$arg")"; wait_for=""; continue ;;
fzf-overrides) fzf_overrides="$arg" ; wait_for=""; continue ;;
esac
case $arg in
--print) print=true ;;
--no-interpolation) interpolation=false ;;
--interpolation) interpolation=true ;;
--no-preview) preview=false ;;
--preview) preview=true ;;
--path|--dir) wait_for="path" ;;
--no-autoselect) autoselect=false ;;
--autoselect) autoselect=true ;;
--col-widths) wait_for="col-widths" ;;
--col-colors) wait_for="col-colors" ;;
--fzf-overrides) wait_for="fzf-overrides" ;;
*) values="$(echo "$values" | coll::add "$arg")" ;;
esac
done
OPTIONS="$(dict::new \
entry_point "$entry_point" \
print "$print" \
interpolation "$interpolation" \
preview "$preview" \
autoselect "$autoselect" \
query "$query" \
best "$best" \
values "$values" \
fzf-overrides "$fzf_overrides" \
col-colors "$col_colors" \
col-widths "$col_widths")"
export NAVI_PATH="$path"
export FZF_DEFAULT_OPTS="$fzf_opts"
}

View file

@ -1,39 +0,0 @@
#!/usr/bin/env bash
search::cheat() {
local -r cmd="$(echo "$1" | str::first_word)"
echo "% ${cmd}, cheatsh"
echo
curl -s "${CHTSH_URL:-http://cht.sh}/${cmd}?T"
}
search::filename() {
local -r cmd="$(echo "$1" | str::first_word)"
echo "${cmd}_cheatsh" \
| head -n1 \
| awk '{print $NF}' \
| xargs \
| tr ' ' '_'
}
search::full_path() {
local -r cmd="$(echo "$1" | str::first_word)"
echo "/tmp/navi/$(search::filename "$cmd").cheat"
}
search::save() {
local -r cmd="$(echo "$1" | str::first_word)"
local -r filepath="$(search::full_path "$cmd")"
local -r filedir="$(dirname "$filepath")"
if [ -f "$filepath" ]; then
return
fi
mkdir -p "$filedir" &> /dev/null || true
search::cheat "$cmd" > "$filepath"
}

View file

@ -1,75 +0,0 @@
#!/usr/bin/env bash
SELECTION_ESCAPE_STR=" "
selection_str::cleanup() {
sed -E "s/ +/${SELECTION_ESCAPE_STR}/g"
}
selection_str::without_ellipsis() {
tr -d "…"
}
selection_str::comment() {
echo "$*" | awk -F "${SELECTION_ESCAPE_STR}" '{print $1}' | selection_str::without_ellipsis
}
selection_str::snippet() {
echo "$*" | awk -F "${SELECTION_ESCAPE_STR}" '{print $2}' | selection_str::without_ellipsis
}
selection_str::tags() {
echo "$*" | awk -F "${SELECTION_ESCAPE_STR}" '{print $3}' | selection_str::without_ellipsis
}
selection::resolve_ellipsis() {
local -r str="$(selection_str::cleanup)"
local -r cheats="$*"
if echo "$str" | grep -q "…"; then
local -r comment="$(selection_str::comment "$str")"
local -r snippet="$(selection_str::snippet "$str")"
local -r tags="$(selection_str::tags "$str")"
local -r cheat="$(cheat::from_tags "$cheats" "$tags")"
local -r tags2="$(echo "$cheat" | head -n1 | str::sub 2)"
local -r comment2="$(echo "$cheat" | grep "$comment" | str::last_line | str::sub 2)"
local -r snippet2="$(echo "$cheat" | grep "$comment2" -A 999| str::last_paragraph_line)"
echo "${comment2}${SELECTION_ESCAPE_STR}${snippet2}${SELECTION_ESCAPE_STR}${tags2}"
else
echo "$str"
fi
}
selection::dict() {
local -r cheats="$1"
local -r key="${2:-}"
local -r str="$(selection::resolve_ellipsis "$cheats")"
local -r comment="$(selection_str::comment "$str")"
local -r snippet="$(selection_str::snippet "$str")"
local -r tags="$(selection_str::tags "$str")"
dict::new comment "$comment" snippet "$snippet" tags "$tags" key "$key" | sed "s/'''/'/g"
}
selection::comment() {
local -r selection="$1"
dict::get "$selection" comment
}
selection::snippet() {
local -r selection="$1"
dict::get "$selection" snippet
}
selection::tags() {
local -r selection="$1"
dict::get "$selection" tags
}
selection::key() {
local -r selection="$1"
dict::get "$selection" key
}

View file

@ -1,85 +0,0 @@
#!/usr/bin/env bash
ESCAPE_CHAR="\034"
ESCAPE_CHAR_2="\035"
ESCAPE_CHAR_3="\036"
str::length() {
awk '{print length}'
}
str::sub() {
local -r start="${1:-0}"
local -r finish="${2:-99999}"
cut -c "$((start + 1))-$((finish - 1))"
}
str::column() {
local -r n="${1:-}"
local -r separator="$(echo "${2:-}" | or " +")"
if [ -n "$n" ]; then
awk -F "${separator:- }" "{print \$$n}"
else
cat
fi
}
str::last_paragraph_line() {
awk '(!NF) { exit } { print $0 }' \
| tail -n1
}
str::first_word() {
awk '{print $1}'
}
str::index_last_occurrence() {
local -r char="$1"
awk 'BEGIN{FS=""}{ for(i=1;i<=NF;i++){ if($i=="'"$char"'"){ p=i } }}END{ print p }'
}
str::reverse_lines() {
if command_exists tac; then
tac
elif command_exists perl; then
perl -e 'print reverse <>'
else
awk '{a[i++]=$0} END {for (j=i-1; j>=0;) print a[j--] }'
fi
}
str::not_empty() {
local -r input="$(cat)"
if [ -n $input ]; then
echo "$input"
else
return 1
fi
}
str::remove_empty_lines() {
sed '/^$/d'
}
str::last_line() {
tail -n1
}
str::as_column() {
local -r txt="$(cat)"
local -r separator="$1"
if command_exists column; then
echo "$txt" | column -t -s "$separator"
else
echo "$txt" | awk -F "$separator" -vOFS=' ' 'NF > 0 { $1 = $1 } 1'
fi
}
str::with_line_numbers() {
awk '{printf("%d %s\n", NR,$0)}'
}

View file

@ -1,52 +0,0 @@
#!/usr/bin/env bash
# TODO: move this elsewhere
style::get_index() {
local -r txt="$1"
local -r ref="$2"
local -r i="$(echo "$txt" | grep "${ref}\$" | awk '{print $1}')"
echo $((i - 1))
}
# TODO: move this elsewhere
style::with_nth() {
grep -Eo 'with\-nth +([^ ]+)' | awk '{print $NF}'
}
style::width() {
local -r col="$1"
local -r print="$(dict::get "$OPTIONS" print)"
local -r widths="$(dict::get "$OPTIONS" col-widths | tr ',' $'\n')"
local -r numbered_with_nth="$(dict::get "$OPTIONS" fzf-overrides | style::with_nth | tr ',' $'\n' | str::with_line_numbers)"
if [ -n "$numbered_with_nth" ]; then
local -r index="$(style::get_index "$numbered_with_nth" $col 2>/dev/null)"
echo "$widths" | coll::get $index 2>/dev/null || echo 0
else
echo 0
fi
}
style::color() {
local -r col="$1"
local -r print="$(dict::get "$OPTIONS" print)"
local -r colors="$(dict::get "$OPTIONS" col-colors | tr ',' $'\n')"
local -r numbered_with_nth="$(dict::get "$OPTIONS" fzf-overrides | style::with_nth | tr ',' $'\n' | str::with_line_numbers)"
if [ -n "$numbered_with_nth" ]; then
local -r index="$(style::get_index "$numbered_with_nth" $col 2>/dev/null)"
echo "$colors" | coll::get $index 2>/dev/null || echo 0
else
echo 30
fi
}
style::comment_width() { style::width 1 "$@"; }
style::snippet_width() { style::width 2 "$@"; }
style::tag_width() { style::width 3 "$@"; }
style::comment_color() { style::color 1 "$@"; }
style::snippet_color() { style::color 2 "$@"; }
style::tag_color() { style::color 3 "$@"; }

View file

@ -1,98 +0,0 @@
#!/usr/bin/env bash
ui::fzf() {
local -r autoselect="$(dict::get "$OPTIONS" autoselect)"
local -r with_nth="$(dict::get "$OPTIONS" with-nth)"
local -r nth="$(dict::get "$OPTIONS" nth)"
local args
args+=("--height")
args+=("100%")
if ${autoselect:-false}; then
args+=("--select-1")
fi
args+=("--bind"); args+=("ctrl-j:down,ctrl-k:up")
local -r fzf_cmd="$([ $NAVI_ENV == "test" ] && echo "fzf_mock" || echo "fzf")"
"$fzf_cmd" ${args[@]:-} --inline-info "$@"
}
ui::select() {
local -r cheats="$1"
[[ "${SHELL:-}" =~ 'fish' ]] || local -r sub='$'
local -r script_path="${NAVI_HOME}/navi"
local -r preview_cmd="\"${script_path}\" preview ${sub:-}(echo \'{}\' | $(arg::serialize_code))"
local -r query="$(dict::get "$OPTIONS" query)"
local -r entry_point="$(dict::get "$OPTIONS" entry_point)"
local -r preview="$(dict::get "$OPTIONS" preview)"
local -r best="$(dict::get "$OPTIONS" best)"
local -r fzf_overrides="$(dict::get "$OPTIONS" fzf-overrides)"
local args=()
args+=("-i")
args+=("--ansi")
if $preview; then
args+=("--preview"); args+=("$preview_cmd")
args+=("--preview-window"); args+=("up:2")
fi
if [[ -n "$query" ]] && $best; then
args+=("--filter"); args+=("${query} ")
elif [[ -n "$query" ]] && ! $best; then
args+=("--query"); args+=("${query} ")
fi
if [ "$entry_point" = "search" ]; then
args+=("--header"); args+=("Displaying online results. Please refer to 'navi --help' for details")
fi
args+=("--delimiter"); args+=('\s\s+')
args+=("--expect"); args+=("ctrl-y")
export FZF_DEFAULT_OPTS="${FZF_DEFAULT_OPTS} ${fzf_overrides}"
local -r result="$(echo "$cheats" \
| cheat::prettify \
| str::as_column $(printf "$ESCAPE_CHAR_3") \
| ui::fzf "${args[@]}")"
local -r key="$(echo "$result" | head -n1)"
echo "$result" \
| ($best && head -n1 || tail -n +2) \
| selection::dict "$cheats" "$key"
}
ui::clear_previous_line() {
tput cuu1 2>/dev/null && tput el || true
}
ui::width() {
shopt -s checkwinsize; (:;:) 2> /dev/null || true
if command_exists tput; then
tput cols
else
echo 130
fi
}
ui::remove_dep_order() {
sed -E 's/^: [^;]+; //'
}
ui::print_preview() {
local -r selection="$1"
local -r comment="$(selection::comment "$selection" | cmd::unescape)"
local -r snippet="$(selection::snippet "$selection" | cmd::unescape)"
local -r tags="$(selection::tags "$selection" | cmd::unescape)"
local -r comment_color="$(style::comment_color)"
local -r snippet_color="$(style::snippet_color)"
local -r tag_color="$(style::tag_color)"
printf "\033[${comment_color}m# "; echo -n "$comment"
printf " \033[${tag_color}m["; echo -n "$tags"; echo "]"
printf "\033[${snippet_color}m"
echo "$snippet" | ui::remove_dep_order
}

View file

@ -1,9 +0,0 @@
version::code() {
cd "$NAVI_HOME"
local -r git_info=$(git log -n 1 --pretty=format:'%h%n%ad%n%s' --date=format:'%Y-%m-%d %Hh%M')
if [ -z "$git_info" ]; then
return 1
else
echo -e "$git_info"
fi
}

View file

@ -1,18 +0,0 @@
#!/usr/bin/env bash
interpolation_one_word() {
echo "curl http://mysite.com/<user>/profile" \
| arg::interpolate "user" "john" \
| test::equals "curl http://mysite.com/john/profile"
}
interpolation_multiple_words() {
echo "cp <file> <new_file>" \
| arg::interpolate "file" "C:/Program Files/app/foo.exe" \
| arg::interpolate "new_file" "/mnt/c/Users/john/foo.exe" \
| test::equals 'cp "C:/Program Files/app/foo.exe" /mnt/c/Users/john/foo.exe'
}
test::set_suite "arg"
test::run "if there's only one word, interpolation doesn't include quotes" interpolation_one_word
test::run "if there are multiple words, interpolation includes quotes" interpolation_multiple_words

View file

@ -1,8 +0,0 @@
#!/usr/bin/env bash
assert_docker_cheat() {
cheat::find | test::contains "docker.cheat"
}
test::set_suite "cheat"
test::run "We can find at least one known cheatsheet" assert_docker_cheat

View file

@ -1,109 +0,0 @@
#!/usr/bin/env bash
test::coll_equals() {
local -r actual="$(cat)"
local -r expected="$(coll::new "$@")"
echo "$actual" | test::equals "$expected"
}
inc() {
local -r x="$1"
echo $((x+1))
}
sum() {
local -r x="$1"
local -r y="$2"
echo $((x*y))
}
powers() {
local x="$1"
coll::new $((x*10)) $((x*100))
}
odd() {
local x="$1"
[ $((x%2)) -eq 1 ]
}
coll_map() {
coll::new 1 2 3 \
| coll::map inc \
| test::coll_equals 2 3 4
}
coll_flatmap() {
coll::new 1 2 3 \
| coll::map powers \
| test::coll_equals 10 100 20 200 30 300
}
coll_reduce() {
coll::new 1 2 3 \
| coll::reduce sum 10 \
| test::equals 60
}
coll_filter() {
coll::new 1 2 3 4 5 \
| coll::filter odd \
| test::coll_equals 1 3 5
}
coll_remove() {
coll::new 1 2 3 4 5 \
| coll::remove odd \
| test::coll_equals 2 4
}
coll_first() {
coll::new 1 2 3 \
| coll::first \
| test::coll_equals 1
}
coll_rest() {
coll::new 1 2 3 \
| coll::rest \
| test::coll_equals 2 3
}
coll_add() {
coll::new 1 2 3 \
| coll::add 4 5 \
| coll::add 6 7 \
| test::coll_equals 1 2 3 4 5 6 7
}
coll_concat() {
coll::new 1 2 3 \
| coll::add "$(coll::new 4 5)" \
| test::coll_equals 1 2 3 4 5
}
coll_reverse() {
coll::new 1 2 3 \
| coll::reverse \
| test::coll_equals 3 2 1
}
coll_set() {
coll::new 1 2 3 2 4 2 \
| coll::set \
| test::coll_equals 1 2 3 4
}
test::set_suite "coll"
test::run "map" coll_map
test::run "filter" coll_filter
test::run "remove" coll_remove
test::run "first" coll_first
test::run "rest" coll_rest
test::run "add" coll_add
test::run "add can be used as concat" coll_concat
test::run "reduce" coll_reduce
test::run "we can use map as flatmap" coll_flatmap
test::run "reverse" coll_reverse
test::run "set" coll_set

View file

@ -1,78 +0,0 @@
#!/usr/bin/env bash
export NAVI_FORCE_GNU=true
source "${NAVI_HOME}/src/main.sh"
source "${NAVI_HOME}/test/log.sh"
opts::eval "$@"
TEST_DIR="${NAVI_HOME}/test"
PASSED=0
FAILED=0
SKIPPED=0
SUITE=""
test::set_suite() {
SUITE="$*"
}
test::success() {
PASSED=$((PASSED+1))
log::success "Test passed!"
}
test::fail() {
FAILED=$((FAILED+1))
log::error "Test failed..."
return
}
test::skip() {
echo
log::note "${SUITE:-unknown} - ${1:-unknown}"
SKIPPED=$((SKIPPED+1))
log::warning "Test skipped..."
return
}
test::run() {
echo
log::note "${SUITE:-unknown} - ${1:-unknown}"
shift
eval "$*" && test::success || test::fail
}
test::equals() {
local -r actual="$(cat)"
local -r expected="$(echo "${1:-}")"
if [[ "$actual" != "$expected" ]]; then
log::error "Expected '${expected}' but got '${actual}'"
return 2
fi
}
test::contains() {
local -r actual="$(cat)"
local -r regex="$(echo "${1:-}")"
if ! echo "$actual" | grep -qE "$regex"; then
log::error "Expected to contain '${regex}' but got '${actual}'"
return 2
fi
}
test::finish() {
echo
if [ $SKIPPED -gt 0 ]; then
log::warning "${SKIPPED} tests skipped!"
fi
if [ $FAILED -gt 0 ]; then
log::error "${PASSED} tests passed but ${FAILED} failed... :("
exit "${FAILED}"
else
log::success "All ${PASSED} tests passed! :)"
exit 0
fi
}

View file

@ -1,133 +0,0 @@
#!/usr/bin/env bash
inc() {
local -r x="$1"
echo $((x+1))
}
test::map_equals() {
local -r actual="$(cat | dict::unescape_value | sort)"
local -r expected="$(dict::new "$@" | dict::unescape_value | sort)"
echo "$actual" | test::equals "$expected"
}
dict_assoc() {
dict::new \
| dict::assoc "foo" "42" \
| tr -d "$ESCAPE_CHAR" \
| tr -d "$ESCAPE_CHAR_2" \
| tr -d "$ESCAPE_CHAR_3" \
| test::equals "foo: 42"
}
dict_assoc_perc() {
dict::new \
| dict::assoc "foo" "42 %" bar "% 7" \
| dict::get bar \
| test::equals "% 7"
}
dict_assoc_multiple() {
dict::new \
| dict::assoc "foo" "42" "bar" "5" \
| test::map_equals "bar" 5 "foo" 42
}
dict_dissoc() {
dict::new \
| dict::assoc "foo" "42" "bar" "5" \
| dict::dissoc "bar" \
| test::map_equals "foo" 42
}
dict_assoc_again() {
dict::new \
| dict::assoc "foo" "42" \
| dict::assoc "foo" "42" \
| test::map_equals "foo" 42
}
dict_dissoc_nested() {
dict::new \
| dict::assoc "foo" "42" "bar.a" 5 "bar.b" 6 "baz" 63 \
| dict::dissoc "bar" \
| test::map_equals "baz" 63 "foo" 42
}
dict_assoc_nested() {
dict::new \
| dict::assoc "foo" "42" "bar.a" 5 "bar.c" 7 "baz" 63 \
| dict::assoc "bar.b" 6 \
| dict::get "bar.b" \
| test::equals "asdfsadf"
}
dict_get() {
dict::new \
| dict::assoc "foo" "42" \
| dict::get "foo" \
| test::equals "42"
}
dict_get_nested() {
dict::new \
| dict::assoc "foo" "42" "bar.a" 5 "bar.b" 6 "baz" 63 \
| dict::get "bar.a" \
| test::equals "5"
}
dict_get_dict() {
dict::new \
| dict::assoc "foo" "42" "bar.a" 5 "bar.b" 6 "baz" 63 \
| dict::get "bar" \
| test::map_equals "bar.a" 5 "bar.b" 6
}
dict_get_keys() {
dict::new \
| dict::assoc "foo" "42" "bar.a" 5 "bar.b" 6 "baz" 63 \
| dict::keys \
| test::equals "$(echo -e "foo\nbar.a\nbar.b\nbaz")"
}
dict_get_values() {
dict::new \
| dict::assoc "foo" "42" "bar.a" 5 "bar.b" 6 "baz" 63 \
| dict::values \
| test::equals "$(echo -e "5\n6\n42\n63")"
}
dict_zipmap() {
dict::zipmap "key1\nkey2\nkey3" "value1\nvalue2\nvalue3" \
| test::map_equals "key1" "value1" "key2" "value2" "key3" "value3"
}
dict_update() {
dict::new "foo" 42 "bar" 5 \
| dict::update "bar" inc \
| test::map_equals "foo" 42 "bar" 6
}
dict_merge() {
dict::new "foo" 42 "bar" 5 \
| dict::merge "$(dict::new "bar" 7 "lorem" "ipsum")" \
| test::map_equals "foo" 42
}
test::set_suite "dict"
test::run "We can assoc a value" dict_assoc
test::skip "We can merge dicts" dict_merge
test::run "We can assoc values with %" dict_assoc_perc
test::run "We can assoc multiple values" dict_assoc_multiple
test::skip "We can assoc a nested value" dict_assoc_nested
test::run "We can dissoc a value" dict_dissoc
test::run "Associng the same value is a no-op" dict_assoc_again
test::run "Dissocing a key will replace all its subvalues" dict_dissoc_nested
test::run "We can get a value" dict_get
test::run "We can get a nested value" dict_get_nested
test::run "We can get a dictionary" dict_get_dict
test::run "We can get all keys" dict_get_keys
test::skip "We can get all values" dict_get_values
test::skip "We can get create a dict from a zipmap" dict_zipmap
test::skip "We can update a value" dict_update

View file

@ -1,57 +0,0 @@
#!/usr/bin/env bash
NAVI_BIN="${NAVI_HOME}/navi"
TEST_DIR="${NAVI_HOME}/test"
_navi() {
"$NAVI_BIN" "$@"
}
fzf_mock() {
head -n1 | sed 's/\x1b\[[0-9;]*m//g'
}
assert_version() {
local -r version="$(cat "$NAVI_BIN" | grep VERSION | cut -d'=' -f2 | tr -d '"')"
_navi --version \
| test::equals "$version"
}
assert_help() {
_navi --help \
| grep -q 'Options:'
}
assert_home() {
_navi home \
| grep -q '/'
}
assert_best() {
_navi best constant --path "$TEST_DIR" \
| test::equals 42
}
assert_query() {
NAVI_ENV="test" _navi --path "$TEST_DIR" \
| test::equals "2 12"
}
export HAS_FZF="$(command_exists fzf && echo true || echo false)"
test::fzf() {
if $HAS_FZF; then
test::run "$@"
else
test::skip "$@"
fi
}
test::set_suite "integration"
export -f fzf_mock
test::run "version" assert_version
test::run "help" assert_help
test::run "home" assert_home
test::fzf "best" assert_best # FZF setup needed in CircleCI
test::fzf "query" assert_query # FZF setup needed in CircleCI

View file

@ -1,102 +0,0 @@
#!/usr/bin/env bash
_export_colors() {
if ! ${DOT_COLORS_EXPORTED:-false}; then
if [ -z ${TERM:-} ] || [ $TERM = "dumb" ]; then
bold=""
underline=""
freset=""
purple=""
red=""
green=""
tan=""
blue=""
else
bold=$(tput bold)
underline=$(tput sgr 0 1)
freset=$(tput sgr0)
purple=$(tput setaf 171)
red=$(tput setaf 1)
green=$(tput setaf 76)
tan=$(tput setaf 3)
blue=$(tput setaf 38)
fi
log_black=30
log_red=31
log_green=32
log_yellow=33
log_blue=34
log_purple=35
log_cyan=36
log_white=37
log_regular=0
log_bold=1
log_underline=4
readonly DOT_COLORS_EXPORTED=true
fi
}
log::color() {
_export_colors
local bg=false
case "$@" in
*reset*) echo "\e[0m"; exit 0 ;;
*black*) color=$log_black ;;
*red*) color=$log_red ;;
*green*) color=$log_green ;;
*yellow*) color=$log_yellow ;;
*blue*) color=$log_blue ;;
*purple*) color=$log_purple ;;
*cyan*) color=$log_cyan ;;
*white*) color=$log_white ;;
esac
case "$@" in
*regular*) mod=$log_regular ;;
*bold*) mod=$log_bold ;;
*underline*) mod=$log_underline ;;
esac
case "$@" in
*background*) bg=true ;;
*bg*) bg=true ;;
esac
if $bg; then
echo "\e[${color}m"
else
echo "\e[${mod:-$log_regular};${color}m"
fi
}
if [ -z ${LOG_FILE+x} ]; then
readonly LOG_FILE="/tmp/$(basename "$0").log"
fi
_log() {
local template=$1
shift
if ${log_to_file:-false}; then
echo -e $(printf "$template" "$@") | tee -a "$LOG_FILE" >&2
else
echo -e $(printf "$template" "$@")
fi
}
_header() {
local TOTAL_CHARS=60
local total=$TOTAL_CHARS-2
local size=${#1}
local left=$((($total - $size) / 2))
local right=$(($total - $size - $left))
printf "%${left}s" '' | tr ' ' =
printf " $1 "
printf "%${right}s" '' | tr ' ' =
}
log::header() { _export_colors && _log "\n${bold}${purple}$(_header "$1")${freset}\n"; }
log::success() { _export_colors && _log "${green}✔ %s${freset}\n" "$@"; }
log::error() { _export_colors && _log "${red}✖ %s${freset}\n" "$@"; }
log::warning() { _export_colors && _log "${tan}➜ %s${freset}\n" "$@"; }
log::note() { _export_colors && _log "${blue}➜ %s${freset}\n" "$@"; }

View file

@ -1,9 +0,0 @@
#!/usr/bin/env bash
existing() {
platform::existing_command oasida fngo ni awk aoisdn oafm \
| test::equals awk
}
test::set_suite "platform"
test::run "existing_command" existing

View file

@ -1,16 +0,0 @@
% test, playground
# this should be the first test. single and double quotes + newlines
echo <x> <y>
# variable names
echo <x> <foo> <foo_bar> <lorem-ipsum> <dolor sit>
# use %
coll::new 1 2 3 | xargs -I% echo "hello %"
# return a constant number
echo 42
$ x: echo -e '2\n3\n4'
$ y: echo -e "$((x+10))\n$((x+20))"

View file

@ -1,15 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
export NAVI_HOME="$(cd "$(dirname "$0")/.." && pwd)"
source "${NAVI_HOME}/test/core.sh"
tests="$(find "$NAVI_HOME/test" -iname "${1:-}*_test.sh")"
NAVI_PATH="${NAVI_HOME}/cheats"
for test in $tests; do
source "$test"
done
test::finish

View file

@ -1,20 +0,0 @@
#!/usr/bin/env bash
check_all_vars() {
local arg
IFS=$'\n'
for var in $(cat "$1" | grep -Eo "<[^>]*>"); do
if ! echo "$var" | grep -qE "$ARG_REGEX"; then
echoerr "$var isn't a valid variable name!"
return 1
fi
done
}
path="$NAVI_PATH"
NAVI_PATH="${NAVI_PATH}:${TEST_DIR}"
for cheat in $(cheat::find); do
test::run "All variables in $(basename $cheat) are valid" \
'check_all_vars "$cheat"'
done
NAVI_PATH="$path"

7
tests/tests.rs Normal file
View file

@ -0,0 +1,7 @@
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}