diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 6d7347f..0000000 --- a/.circleci/config.yml +++ /dev/null @@ -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 \ No newline at end of file diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml new file mode 100644 index 0000000..7ee639f --- /dev/null +++ b/.github/workflows/cd.yml @@ -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 }} \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..c380d0f --- /dev/null +++ b/.github/workflows/ci.yml @@ -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 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..53eaa21 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +**/*.rs.bk diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..095b023 --- /dev/null +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..f4ea05f --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "navi" +version = "2.0.0" +authors = ["Denis Isidoro "] +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" \ No newline at end of file diff --git a/Makefile b/Makefile index 4260650..6ee6d14 100644 --- a/Makefile +++ b/Makefile @@ -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 \ No newline at end of file diff --git a/README.md b/README.md index b512837..044669e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ # navi icon [![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: diff --git a/cheats/android.cheat b/cheats/android.cheat index fbc02f1..b377378 100644 --- a/cheats/android.cheat +++ b/cheats/android.cheat @@ -24,4 +24,7 @@ $ device: adb devices --- --headers 1 --column 1 # Start emulator "$ANDROID_HOME/tools/emulator" -avd -netdelay none -netspeed full +# TODO: remove this +echo "$(ls -la)" + $ emulator: "$ANDROID_HOME/tools/emulator" -list-avds diff --git a/cheats/network.cheat b/cheats/network.cheat index 2d69ba3..133d294 100644 --- a/cheats/network.cheat +++ b/cheats/network.cheat @@ -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 \ No newline at end of file diff --git a/navi b/navi index bbeee3c..9fb406d 100755 --- a/navi +++ b/navi @@ -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 diff --git a/navi.plugin.bash b/navi.plugin.bash deleted file mode 100644 index 5ee3757..0000000 --- a/navi.plugin.bash +++ /dev/null @@ -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"' diff --git a/navi.plugin.fish b/navi.plugin.fish deleted file mode 100644 index 645dffd..0000000 --- a/navi.plugin.fish +++ /dev/null @@ -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 diff --git a/navi.plugin.zsh b/navi.plugin.zsh deleted file mode 100644 index 60c2ea4..0000000 --- a/navi.plugin.zsh +++ /dev/null @@ -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 diff --git a/scripts/action b/scripts/action new file mode 100755 index 0000000..a9b2d50 --- /dev/null +++ b/scripts/action @@ -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 diff --git a/scripts/brew b/scripts/brew new file mode 100755 index 0000000..e292121 --- /dev/null +++ b/scripts/brew @@ -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 diff --git a/scripts/docker b/scripts/docker index 87278e1..e5319a1 100755 --- a/scripts/docker +++ b/scripts/docker @@ -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" \ No newline at end of file + -e HOMEBREW_NO_AUTO_UPDATE=1 \ + -it linuxbrew/alpine \ + bash -c 'brew install denisidoro/tools/navirs; bash' diff --git a/scripts/fix b/scripts/fix new file mode 100755 index 0000000..0d927ab --- /dev/null +++ b/scripts/fix @@ -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 diff --git a/scripts/install b/scripts/install index e58b4b6..a8b7675 100755 --- a/scripts/install +++ b/scripts/install @@ -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" \ No newline at end of file +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 "$@" diff --git a/scripts/lint b/scripts/lint deleted file mode 100755 index 5952fca..0000000 --- a/scripts/lint +++ /dev/null @@ -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 diff --git a/scripts/make b/scripts/make new file mode 100755 index 0000000..ea7758d --- /dev/null +++ b/scripts/make @@ -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 diff --git a/scripts/playground b/scripts/playground deleted file mode 100755 index 5ca41bb..0000000 --- a/scripts/playground +++ /dev/null @@ -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" "$@" \ No newline at end of file diff --git a/scripts/release b/scripts/release deleted file mode 100755 index d82a0dc..0000000 --- a/scripts/release +++ /dev/null @@ -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}\"" \ No newline at end of file diff --git a/scripts/run b/scripts/run new file mode 100755 index 0000000..93198d4 --- /dev/null +++ b/scripts/run @@ -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 -- "$@" diff --git a/scripts/tag b/scripts/tag new file mode 100755 index 0000000..cd89a56 --- /dev/null +++ b/scripts/tag @@ -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 diff --git a/scripts/uninstall b/scripts/uninstall deleted file mode 100755 index 1a90c6a..0000000 --- a/scripts/uninstall +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -BIN="/usr/local/bin/navi" -rm -rf "$BIN" || true \ No newline at end of file diff --git a/scripts/update b/scripts/update deleted file mode 100755 index a448a42..0000000 --- a/scripts/update +++ /dev/null @@ -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 diff --git a/shell/navi.plugin.bash b/shell/navi.plugin.bash new file mode 100644 index 0000000..138ec3b --- /dev/null +++ b/shell/navi.plugin.bash @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +__call_navi() { + local -r f="$(mktemp || echo "${HOME}/.naviresult")" + navi --save "$f" /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"' \ No newline at end of file diff --git a/shell/navi.plugin.fish b/shell/navi.plugin.fish new file mode 100644 index 0000000..49ca20b --- /dev/null +++ b/shell/navi.plugin.fish @@ -0,0 +1,23 @@ +#!/usr/bin/env fish + +function __call_navi + set -l f (mktemp || echo "${HOME}/.naviresult") + navi --save "$f" /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 diff --git a/shell/navi.plugin.zsh b/shell/navi.plugin.zsh new file mode 100644 index 0000000..c9f2de4 --- /dev/null +++ b/shell/navi.plugin.zsh @@ -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 + 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 diff --git a/src/arg.sh b/src/arg.sh deleted file mode 100644 index 6fd2afc..0000000 --- a/src/arg.sh +++ /dev/null @@ -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 -} diff --git a/src/cheat.rs b/src/cheat.rs new file mode 100644 index 0000000..6e8b864 --- /dev/null +++ b/src/cheat.rs @@ -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, + pub multi: bool, +} + +pub type Value = (String, Option); + +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 = 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::().unwrap() + } + "--column" => column = Some(parts.next().unwrap().parse::().unwrap()), + _ => (), + } + } + + SuggestionOpts { + header_lines, + column, + multi, + } +} + +fn parse_variable_line(line: &str) -> (&str, &str, Option) { + 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, + 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 { + let mut variables: HashMap = 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 +} diff --git a/src/cheat.sh b/src/cheat.sh deleted file mode 100755 index 84bf6ea..0000000 --- a/src/cheat.sh +++ /dev/null @@ -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" -} diff --git a/src/clipboard.sh b/src/clipboard.sh deleted file mode 100644 index 92eafc9..0000000 --- a/src/clipboard.sh +++ /dev/null @@ -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 -} \ No newline at end of file diff --git a/src/cmd.sh b/src/cmd.sh deleted file mode 100644 index f0bdd8e..0000000 --- a/src/cmd.sh +++ /dev/null @@ -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 -} \ No newline at end of file diff --git a/src/cmds/aux.rs b/src/cmds/aux.rs new file mode 100644 index 0000000..2662715 --- /dev/null +++ b/src/cmds/aux.rs @@ -0,0 +1,16 @@ +use std::error::Error; +use std::process; + +pub fn abort(operation: &str, issue_number: u32) -> Result<(), Box> { + 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) +} diff --git a/src/cmds/best.rs b/src/cmds/best.rs new file mode 100644 index 0000000..bec3dd7 --- /dev/null +++ b/src/cmds/best.rs @@ -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, config: Config) -> Result<(), Box> { + if !args.is_empty() { + cmds::aux::abort("passing arguments to 'navi best'", 201) + } else { + cmds::core::main(Variant::Filter(query), config) + } +} diff --git a/src/cmds/core.rs b/src/cmds/core.rs new file mode 100644 index 0000000..3922dba --- /dev/null +++ b/src/cmds/core.rs @@ -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, + 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> { + 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) + } + } +} diff --git a/src/cmds/func.rs b/src/cmds/func.rs new file mode 100644 index 0000000..8035eca --- /dev/null +++ b/src/cmds/func.rs @@ -0,0 +1,7 @@ +use std::error::Error; + +use super::aux; + +pub fn main(_func: String, _args: Vec) -> Result<(), Box> { + aux::abort("calling `navi fn`", 201) +} diff --git a/src/cmds/home.rs b/src/cmds/home.rs new file mode 100644 index 0000000..cbcdfb1 --- /dev/null +++ b/src/cmds/home.rs @@ -0,0 +1,8 @@ +use std::error::Error; + +use crate::filesystem; + +pub fn main() -> Result<(), Box> { + println!("{}", filesystem::exe_path_string()); + Ok(()) +} diff --git a/src/cmds/mod.rs b/src/cmds/mod.rs new file mode 100644 index 0000000..1c7a920 --- /dev/null +++ b/src/cmds/mod.rs @@ -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; diff --git a/src/cmds/preview.rs b/src/cmds/preview.rs new file mode 100644 index 0000000..c90b6d1 --- /dev/null +++ b/src/cmds/preview.rs @@ -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> { + let (tags, comment, snippet) = extract_elements(&line[..]); + display::preview(comment, tags, snippet); + process::exit(0) +} diff --git a/src/cmds/query.rs b/src/cmds/query.rs new file mode 100644 index 0000000..12a6571 --- /dev/null +++ b/src/cmds/query.rs @@ -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> { + cmds::core::main(Variant::Query(query), config) +} diff --git a/src/cmds/search.rs b/src/cmds/search.rs new file mode 100644 index 0000000..1f334ce --- /dev/null +++ b/src/cmds/search.rs @@ -0,0 +1,8 @@ +use std::error::Error; + +use super::aux; +use crate::option::Config; + +pub fn main(_query: String, _config: Config) -> Result<(), Box> { + aux::abort("searching for cheats online", 201) +} diff --git a/src/cmds/shell.rs b/src/cmds/shell.rs new file mode 100644 index 0000000..beaa72b --- /dev/null +++ b/src/cmds/shell.rs @@ -0,0 +1,15 @@ +use std::error::Error; + +use crate::filesystem; + +pub fn main(shell: &str) -> Result<(), Box> { + let file = match shell { + "zsh" => "navi.plugin.zsh", + "fish" => "navi.plugin.fish", + _ => "navi.plugin.bash", + }; + + println!("{}/shell/{}", filesystem::exe_path_string(), file); + + Ok(()) +} diff --git a/src/coll.sh b/src/coll.sh deleted file mode 100644 index af2071e..0000000 --- a/src/coll.sh +++ /dev/null @@ -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 -} \ No newline at end of file diff --git a/src/dict.sh b/src/dict.sh deleted file mode 100644 index df5f6ab..0000000 --- a/src/dict.sh +++ /dev/null @@ -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" -} \ No newline at end of file diff --git a/src/display.rs b/src/display.rs new file mode 100644 index 0000000..c786e89 --- /dev/null +++ b/src/display.rs @@ -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) +} diff --git a/src/docstring.txt b/src/docstring.txt deleted file mode 100644 index 9ffef7c..0000000 --- a/src/docstring.txt +++ /dev/null @@ -1,63 +0,0 @@ -An interactive cheatsheet tool for the command-line - -Usage: - navi [command] [...] [options] - -Commands: - query Pre-filter results - search Search for cheatsheets on online repositories - best ... Considers the best match - widget 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 diff --git a/src/filesystem.rs b/src/filesystem.rs new file mode 100644 index 0000000..58e9da6 --- /dev/null +++ b/src/filesystem.rs @@ -0,0 +1,33 @@ +use std::fs::File; +use std::io::{self, BufRead, BufReader, Lines}; +use std::path::Path; + +pub fn read_lines

(filename: P) -> io::Result>> +where + P: AsRef, +{ + 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(), + ) +} diff --git a/src/fzf.rs b/src/fzf.rs new file mode 100644 index 0000000..5a49110 --- /dev/null +++ b/src/fzf.rs @@ -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, + pub filter: Option, + pub prompt: Option, + 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, +} + +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(opts: Opts, stdin_fn: F) -> (process::Output, Option>) +where + F: Fn(&mut process::ChildStdin) -> Option>, +{ + 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) +} diff --git a/src/health.sh b/src/health.sh deleted file mode 100644 index 3e7eb6e..0000000 --- a/src/health.sh +++ /dev/null @@ -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 -} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..036c0e9 --- /dev/null +++ b/src/main.rs @@ -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> { + 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(), + }, + } + } + } +} diff --git a/src/main.sh b/src/main.sh deleted file mode 100644 index 759c1d3..0000000 --- a/src/main.sh +++ /dev/null @@ -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 -} diff --git a/src/misc.sh b/src/misc.sh deleted file mode 100644 index df02a30..0000000 --- a/src/misc.sh +++ /dev/null @@ -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 -} \ No newline at end of file diff --git a/src/option.rs b/src/option.rs new file mode 100644 index 0000000..8b95c81 --- /dev/null +++ b/src/option.rs @@ -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, + + /// [alpha] Instead of executing a snippet, saves it to a file + #[structopt(short, long)] + pub save: Option, + + /// 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, + /// Overrides for fzf commands (must start with an empty space) + #[structopt(long)] + #[structopt(long)] + pub fzf_overrides: Option, + + #[structopt(subcommand)] + pub cmd: Option, +} + +#[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 }, + /// Performs ad-hoc functions provided by navi + Func { func: String, args: Vec }, + /// 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 { + let mut args = env::args(); + args.next(); + if args.next() == Some(String::from("preview")) { + Some(InternalCommand::Preview { + line: args.next().unwrap(), + }) + } else { + None + } +} diff --git a/src/opts.sh b/src/opts.sh deleted file mode 100644 index a1825c6..0000000 --- a/src/opts.sh +++ /dev/null @@ -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" -} diff --git a/src/search.sh b/src/search.sh deleted file mode 100644 index f611e0b..0000000 --- a/src/search.sh +++ /dev/null @@ -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" -} \ No newline at end of file diff --git a/src/selection.sh b/src/selection.sh deleted file mode 100644 index 06cde84..0000000 --- a/src/selection.sh +++ /dev/null @@ -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 -} \ No newline at end of file diff --git a/src/str.sh b/src/str.sh deleted file mode 100644 index 55ed101..0000000 --- a/src/str.sh +++ /dev/null @@ -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)}' -} \ No newline at end of file diff --git a/src/style.sh b/src/style.sh deleted file mode 100644 index 737be5c..0000000 --- a/src/style.sh +++ /dev/null @@ -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 "$@"; } - diff --git a/src/ui.sh b/src/ui.sh deleted file mode 100644 index 08c4a71..0000000 --- a/src/ui.sh +++ /dev/null @@ -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 -} diff --git a/src/version.sh b/src/version.sh deleted file mode 100644 index a830cd8..0000000 --- a/src/version.sh +++ /dev/null @@ -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 -} \ No newline at end of file diff --git a/test/arg_test.sh b/test/arg_test.sh deleted file mode 100644 index 21bf857..0000000 --- a/test/arg_test.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env bash - -interpolation_one_word() { - echo "curl http://mysite.com//profile" \ - | arg::interpolate "user" "john" \ - | test::equals "curl http://mysite.com/john/profile" -} - -interpolation_multiple_words() { - echo "cp " \ - | 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 diff --git a/test/cheat_test.sh b/test/cheat_test.sh deleted file mode 100644 index 17e2abe..0000000 --- a/test/cheat_test.sh +++ /dev/null @@ -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 diff --git a/test/coll_test.sh b/test/coll_test.sh deleted file mode 100644 index aeb749b..0000000 --- a/test/coll_test.sh +++ /dev/null @@ -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 diff --git a/test/core.sh b/test/core.sh deleted file mode 100644 index cb3b2e7..0000000 --- a/test/core.sh +++ /dev/null @@ -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 -} diff --git a/test/dict_test.sh b/test/dict_test.sh deleted file mode 100644 index f310bec..0000000 --- a/test/dict_test.sh +++ /dev/null @@ -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 diff --git a/test/integration_test.sh b/test/integration_test.sh deleted file mode 100644 index 36cf735..0000000 --- a/test/integration_test.sh +++ /dev/null @@ -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 diff --git a/test/log.sh b/test/log.sh deleted file mode 100644 index 507c610..0000000 --- a/test/log.sh +++ /dev/null @@ -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" "$@"; } diff --git a/test/platform_test.sh b/test/platform_test.sh deleted file mode 100644 index 3b06650..0000000 --- a/test/platform_test.sh +++ /dev/null @@ -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 diff --git a/test/playground.cheat b/test/playground.cheat deleted file mode 100644 index abd859f..0000000 --- a/test/playground.cheat +++ /dev/null @@ -1,16 +0,0 @@ -% test, playground - -# this should be the first test. single and double quotes + newlines -echo - -# variable names -echo - -# 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))" \ No newline at end of file diff --git a/test/run b/test/run deleted file mode 100755 index 93f532b..0000000 --- a/test/run +++ /dev/null @@ -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 diff --git a/test/var_test.sh b/test/var_test.sh deleted file mode 100644 index e9bfbd5..0000000 --- a/test/var_test.sh +++ /dev/null @@ -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" \ No newline at end of file diff --git a/tests/tests.rs b/tests/tests.rs new file mode 100644 index 0000000..31e1bb2 --- /dev/null +++ b/tests/tests.rs @@ -0,0 +1,7 @@ +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + assert_eq!(2 + 2, 4); + } +}