mirror of
https://github.com/denisidoro/navi
synced 2024-11-21 19:13:07 +00:00
Refactor code base (#760)
This commit is contained in:
parent
f5759f26aa
commit
ebb02e28ec
72 changed files with 1556 additions and 1354 deletions
2
.github/workflows/cd.yml
vendored
2
.github/workflows/cd.yml
vendored
|
@ -48,7 +48,7 @@ jobs:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v1
|
||||||
- name: Build
|
- name: Build
|
||||||
id: build
|
id: build
|
||||||
run: scripts/release ${{ matrix.target }}
|
run: scripts/dot rust release ${{ matrix.target }}
|
||||||
- name: Get the version
|
- name: Get the version
|
||||||
id: get_version
|
id: get_version
|
||||||
run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//}
|
run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//}
|
||||||
|
|
100
.github/workflows/ci.yml
vendored
100
.github/workflows/ci.yml
vendored
|
@ -9,25 +9,25 @@ on: [push]
|
||||||
name: CI
|
name: CI
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check:
|
# check:
|
||||||
name: Check
|
# name: Check
|
||||||
runs-on: ubuntu-latest
|
# runs-on: ubuntu-latest
|
||||||
steps:
|
# steps:
|
||||||
- name: Checkout sources
|
# - name: Checkout sources
|
||||||
uses: actions/checkout@v2
|
# uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Install stable toolchain
|
# - name: Install stable toolchain
|
||||||
uses: actions-rs/toolchain@v1
|
# uses: actions-rs/toolchain@v1
|
||||||
with:
|
# with:
|
||||||
profile: minimal
|
# profile: minimal
|
||||||
toolchain: stable
|
# toolchain: stable
|
||||||
override: true
|
# override: true
|
||||||
|
|
||||||
- name: Run cargo check
|
# - name: Run cargo check
|
||||||
uses: actions-rs/cargo@v1
|
# uses: actions-rs/cargo@v1
|
||||||
continue-on-error: false
|
# continue-on-error: false
|
||||||
with:
|
# with:
|
||||||
command: check
|
# command: check
|
||||||
|
|
||||||
test:
|
test:
|
||||||
name: Tests
|
name: Tests
|
||||||
|
@ -64,21 +64,7 @@ jobs:
|
||||||
command: test
|
command: test
|
||||||
|
|
||||||
- name: Install deps
|
- name: Install deps
|
||||||
run: |
|
run: ./scripts/dot pkg add git bash npm tmux
|
||||||
_install() {
|
|
||||||
if command -v "$1"; then
|
|
||||||
return 0;
|
|
||||||
fi
|
|
||||||
sudo apt-get install "$1" || sudo apt install "$1" || brew install "$1"
|
|
||||||
};
|
|
||||||
|
|
||||||
_install_many() {
|
|
||||||
for dep in $@; do
|
|
||||||
_install "$dep"
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
_install_many git bash npm tmux
|
|
||||||
|
|
||||||
- name: Install fzf
|
- name: Install fzf
|
||||||
run: git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf; yes | ~/.fzf/install;
|
run: git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf; yes | ~/.fzf/install;
|
||||||
|
@ -89,31 +75,31 @@ jobs:
|
||||||
- name: Run bash tests
|
- name: Run bash tests
|
||||||
run: ./tests/run
|
run: ./tests/run
|
||||||
|
|
||||||
# lints:
|
lints:
|
||||||
# name: Lints
|
name: Lints
|
||||||
# runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
# steps:
|
steps:
|
||||||
# - name: Checkout sources
|
- name: Checkout sources
|
||||||
# uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
# - name: Install stable toolchain
|
# - name: Install stable toolchain
|
||||||
# uses: actions-rs/toolchain@v1
|
# uses: actions-rs/toolchain@v1
|
||||||
# with:
|
# with:
|
||||||
# profile: minimal
|
# profile: minimal
|
||||||
# toolchain: stable
|
# toolchain: stable
|
||||||
# override: true
|
# override: true
|
||||||
# components: rustfmt, clippy
|
# components: rustfmt, clippy
|
||||||
|
|
||||||
# - name: Run cargo fmt
|
- name: Run cargo fmt
|
||||||
# uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
# continue-on-error: false
|
continue-on-error: false
|
||||||
# with:
|
with:
|
||||||
# command: fmt
|
command: fmt
|
||||||
# args: --all -- --check
|
args: --all -- --check
|
||||||
|
|
||||||
# - name: Run cargo clippy
|
- name: Run cargo clippy
|
||||||
# uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
# continue-on-error: false
|
continue-on-error: false
|
||||||
# with:
|
with:
|
||||||
# command: clippy
|
command: clippy
|
||||||
# args: -- -D warnings
|
args: -- -D warnings
|
||||||
|
|
26
Cargo.lock
generated
26
Cargo.lock
generated
|
@ -54,9 +54,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "3.2.8"
|
version = "3.2.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "190814073e85d238f31ff738fcb0bf6910cedeb73376c87cd69291028966fd83"
|
checksum = "54635806b078b7925d6e36810b1755f2a4b5b4d57560432c1ecf60bcbe10602b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atty",
|
"atty",
|
||||||
"bitflags",
|
"bitflags",
|
||||||
|
@ -304,7 +304,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "navi"
|
name = "navi"
|
||||||
version = "2.20.1"
|
version = "2.21.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
|
@ -501,9 +501,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.5.6"
|
version = "1.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1"
|
checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
|
@ -512,9 +512,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-syntax"
|
name = "regex-syntax"
|
||||||
version = "0.6.26"
|
version = "0.6.27"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64"
|
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "remove_dir_all"
|
name = "remove_dir_all"
|
||||||
|
@ -561,18 +561,18 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.138"
|
version = "1.0.140"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1578c6245786b9d168c5447eeacfb96856573ca56c9d68fdcf394be134882a47"
|
checksum = "fc855a42c7967b7c369eb5860f7164ef1f6f81c20c7cc1141f2a604e18723b03"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.138"
|
version = "1.0.140"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "023e9b1467aef8a10fb88f25611870ada9800ef7e22afce356bb0d2387b6f27c"
|
checksum = "6f2122636b9fe3b81f1cb25099fcf2d3f542cdb1d45940d56c713158884a05da"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -581,9 +581,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_yaml"
|
name = "serde_yaml"
|
||||||
version = "0.8.24"
|
version = "0.8.26"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "707d15895415db6628332b737c838b88c598522e4dc70647e59b72312924aebc"
|
checksum = "578a7433b776b56a35785ed5ce9a7e777ac0598aac5a6dd1b4b18a307c7fc71b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"ryu",
|
"ryu",
|
||||||
|
|
10
Cargo.toml
10
Cargo.toml
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "navi"
|
name = "navi"
|
||||||
version = "2.20.1"
|
version = "2.21.0"
|
||||||
authors = ["Denis Isidoro <denis_isidoro@live.com>"]
|
authors = ["Denis Isidoro <denis_isidoro@live.com>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "An interactive cheatsheet tool for the command-line"
|
description = "An interactive cheatsheet tool for the command-line"
|
||||||
|
@ -19,8 +19,8 @@ disable-repo-management = []
|
||||||
travis-ci = { repository = "denisidoro/navi", branch = "master" }
|
travis-ci = { repository = "denisidoro/navi", branch = "master" }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
regex = { version = "1.5.6", default-features = false, features = ["std", "unicode-perl"] }
|
regex = { version = "1.6.0", default-features = false, features = ["std", "unicode-perl"] }
|
||||||
clap = { version = "3.2.8", features = ["derive", "cargo"] }
|
clap = { version = "3.2.14", features = ["derive", "cargo"] }
|
||||||
crossterm = "0.24.0"
|
crossterm = "0.24.0"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
directories-next = "2.0.0"
|
directories-next = "2.0.0"
|
||||||
|
@ -31,8 +31,8 @@ thiserror = "1.0.31"
|
||||||
strip-ansi-escapes = "0.1.1"
|
strip-ansi-escapes = "0.1.1"
|
||||||
edit = "0.1.4"
|
edit = "0.1.4"
|
||||||
remove_dir_all = "0.7.0"
|
remove_dir_all = "0.7.0"
|
||||||
serde = { version = "1.0.138", features = ["derive"] }
|
serde = { version = "1.0.140", features = ["derive"] }
|
||||||
serde_yaml = "0.8.24"
|
serde_yaml = "0.8.26"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "navi"
|
name = "navi"
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
[toolchain]
|
[toolchain]
|
||||||
channel = "1.56.0"
|
channel = "1.62.0"
|
||||||
components = [ "rustfmt", "clippy" ]
|
components = [ "rustfmt", "clippy" ]
|
27
scripts/dot
Executable file
27
scripts/dot
Executable file
|
@ -0,0 +1,27 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
export NAVI_HOME="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
export PROJ_HOME="$NAVI_HOME"
|
||||||
|
export PROJ_NAME="navi"
|
||||||
|
export CARGO_PATH="${NAVI_HOME}/core/Cargo.toml"
|
||||||
|
|
||||||
|
# TODO: bump dotfiles + remove this fn
|
||||||
|
log::note() { log::info "$@"; }
|
||||||
|
export -f log::note
|
||||||
|
|
||||||
|
dot::clone() {
|
||||||
|
git clone 'https://github.com/denisidoro/dotfiles' "$DOTFILES"
|
||||||
|
cd "$DOTFILES"
|
||||||
|
git checkout 'v2022.07.16'
|
||||||
|
}
|
||||||
|
|
||||||
|
dot::clone_if_necessary() {
|
||||||
|
[ -n "${DOTFILES:-}" ] && [ -x "${DOTFILES}/bin/dot" ] && return
|
||||||
|
export DOTFILES="${NAVI_HOME}/target/dotfiles"
|
||||||
|
dot::clone
|
||||||
|
}
|
||||||
|
|
||||||
|
dot::clone_if_necessary
|
||||||
|
|
||||||
|
"${DOTFILES}/bin/dot" "$@"
|
38
scripts/fix
38
scripts/fix
|
@ -1,38 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
export NAVI_HOME="$(cd "$(dirname "$0")/.." && pwd)"
|
|
||||||
source "${NAVI_HOME}/scripts/install"
|
|
||||||
|
|
||||||
_commit() {
|
|
||||||
if [ -n "${DOTFILES:-}" ]; then
|
|
||||||
git add --all || true
|
|
||||||
dot git commit am || true
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
cd "$NAVI_HOME"
|
|
||||||
|
|
||||||
_commit
|
|
||||||
log::note "cargo clippy fix..."
|
|
||||||
cargo +nightly clippy --fix -Z unstable-options || true
|
|
||||||
|
|
||||||
_commit
|
|
||||||
log::note "cargo fix..."
|
|
||||||
cargo fix || true
|
|
||||||
|
|
||||||
_commit
|
|
||||||
log::note "cargo fmt..."
|
|
||||||
cargo fmt || true
|
|
||||||
|
|
||||||
_commit
|
|
||||||
log::note "clippy..."
|
|
||||||
cargo clippy || true
|
|
||||||
|
|
||||||
_commit
|
|
||||||
log::note "dot code beautify..."
|
|
||||||
find scripts -type f | xargs -I% dot code beautify % || true
|
|
||||||
dot code beautify "${NAVI_HOME}/shell/navi.plugin.bash" || true
|
|
||||||
dot code beautify "${NAVI_HOME}/shell/navi.plugin.zsh" || true
|
|
||||||
dot code beautify "${NAVI_HOME}/tests/core.bash" || true
|
|
||||||
dot code beautify "${NAVI_HOME}/tests/run" || true
|
|
|
@ -1,93 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
##? release
|
|
||||||
|
|
||||||
export NAVI_HOME="$(cd "$(dirname "$0")/.." && pwd)"
|
|
||||||
source "${NAVI_HOME}/scripts/install"
|
|
||||||
|
|
||||||
is_windows() {
|
|
||||||
local -r target="$1"
|
|
||||||
echo "$target" | grep -q "windows"
|
|
||||||
}
|
|
||||||
|
|
||||||
get_env_target() {
|
|
||||||
eval $(rustc --print cfg | grep target)
|
|
||||||
local r raw="${target_arch}-${target_vendor}-${target_os}-${target_env}"
|
|
||||||
log::note "env target raw: $raw"
|
|
||||||
if echo "$raw" | grep -q "x86_64-apple-macos"; then
|
|
||||||
echo "x86_64-apple-darwin"
|
|
||||||
else
|
|
||||||
echo "$raw"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
_tap() {
|
|
||||||
log::note "$@"
|
|
||||||
"$@"
|
|
||||||
}
|
|
||||||
|
|
||||||
release() {
|
|
||||||
local -r env_target="$(get_env_target)"
|
|
||||||
log::note "env target: $env_target"
|
|
||||||
|
|
||||||
local -r cross_target="${1:-"$env_target"}"
|
|
||||||
log::note "desired target: $cross_target"
|
|
||||||
|
|
||||||
TAR_DIR="${NAVI_HOME}/target/tar"
|
|
||||||
local use_zip=false
|
|
||||||
local cross=true
|
|
||||||
|
|
||||||
if [[ $cross_target == $env_target ]]; then
|
|
||||||
cross=false
|
|
||||||
fi
|
|
||||||
|
|
||||||
cd "$NAVI_HOME"
|
|
||||||
|
|
||||||
rm -rf "${NAVI_HOME}/target" 2> /dev/null || true
|
|
||||||
|
|
||||||
if $cross; then
|
|
||||||
cargo install cross 2> /dev/null || true
|
|
||||||
_tap cross build --release --locked --target "$cross_target"
|
|
||||||
local -r bin_folder="${cross_target}/release"
|
|
||||||
else
|
|
||||||
_tap cargo build --release --locked
|
|
||||||
local -r bin_folder="release"
|
|
||||||
fi
|
|
||||||
|
|
||||||
_ls "${bin_folder}"
|
|
||||||
|
|
||||||
if is_windows "$cross_target"; then
|
|
||||||
local -r exe_ext=".exe"
|
|
||||||
use_zip=true
|
|
||||||
else
|
|
||||||
local -r exe_ext=""
|
|
||||||
fi
|
|
||||||
|
|
||||||
bin_path="${NAVI_HOME}/target/${bin_folder}/navi${exe_ext}"
|
|
||||||
|
|
||||||
chmod +x "$bin_path"
|
|
||||||
mkdir -p "$TAR_DIR" 2> /dev/null || true
|
|
||||||
|
|
||||||
cp "$bin_path" "$TAR_DIR"
|
|
||||||
|
|
||||||
cd "$TAR_DIR"
|
|
||||||
|
|
||||||
if $use_zip; then
|
|
||||||
zip -r navi.zip *
|
|
||||||
echo ::set-output name=EXTENSION::zip
|
|
||||||
else
|
|
||||||
tar -czf navi.tar.gz *
|
|
||||||
echo ::set-output name=EXTENSION::tar.gz
|
|
||||||
fi
|
|
||||||
|
|
||||||
_ls "${bin_path}"
|
|
||||||
_ls "${TAR_DIR}"
|
|
||||||
}
|
|
||||||
|
|
||||||
_ls() {
|
|
||||||
log::note "contents from $@:"
|
|
||||||
ls -la "$@" || true
|
|
||||||
}
|
|
||||||
|
|
||||||
release "$@"
|
|
16
scripts/run
16
scripts/run
|
@ -1,16 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
export NAVI_HOME="$(cd "$(dirname "$0")/.." && pwd)"
|
|
||||||
|
|
||||||
cd "$NAVI_HOME"
|
|
||||||
|
|
||||||
if command_exists navi; then
|
|
||||||
navi "$@"
|
|
||||||
elif [ -f "./target/release/navi" ]; then
|
|
||||||
"./target/release/navi" "$@"
|
|
||||||
elif [ -f "./target/debug/navi" ]; then
|
|
||||||
"./target/debug/navi" "$@"
|
|
||||||
else
|
|
||||||
cargo run -- "$@"
|
|
||||||
fi
|
|
12
scripts/tag
12
scripts/tag
|
@ -1,12 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
export NAVI_HOME="$(cd "$(dirname "$0")/.." && pwd)"
|
|
||||||
source "${NAVI_HOME}/scripts/install"
|
|
||||||
|
|
||||||
version="${1:-$(version_from_toml)}"
|
|
||||||
log::note "version: $version..."
|
|
||||||
sleep 2
|
|
||||||
|
|
||||||
git tag -a "v${version}"
|
|
||||||
git push origin --tags
|
|
|
@ -1,10 +1,8 @@
|
||||||
use crate::parser;
|
use crate::parser::Parser;
|
||||||
use crate::structures::cheat::VariableMap;
|
use crate::prelude::*;
|
||||||
|
|
||||||
use crate::structures::fetcher;
|
use crate::structures::fetcher;
|
||||||
use anyhow::Context;
|
use std::process::{self, Command};
|
||||||
use anyhow::Result;
|
|
||||||
use std::collections::HashSet;
|
|
||||||
use std::process::{self, Command, Stdio};
|
|
||||||
|
|
||||||
fn map_line(line: &str) -> String {
|
fn map_line(line: &str) -> String {
|
||||||
line.trim().trim_end_matches(':').to_string()
|
line.trim().trim_end_matches(':').to_string()
|
||||||
|
@ -22,35 +20,6 @@ fn lines(query: &str, markdown: &str) -> impl Iterator<Item = Result<String>> {
|
||||||
.into_iter()
|
.into_iter()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_all(query: &str, cheat: &str, stdin: &mut std::process::ChildStdin) -> Result<Option<VariableMap>> {
|
|
||||||
let mut variables = VariableMap::new();
|
|
||||||
let mut visited_lines = HashSet::new();
|
|
||||||
|
|
||||||
if cheat.starts_with("Unknown topic.") {
|
|
||||||
eprintln!(
|
|
||||||
"`{}` not found in cheatsh.
|
|
||||||
|
|
||||||
Output:
|
|
||||||
{}
|
|
||||||
",
|
|
||||||
query, cheat
|
|
||||||
);
|
|
||||||
process::exit(35)
|
|
||||||
}
|
|
||||||
|
|
||||||
parser::read_lines(
|
|
||||||
lines(query, cheat),
|
|
||||||
"cheat.sh",
|
|
||||||
0,
|
|
||||||
&mut variables,
|
|
||||||
&mut visited_lines,
|
|
||||||
stdin,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
)?;
|
|
||||||
Ok(Some(variables))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn fetch(query: &str) -> Result<String> {
|
pub fn fetch(query: &str) -> Result<String> {
|
||||||
let args = ["-qO-", &format!("cheat.sh/{}", query)];
|
let args = ["-qO-", &format!("cheat.sh/{}", query)];
|
||||||
|
|
||||||
|
@ -109,12 +78,23 @@ impl Fetcher {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fetcher::Fetcher for Fetcher {
|
impl fetcher::Fetcher for Fetcher {
|
||||||
fn fetch(
|
fn fetch(&self, parser: &mut Parser) -> Result<bool> {
|
||||||
&self,
|
let cheat = &fetch(&self.query)?;
|
||||||
stdin: &mut std::process::ChildStdin,
|
|
||||||
_files: &mut Vec<String>,
|
if cheat.starts_with("Unknown topic.") {
|
||||||
) -> Result<Option<VariableMap>> {
|
eprintln!(
|
||||||
let cheat = fetch(&self.query)?;
|
"`{}` not found in cheatsh.
|
||||||
read_all(&self.query, &cheat, stdin)
|
|
||||||
|
Output:
|
||||||
|
{}
|
||||||
|
",
|
||||||
|
&self.query, cheat
|
||||||
|
);
|
||||||
|
process::exit(35)
|
||||||
|
}
|
||||||
|
|
||||||
|
parser.read_lines(lines(&self.query, cheat), "cheat.sh", None)?;
|
||||||
|
|
||||||
|
Ok(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
2
src/clients/mod.rs
Normal file
2
src/clients/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
pub mod cheatsh;
|
||||||
|
pub mod tldr;
|
|
@ -1,10 +1,6 @@
|
||||||
use crate::parser;
|
use crate::parser::Parser;
|
||||||
use crate::structures::cheat::VariableMap;
|
use crate::prelude::*;
|
||||||
use crate::structures::fetcher;
|
use crate::structures::fetcher;
|
||||||
use anyhow::{Context, Result};
|
|
||||||
use regex::Regex;
|
|
||||||
use std::collections::HashSet;
|
|
||||||
|
|
||||||
use std::process::{self, Command, Stdio};
|
use std::process::{self, Command, Stdio};
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
|
@ -58,26 +54,6 @@ fn markdown_lines(query: &str, markdown: &str) -> impl Iterator<Item = Result<St
|
||||||
.into_iter()
|
.into_iter()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_all(
|
|
||||||
query: &str,
|
|
||||||
markdown: &str,
|
|
||||||
stdin: &mut std::process::ChildStdin,
|
|
||||||
) -> Result<Option<VariableMap>> {
|
|
||||||
let mut variables = VariableMap::new();
|
|
||||||
let mut visited_lines = HashSet::new();
|
|
||||||
parser::read_lines(
|
|
||||||
markdown_lines(query, markdown),
|
|
||||||
"markdown",
|
|
||||||
0,
|
|
||||||
&mut variables,
|
|
||||||
&mut visited_lines,
|
|
||||||
stdin,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
)?;
|
|
||||||
Ok(Some(variables))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn fetch(query: &str) -> Result<String> {
|
pub fn fetch(query: &str) -> Result<String> {
|
||||||
let args = [query, "--markdown"];
|
let args = [query, "--markdown"];
|
||||||
|
|
||||||
|
@ -148,12 +124,9 @@ impl Fetcher {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fetcher::Fetcher for Fetcher {
|
impl fetcher::Fetcher for Fetcher {
|
||||||
fn fetch(
|
fn fetch(&self, parser: &mut Parser) -> Result<bool> {
|
||||||
&self,
|
|
||||||
stdin: &mut std::process::ChildStdin,
|
|
||||||
_files: &mut Vec<String>,
|
|
||||||
) -> Result<Option<VariableMap>> {
|
|
||||||
let markdown = fetch(&self.query)?;
|
let markdown = fetch(&self.query)?;
|
||||||
read_all(&self.query, &markdown, stdin)
|
parser.read_lines(markdown_lines(&self.query, &markdown), "markdown", None)?;
|
||||||
|
Ok(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,20 +1,15 @@
|
||||||
use crate::clipboard;
|
use super::extractor;
|
||||||
|
use crate::common::clipboard;
|
||||||
|
use crate::common::fs;
|
||||||
|
use crate::common::shell;
|
||||||
|
use crate::common::shell::ShellSpawnError;
|
||||||
use crate::config::Action;
|
use crate::config::Action;
|
||||||
use crate::config::CONFIG;
|
|
||||||
use crate::env_var;
|
use crate::env_var;
|
||||||
use crate::extractor;
|
|
||||||
use crate::finder::structures::{Opts as FinderOpts, SuggestionType};
|
use crate::finder::structures::{Opts as FinderOpts, SuggestionType};
|
||||||
use crate::finder::Finder;
|
use crate::prelude::*;
|
||||||
use crate::fs;
|
use crate::serializer;
|
||||||
use crate::shell;
|
|
||||||
use crate::shell::ShellSpawnError;
|
|
||||||
use crate::structures::cheat::{Suggestion, VariableMap};
|
use crate::structures::cheat::{Suggestion, VariableMap};
|
||||||
use crate::writer;
|
|
||||||
use anyhow::Context;
|
|
||||||
use anyhow::Result;
|
|
||||||
use shell::EOF;
|
use shell::EOF;
|
||||||
use std::io::Write;
|
|
||||||
use std::path::Path;
|
|
||||||
use std::process::Stdio;
|
use std::process::Stdio;
|
||||||
|
|
||||||
fn prompt_finder(
|
fn prompt_finder(
|
||||||
|
@ -128,13 +123,13 @@ fn prompt_finder(
|
||||||
opts.suggestion_type = SuggestionType::Disabled;
|
opts.suggestion_type = SuggestionType::Disabled;
|
||||||
};
|
};
|
||||||
|
|
||||||
let (output, _, _) = CONFIG
|
let (output, _) = CONFIG
|
||||||
.finder()
|
.finder()
|
||||||
.call(opts, |stdin, _| {
|
.call(opts, |stdin| {
|
||||||
stdin
|
stdin
|
||||||
.write_all(suggestions.as_bytes())
|
.write_all(suggestions.as_bytes())
|
||||||
.context("Could not write to finder's stdin")?;
|
.context("Could not write to finder's stdin")?;
|
||||||
Ok(None)
|
Ok(())
|
||||||
})
|
})
|
||||||
.context("finder was unable to prompt with suggestions")?;
|
.context("finder was unable to prompt with suggestions")?;
|
||||||
|
|
||||||
|
@ -150,7 +145,10 @@ fn unique_result_count(results: &[&str]) -> usize {
|
||||||
|
|
||||||
fn replace_variables_from_snippet(snippet: &str, tags: &str, variables: VariableMap) -> Result<String> {
|
fn replace_variables_from_snippet(snippet: &str, tags: &str, variables: VariableMap) -> Result<String> {
|
||||||
let mut interpolated_snippet = String::from(snippet);
|
let mut interpolated_snippet = String::from(snippet);
|
||||||
let variables_found: Vec<&str> = writer::VAR_REGEX.find_iter(snippet).map(|m| m.as_str()).collect();
|
let variables_found: Vec<&str> = serializer::VAR_REGEX
|
||||||
|
.find_iter(snippet)
|
||||||
|
.map(|m| m.as_str())
|
||||||
|
.collect();
|
||||||
let variable_count = unique_result_count(&variables_found);
|
let variable_count = unique_result_count(&variables_found);
|
||||||
|
|
||||||
for bracketed_variable_name in variables_found {
|
for bracketed_variable_name in variables_found {
|
||||||
|
@ -213,7 +211,7 @@ pub fn act(
|
||||||
)
|
)
|
||||||
.context("Failed to replace variables from snippet")?;
|
.context("Failed to replace variables from snippet")?;
|
||||||
s = with_absolute_path(s);
|
s = with_absolute_path(s);
|
||||||
s = writer::with_new_lines(s);
|
s = serializer::with_new_lines(s);
|
||||||
s
|
s
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
use crate::writer;
|
use crate::prelude::*;
|
||||||
|
use crate::serializer;
|
||||||
use anyhow::Context;
|
|
||||||
use anyhow::Result;
|
|
||||||
|
|
||||||
pub type Output<'a> = (&'a str, &'a str, &'a str, &'a str, Option<usize>);
|
pub type Output<'a> = (&'a str, &'a str, &'a str, &'a str, Option<usize>);
|
||||||
|
|
||||||
|
@ -18,7 +16,7 @@ pub fn extract_from_selections(raw_snippet: &str, is_single: bool) -> Result<Out
|
||||||
let mut parts = lines
|
let mut parts = lines
|
||||||
.next()
|
.next()
|
||||||
.context("No more parts in `selections`")?
|
.context("No more parts in `selections`")?
|
||||||
.split(writer::DELIMITER)
|
.split(serializer::DELIMITER)
|
||||||
.skip(3);
|
.skip(3);
|
||||||
|
|
||||||
let tags = parts.next().unwrap_or("");
|
let tags = parts.next().unwrap_or("");
|
41
src/commands/core/mod.rs
Normal file
41
src/commands/core/mod.rs
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
mod actor;
|
||||||
|
mod extractor;
|
||||||
|
|
||||||
|
use crate::finder::structures::Opts as FinderOpts;
|
||||||
|
use crate::parser::Parser;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use crate::welcome;
|
||||||
|
|
||||||
|
pub fn main() -> Result<()> {
|
||||||
|
let config = &CONFIG;
|
||||||
|
let opts = FinderOpts::snippet_default();
|
||||||
|
|
||||||
|
let (raw_selection, (variables, files)) = config
|
||||||
|
.finder()
|
||||||
|
.call(opts, |writer| {
|
||||||
|
let fetcher = config.fetcher();
|
||||||
|
|
||||||
|
let mut parser = Parser::new(writer, true);
|
||||||
|
|
||||||
|
let found_something = fetcher
|
||||||
|
.fetch(&mut parser)
|
||||||
|
.context("Failed to parse variables intended for finder")?;
|
||||||
|
|
||||||
|
if !found_something {
|
||||||
|
welcome::populate_cheatsheet(&mut parser)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((Some(parser.variables), fetcher.files()))
|
||||||
|
})
|
||||||
|
.context("Failed getting selection and variables from finder")?;
|
||||||
|
|
||||||
|
let extractions = extractor::extract_from_selections(&raw_selection, config.best_match());
|
||||||
|
|
||||||
|
if extractions.is_err() {
|
||||||
|
return main();
|
||||||
|
}
|
||||||
|
|
||||||
|
actor::act(extractions, files, variables)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -1,8 +1,7 @@
|
||||||
use crate::shell::{self, ShellSpawnError};
|
use crate::common::shell::{self, ShellSpawnError};
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
use anyhow::Result;
|
pub fn expand() -> Result<()> {
|
||||||
|
|
||||||
pub fn map_expand() -> Result<()> {
|
|
||||||
let cmd = r#"sed -e 's/^.*$/"&"/' | tr '\n' ' '"#;
|
let cmd = r#"sed -e 's/^.*$/"&"/' | tr '\n' ' '"#;
|
||||||
shell::out()
|
shell::out()
|
||||||
.arg(cmd)
|
.arg(cmd)
|
65
src/commands/func/mod.rs
Normal file
65
src/commands/func/mod.rs
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
mod map;
|
||||||
|
mod widget;
|
||||||
|
|
||||||
|
use super::core;
|
||||||
|
use super::temp;
|
||||||
|
use crate::common::url;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use clap::Args;
|
||||||
|
use clap::Parser;
|
||||||
|
|
||||||
|
const FUNC_POSSIBLE_VALUES: &[&str] = &[
|
||||||
|
"url::open",
|
||||||
|
"welcome",
|
||||||
|
"widget::last_command",
|
||||||
|
"map::expand",
|
||||||
|
"temp",
|
||||||
|
];
|
||||||
|
|
||||||
|
impl FromStr for Func {
|
||||||
|
type Err = &'static str;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
match s {
|
||||||
|
"url::open" => Ok(Func::UrlOpen),
|
||||||
|
"welcome" => Ok(Func::Welcome),
|
||||||
|
"widget::last_command" => Ok(Func::WidgetLastCommand),
|
||||||
|
"map::expand" => Ok(Func::MapExpand),
|
||||||
|
"temp" => Ok(Func::Temp),
|
||||||
|
_ => Err("no match"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Parser)]
|
||||||
|
pub enum Func {
|
||||||
|
UrlOpen,
|
||||||
|
Welcome,
|
||||||
|
WidgetLastCommand,
|
||||||
|
MapExpand,
|
||||||
|
Temp,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Args)]
|
||||||
|
pub struct Input {
|
||||||
|
/// Function name (example: "url::open")
|
||||||
|
#[clap(possible_values = FUNC_POSSIBLE_VALUES, ignore_case = true)]
|
||||||
|
pub func: Func,
|
||||||
|
/// List of arguments (example: "https://google.com")
|
||||||
|
pub args: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Runnable for Input {
|
||||||
|
fn run(&self) -> Result<()> {
|
||||||
|
let func = &self.func;
|
||||||
|
let args = self.args.clone(); // TODO
|
||||||
|
|
||||||
|
match func {
|
||||||
|
Func::UrlOpen => url::open(args),
|
||||||
|
Func::Welcome => core::main(),
|
||||||
|
Func::WidgetLastCommand => widget::last_command(),
|
||||||
|
Func::MapExpand => map::expand(),
|
||||||
|
Func::Temp => temp::main(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
40
src/commands/func/widget.rs
Normal file
40
src/commands/func/widget.rs
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
use crate::prelude::*;
|
||||||
|
use std::io::{self, Read};
|
||||||
|
|
||||||
|
pub fn last_command() -> Result<()> {
|
||||||
|
let mut text = String::new();
|
||||||
|
io::stdin().read_to_string(&mut text)?;
|
||||||
|
|
||||||
|
let replacements = vec![("||", "ග"), ("|", "ඛ"), ("&&", "ඝ")];
|
||||||
|
|
||||||
|
let parts = shellwords::split(&text).unwrap_or_else(|_| text.split('|').map(|s| s.to_string()).collect());
|
||||||
|
|
||||||
|
for p in parts {
|
||||||
|
for (pattern, escaped) in replacements.clone() {
|
||||||
|
if p.contains(pattern) && p != pattern && p != format!("{}{}", pattern, pattern) {
|
||||||
|
let replacement = p.replace(pattern, escaped);
|
||||||
|
text = text.replace(&p, &replacement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut extracted = text.clone();
|
||||||
|
|
||||||
|
for (pattern, _) in replacements.clone() {
|
||||||
|
let mut new_parts = text.rsplit(pattern);
|
||||||
|
if let Some(extracted_attempt) = new_parts.next() {
|
||||||
|
if extracted_attempt.len() <= extracted.len() {
|
||||||
|
extracted = extracted_attempt.to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (pattern, escaped) in replacements.clone() {
|
||||||
|
text = text.replace(&escaped, pattern);
|
||||||
|
extracted = extracted.replace(&escaped, pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("{}", extracted.trim_start());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
48
src/commands/info.rs
Normal file
48
src/commands/info.rs
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
use clap::Args;
|
||||||
|
|
||||||
|
use crate::filesystem;
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
const INFO_POSSIBLE_VALUES: &[&str] = &["cheats-example", "cheats-path", "config-path", "config-example"];
|
||||||
|
|
||||||
|
impl FromStr for Info {
|
||||||
|
type Err = &'static str;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
match s {
|
||||||
|
"cheats-example" => Ok(Info::CheatsExample),
|
||||||
|
"cheats-path" => Ok(Info::CheatsPath),
|
||||||
|
"config-example" => Ok(Info::ConfigExample),
|
||||||
|
"config-path" => Ok(Info::ConfigPath),
|
||||||
|
_ => Err("no match"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Args)]
|
||||||
|
pub struct Input {
|
||||||
|
#[clap(possible_values = INFO_POSSIBLE_VALUES, ignore_case = true)]
|
||||||
|
pub info: Info,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Info {
|
||||||
|
CheatsExample,
|
||||||
|
CheatsPath,
|
||||||
|
ConfigPath,
|
||||||
|
ConfigExample,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Runnable for Input {
|
||||||
|
fn run(&self) -> Result<()> {
|
||||||
|
let info = &self.info;
|
||||||
|
|
||||||
|
match info {
|
||||||
|
Info::CheatsExample => println!("{}", include_str!("../../docs/cheat_example.cheat")),
|
||||||
|
Info::CheatsPath => println!("{}", &filesystem::default_cheat_pathbuf()?.to_string()),
|
||||||
|
Info::ConfigPath => println!("{}", &filesystem::default_config_pathbuf()?.to_string()),
|
||||||
|
Info::ConfigExample => println!("{}", include_str!("../../docs/config_file_example.yaml")),
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
39
src/commands/mod.rs
Normal file
39
src/commands/mod.rs
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
pub mod core;
|
||||||
|
pub mod func;
|
||||||
|
pub mod info;
|
||||||
|
pub mod preview;
|
||||||
|
pub mod repo;
|
||||||
|
pub mod shell;
|
||||||
|
pub mod temp;
|
||||||
|
|
||||||
|
use crate::commands;
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
pub fn handle() -> Result<()> {
|
||||||
|
use crate::config::Command::*;
|
||||||
|
|
||||||
|
match CONFIG.cmd() {
|
||||||
|
None => commands::core::main(),
|
||||||
|
|
||||||
|
Some(c) => match c {
|
||||||
|
Preview(input) => input.run(),
|
||||||
|
|
||||||
|
PreviewVarStdin(input) => input.run(),
|
||||||
|
|
||||||
|
PreviewVar(input) => input.run(),
|
||||||
|
|
||||||
|
Widget(input) => input.run().context("Failed to print shell widget code"),
|
||||||
|
|
||||||
|
Fn(input) => input
|
||||||
|
.run()
|
||||||
|
.with_context(|| format!("Failed to execute function `{:#?}`", input.func)),
|
||||||
|
|
||||||
|
Info(input) => input
|
||||||
|
.run()
|
||||||
|
.with_context(|| format!("Failed to fetch info `{:#?}`", input.info)),
|
||||||
|
|
||||||
|
#[cfg(not(feature = "disable-repo-management"))]
|
||||||
|
Repo(input) => input.run(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
39
src/commands/preview/mod.rs
Normal file
39
src/commands/preview/mod.rs
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
use crate::prelude::*;
|
||||||
|
use crate::serializer;
|
||||||
|
use clap::Args;
|
||||||
|
use crossterm::style::{style, Stylize};
|
||||||
|
use std::process;
|
||||||
|
|
||||||
|
pub mod var;
|
||||||
|
pub mod var_stdin;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Args)]
|
||||||
|
pub struct Input {
|
||||||
|
/// Selection line
|
||||||
|
pub line: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_elements(argstr: &str) -> Result<(&str, &str, &str)> {
|
||||||
|
let mut parts = argstr.split(serializer::DELIMITER).skip(3);
|
||||||
|
let tags = parts.next().context("No `tags` element provided.")?;
|
||||||
|
let comment = parts.next().context("No `comment` element provided.")?;
|
||||||
|
let snippet = parts.next().context("No `snippet` element provided.")?;
|
||||||
|
Ok((tags, comment, snippet))
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Runnable for Input {
|
||||||
|
fn run(&self) -> Result<()> {
|
||||||
|
let line = &self.line;
|
||||||
|
|
||||||
|
let (tags, comment, snippet) = extract_elements(line)?;
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"{comment} {tags} \n{snippet}",
|
||||||
|
comment = style(comment).with(CONFIG.comment_color()),
|
||||||
|
tags = style(format!("[{}]", tags)).with(CONFIG.tag_color()),
|
||||||
|
snippet = style(serializer::fix_newlines(snippet)).with(CONFIG.snippet_color()),
|
||||||
|
);
|
||||||
|
|
||||||
|
process::exit(0)
|
||||||
|
}
|
||||||
|
}
|
106
src/commands/preview/var.rs
Normal file
106
src/commands/preview/var.rs
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
use crate::env_var;
|
||||||
|
use crate::finder;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use crate::serializer;
|
||||||
|
use clap::Args;
|
||||||
|
use crossterm::style::style;
|
||||||
|
use crossterm::style::Stylize;
|
||||||
|
use std::iter;
|
||||||
|
use std::process;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Args)]
|
||||||
|
pub struct Input {
|
||||||
|
/// Selection line
|
||||||
|
pub selection: String,
|
||||||
|
/// Query match
|
||||||
|
pub query: String,
|
||||||
|
/// Typed text
|
||||||
|
pub variable: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Runnable for Input {
|
||||||
|
fn run(&self) -> Result<()> {
|
||||||
|
let selection = &self.selection;
|
||||||
|
let query = &self.query;
|
||||||
|
let variable = &self.variable;
|
||||||
|
|
||||||
|
let snippet = env_var::must_get(env_var::PREVIEW_INITIAL_SNIPPET);
|
||||||
|
let tags = env_var::must_get(env_var::PREVIEW_TAGS);
|
||||||
|
let comment = env_var::must_get(env_var::PREVIEW_COMMENT);
|
||||||
|
let column = env_var::parse(env_var::PREVIEW_COLUMN);
|
||||||
|
let delimiter = env_var::get(env_var::PREVIEW_DELIMITER).ok();
|
||||||
|
let map = env_var::get(env_var::PREVIEW_MAP).ok();
|
||||||
|
|
||||||
|
let active_color = CONFIG.tag_color();
|
||||||
|
let inactive_color = CONFIG.comment_color();
|
||||||
|
|
||||||
|
let mut colored_snippet = String::from(&snippet);
|
||||||
|
let mut visited_vars: HashSet<&str> = HashSet::new();
|
||||||
|
|
||||||
|
let mut variables = String::from("");
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"{comment} {tags}",
|
||||||
|
comment = style(comment).with(CONFIG.comment_color()),
|
||||||
|
tags = style(format!("[{}]", tags)).with(CONFIG.tag_color()),
|
||||||
|
);
|
||||||
|
|
||||||
|
let bracketed_current_variable = format!("<{}>", variable);
|
||||||
|
|
||||||
|
let bracketed_variables: Vec<&str> = {
|
||||||
|
if snippet.contains(&bracketed_current_variable) {
|
||||||
|
serializer::VAR_REGEX
|
||||||
|
.find_iter(&snippet)
|
||||||
|
.map(|m| m.as_str())
|
||||||
|
.collect()
|
||||||
|
} else {
|
||||||
|
iter::once(&bracketed_current_variable)
|
||||||
|
.map(|s| s.as_str())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for bracketed_variable_name in bracketed_variables {
|
||||||
|
let variable_name = &bracketed_variable_name[1..bracketed_variable_name.len() - 1];
|
||||||
|
|
||||||
|
if visited_vars.contains(variable_name) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
visited_vars.insert(variable_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
let is_current = variable_name == variable;
|
||||||
|
let variable_color = if is_current { active_color } else { inactive_color };
|
||||||
|
let env_variable_name = env_var::escape(variable_name);
|
||||||
|
|
||||||
|
let value = if is_current {
|
||||||
|
let v = selection.trim_matches('\'');
|
||||||
|
if v.is_empty() { query.trim_matches('\'') } else { v }.to_string()
|
||||||
|
} else if let Ok(v) = env_var::get(&env_variable_name) {
|
||||||
|
v
|
||||||
|
} else {
|
||||||
|
"".to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
let replacement = format!(
|
||||||
|
"{variable}",
|
||||||
|
variable = style(bracketed_variable_name).with(variable_color),
|
||||||
|
);
|
||||||
|
|
||||||
|
colored_snippet = colored_snippet.replace(bracketed_variable_name, &replacement);
|
||||||
|
|
||||||
|
variables = format!(
|
||||||
|
"{variables}\n{variable} = {value}",
|
||||||
|
variables = variables,
|
||||||
|
variable = style(variable_name).with(variable_color),
|
||||||
|
value = finder::process(value, column, delimiter.as_deref(), map.clone())
|
||||||
|
.expect("Unable to process value"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("{snippet}", snippet = serializer::fix_newlines(&colored_snippet));
|
||||||
|
println!("{variables}", variables = variables);
|
||||||
|
|
||||||
|
process::exit(0)
|
||||||
|
}
|
||||||
|
}
|
43
src/commands/preview/var_stdin.rs
Executable file
43
src/commands/preview/var_stdin.rs
Executable file
|
@ -0,0 +1,43 @@
|
||||||
|
use clap::Args;
|
||||||
|
|
||||||
|
use super::var;
|
||||||
|
use crate::common::shell::{self, ShellSpawnError, EOF};
|
||||||
|
use crate::prelude::*;
|
||||||
|
use std::io::{self, Read};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Args)]
|
||||||
|
pub struct Input {}
|
||||||
|
|
||||||
|
impl Runnable for Input {
|
||||||
|
fn run(&self) -> Result<()> {
|
||||||
|
let mut text = String::new();
|
||||||
|
io::stdin().read_to_string(&mut text)?;
|
||||||
|
|
||||||
|
let mut parts = text.split(EOF);
|
||||||
|
let selection = parts.next().expect("Unable to get selection").to_owned();
|
||||||
|
let query = parts.next().expect("Unable to get query").to_owned();
|
||||||
|
let variable = parts.next().expect("Unable to get variable").trim().to_owned();
|
||||||
|
|
||||||
|
let input = var::Input {
|
||||||
|
selection,
|
||||||
|
query,
|
||||||
|
variable,
|
||||||
|
};
|
||||||
|
|
||||||
|
input.run()?;
|
||||||
|
|
||||||
|
if let Some(extra) = parts.next() {
|
||||||
|
if !extra.is_empty() {
|
||||||
|
print!("");
|
||||||
|
|
||||||
|
shell::out()
|
||||||
|
.arg(extra)
|
||||||
|
.spawn()
|
||||||
|
.map_err(|e| ShellSpawnError::new(extra, e))?
|
||||||
|
.wait()?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,13 +1,9 @@
|
||||||
use crate::config::CONFIG;
|
use crate::common::git;
|
||||||
use crate::filesystem;
|
use crate::filesystem;
|
||||||
use crate::finder::structures::{Opts as FinderOpts, SuggestionType};
|
use crate::finder::structures::{Opts as FinderOpts, SuggestionType};
|
||||||
use crate::finder::{Finder, FinderChoice};
|
use crate::finder::FinderChoice;
|
||||||
use crate::fs::pathbuf_to_string;
|
use crate::prelude::*;
|
||||||
use crate::git;
|
|
||||||
use anyhow::Context;
|
|
||||||
use anyhow::Result;
|
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io::Write;
|
|
||||||
use std::path;
|
use std::path;
|
||||||
|
|
||||||
fn ask_if_should_import_all(finder: &FinderChoice) -> Result<bool> {
|
fn ask_if_should_import_all(finder: &FinderChoice) -> Result<bool> {
|
||||||
|
@ -17,20 +13,16 @@ fn ask_if_should_import_all(finder: &FinderChoice) -> Result<bool> {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (response, _, _) = finder
|
let (response, _) = finder
|
||||||
.call(opts, |stdin, _| {
|
.call(opts, |stdin| {
|
||||||
stdin
|
stdin
|
||||||
.write_all(b"Yes\nNo")
|
.write_all(b"Yes\nNo")
|
||||||
.context("Unable to writer alternatives")?;
|
.context("Unable to writer alternatives")?;
|
||||||
Ok(None)
|
Ok(())
|
||||||
})
|
})
|
||||||
.context("Unable to get response")?;
|
.context("Unable to get response")?;
|
||||||
|
|
||||||
if response.to_lowercase().starts_with('y') {
|
Ok(response.to_lowercase().starts_with('y'))
|
||||||
Ok(true)
|
|
||||||
} else {
|
|
||||||
Ok(false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn main(uri: String) -> Result<()> {
|
pub fn main(uri: String) -> Result<()> {
|
||||||
|
@ -41,14 +33,14 @@ pub fn main(uri: String) -> Result<()> {
|
||||||
|
|
||||||
let cheat_pathbuf = filesystem::default_cheat_pathbuf()?;
|
let cheat_pathbuf = filesystem::default_cheat_pathbuf()?;
|
||||||
let tmp_pathbuf = filesystem::tmp_pathbuf()?;
|
let tmp_pathbuf = filesystem::tmp_pathbuf()?;
|
||||||
let tmp_path_str = pathbuf_to_string(&tmp_pathbuf)?;
|
let tmp_path_str = &tmp_pathbuf.to_string();
|
||||||
|
|
||||||
let _ = filesystem::remove_dir(&tmp_pathbuf);
|
let _ = filesystem::remove_dir(&tmp_pathbuf);
|
||||||
filesystem::create_dir(&tmp_pathbuf)?;
|
filesystem::create_dir(&tmp_pathbuf)?;
|
||||||
|
|
||||||
eprintln!("Cloning {} into {}...\n", &actual_uri, &tmp_path_str);
|
eprintln!("Cloning {} into {}...\n", &actual_uri, &tmp_path_str);
|
||||||
|
|
||||||
git::shallow_clone(actual_uri.as_str(), &tmp_path_str)
|
git::shallow_clone(actual_uri.as_str(), tmp_path_str)
|
||||||
.with_context(|| format!("Failed to clone `{}`", actual_uri))?;
|
.with_context(|| format!("Failed to clone `{}`", actual_uri))?;
|
||||||
|
|
||||||
let all_files = filesystem::all_cheat_files(&tmp_pathbuf).join("\n");
|
let all_files = filesystem::all_cheat_files(&tmp_pathbuf).join("\n");
|
||||||
|
@ -64,12 +56,12 @@ pub fn main(uri: String) -> Result<()> {
|
||||||
let files = if should_import_all {
|
let files = if should_import_all {
|
||||||
all_files
|
all_files
|
||||||
} else {
|
} else {
|
||||||
let (files, _, _) = finder
|
let (files, _) = finder
|
||||||
.call(opts, |stdin, _| {
|
.call(opts, |stdin| {
|
||||||
stdin
|
stdin
|
||||||
.write_all(all_files.as_bytes())
|
.write_all(all_files.as_bytes())
|
||||||
.context("Unable to prompt cheats to import")?;
|
.context("Unable to prompt cheats to import")?;
|
||||||
Ok(None)
|
Ok(())
|
||||||
})
|
})
|
||||||
.context("Failed to get cheatsheet files from finder")?;
|
.context("Failed to get cheatsheet files from finder")?;
|
||||||
files
|
files
|
||||||
|
@ -96,13 +88,8 @@ pub fn main(uri: String) -> Result<()> {
|
||||||
p
|
p
|
||||||
};
|
};
|
||||||
fs::create_dir_all(&to_folder).unwrap_or(());
|
fs::create_dir_all(&to_folder).unwrap_or(());
|
||||||
fs::copy(&from, &to).with_context(|| {
|
fs::copy(&from, &to)
|
||||||
format!(
|
.with_context(|| format!("Failed to copy `{}` to `{}`", &from.to_string(), &to.to_string()))?;
|
||||||
"Failed to copy `{}` to `{}`",
|
|
||||||
pathbuf_to_string(&from).expect("unable to parse {from}"),
|
|
||||||
pathbuf_to_string(&to).expect("unable to parse {to}")
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
filesystem::remove_dir(&tmp_pathbuf)?;
|
filesystem::remove_dir(&tmp_pathbuf)?;
|
||||||
|
@ -110,7 +97,7 @@ pub fn main(uri: String) -> Result<()> {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"The following .cheat files were imported successfully:\n{}\n\nThey are now located at {}",
|
"The following .cheat files were imported successfully:\n{}\n\nThey are now located at {}",
|
||||||
files,
|
files,
|
||||||
pathbuf_to_string(&to_folder)?
|
to_folder.to_string()
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
|
@ -1,13 +1,9 @@
|
||||||
use crate::config::CONFIG;
|
|
||||||
use crate::filesystem;
|
use crate::filesystem;
|
||||||
use crate::finder::structures::{Opts as FinderOpts, SuggestionType};
|
use crate::finder::structures::{Opts as FinderOpts, SuggestionType};
|
||||||
use crate::finder::Finder;
|
|
||||||
use crate::fs::pathbuf_to_string;
|
use crate::common::git;
|
||||||
use crate::git;
|
use crate::prelude::*;
|
||||||
use anyhow::Context;
|
|
||||||
use anyhow::Result;
|
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io::Write;
|
|
||||||
|
|
||||||
pub fn main() -> Result<String> {
|
pub fn main() -> Result<String> {
|
||||||
let finder = CONFIG.finder();
|
let finder = CONFIG.finder();
|
||||||
|
@ -18,13 +14,13 @@ pub fn main() -> Result<String> {
|
||||||
p
|
p
|
||||||
};
|
};
|
||||||
|
|
||||||
let repo_path_str = pathbuf_to_string(&repo_pathbuf)?;
|
let repo_path_str = &repo_pathbuf.to_string();
|
||||||
|
|
||||||
let _ = filesystem::remove_dir(&repo_pathbuf);
|
let _ = filesystem::remove_dir(&repo_pathbuf);
|
||||||
filesystem::create_dir(&repo_pathbuf)?;
|
filesystem::create_dir(&repo_pathbuf)?;
|
||||||
|
|
||||||
let (repo_url, _, _) = git::meta("denisidoro/cheats");
|
let (repo_url, _, _) = git::meta("denisidoro/cheats");
|
||||||
git::shallow_clone(repo_url.as_str(), &repo_path_str)
|
git::shallow_clone(repo_url.as_str(), repo_path_str)
|
||||||
.with_context(|| format!("Failed to clone `{}`", repo_url))?;
|
.with_context(|| format!("Failed to clone `{}`", repo_url))?;
|
||||||
|
|
||||||
let feature_repos_file = {
|
let feature_repos_file = {
|
||||||
|
@ -41,12 +37,12 @@ pub fn main() -> Result<String> {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let (repo, _, _) = finder
|
let (repo, _) = finder
|
||||||
.call(opts, |stdin, _| {
|
.call(opts, |stdin| {
|
||||||
stdin
|
stdin
|
||||||
.write_all(repos.as_bytes())
|
.write_all(repos.as_bytes())
|
||||||
.context("Unable to prompt featured repositories")?;
|
.context("Unable to prompt featured repositories")?;
|
||||||
Ok(None)
|
Ok(())
|
||||||
})
|
})
|
||||||
.context("Failed to get repo URL from finder")?;
|
.context("Failed to get repo URL from finder")?;
|
||||||
|
|
41
src/commands/repo/mod.rs
Normal file
41
src/commands/repo/mod.rs
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
use crate::commands;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use clap::{Args, Subcommand};
|
||||||
|
|
||||||
|
pub mod add;
|
||||||
|
pub mod browse;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Subcommand)]
|
||||||
|
pub enum RepoCommand {
|
||||||
|
/// Imports cheatsheets from a repo
|
||||||
|
Add {
|
||||||
|
/// A URI to a git repository containing .cheat files ("user/repo" will download cheats from github.com/user/repo)
|
||||||
|
uri: String,
|
||||||
|
},
|
||||||
|
/// Browses for featured cheatsheet repos
|
||||||
|
Browse,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Args)]
|
||||||
|
pub struct Input {
|
||||||
|
#[clap(subcommand)]
|
||||||
|
pub cmd: RepoCommand,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Runnable for Input {
|
||||||
|
fn run(&self) -> Result<()> {
|
||||||
|
match &self.cmd {
|
||||||
|
RepoCommand::Add { uri } => {
|
||||||
|
add::main(uri.clone())
|
||||||
|
.with_context(|| format!("Failed to import cheatsheets from `{}`", uri))?;
|
||||||
|
commands::core::main()
|
||||||
|
}
|
||||||
|
RepoCommand::Browse => {
|
||||||
|
let repo = browse::main().context("Failed to browse featured cheatsheets")?;
|
||||||
|
add::main(repo.clone())
|
||||||
|
.with_context(|| format!("Failed to import cheatsheets from `{}`", repo))?;
|
||||||
|
commands::core::main()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
43
src/commands/shell.rs
Normal file
43
src/commands/shell.rs
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
use clap::Args;
|
||||||
|
|
||||||
|
use crate::common::shell::Shell;
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
const WIDGET_POSSIBLE_VALUES: &[&str] = &["bash", "zsh", "fish", "elvish"];
|
||||||
|
|
||||||
|
impl FromStr for Shell {
|
||||||
|
type Err = &'static str;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
match s {
|
||||||
|
"bash" => Ok(Shell::Bash),
|
||||||
|
"zsh" => Ok(Shell::Zsh),
|
||||||
|
"fish" => Ok(Shell::Fish),
|
||||||
|
"elvish" => Ok(Shell::Elvish),
|
||||||
|
_ => Err("no match"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Args)]
|
||||||
|
pub struct Input {
|
||||||
|
#[clap(possible_values = WIDGET_POSSIBLE_VALUES, ignore_case = true, default_value = "bash")]
|
||||||
|
pub shell: Shell,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Runnable for Input {
|
||||||
|
fn run(&self) -> Result<()> {
|
||||||
|
let shell = &self.shell;
|
||||||
|
|
||||||
|
let content = match shell {
|
||||||
|
Shell::Bash => include_str!("../../shell/navi.plugin.bash"),
|
||||||
|
Shell::Zsh => include_str!("../../shell/navi.plugin.zsh"),
|
||||||
|
Shell::Fish => include_str!("../../shell/navi.plugin.fish"),
|
||||||
|
Shell::Elvish => include_str!("../../shell/navi.plugin.elv"),
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("{}", content);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
64
src/commands/temp.rs
Normal file
64
src/commands/temp.rs
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
use crate::common::shell::{self, ShellSpawnError};
|
||||||
|
use crate::finder::structures::Opts as FinderOpts;
|
||||||
|
use crate::parser::Parser;
|
||||||
|
use crate::{prelude::*, serializer};
|
||||||
|
use std::io::{self, Write};
|
||||||
|
|
||||||
|
pub fn main() -> Result<()> {
|
||||||
|
let config = &CONFIG;
|
||||||
|
let _opts = FinderOpts::snippet_default();
|
||||||
|
|
||||||
|
let fetcher = config.fetcher();
|
||||||
|
let hash: u64 = 2087294461664323320;
|
||||||
|
|
||||||
|
let mut buf = vec![];
|
||||||
|
let mut parser = Parser::new(&mut buf, false);
|
||||||
|
parser.set_hash(hash);
|
||||||
|
|
||||||
|
let _res = fetcher
|
||||||
|
.fetch(&mut parser)
|
||||||
|
.context("Failed to parse variables intended for finder")?;
|
||||||
|
|
||||||
|
let variables = parser.variables;
|
||||||
|
let item_str = String::from_utf8(buf)?;
|
||||||
|
let item = serializer::raycast_deser(&item_str)?;
|
||||||
|
dbg!(&item);
|
||||||
|
|
||||||
|
let x = variables.get_suggestion(&item.tags, "local_branch").expect("foo");
|
||||||
|
dbg!(&x);
|
||||||
|
|
||||||
|
let suggestion_command = x.0.clone();
|
||||||
|
let child = shell::out()
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.arg(&suggestion_command)
|
||||||
|
.spawn()
|
||||||
|
.map_err(|e| ShellSpawnError::new(suggestion_command, e))?;
|
||||||
|
|
||||||
|
let text = String::from_utf8(
|
||||||
|
child
|
||||||
|
.wait_with_output()
|
||||||
|
.context("Failed to wait and collect output from bash")?
|
||||||
|
.stdout,
|
||||||
|
)
|
||||||
|
.context("Suggestions are invalid utf8")?;
|
||||||
|
|
||||||
|
dbg!(&text);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn _main0() -> Result<()> {
|
||||||
|
let config = &CONFIG;
|
||||||
|
|
||||||
|
let fetcher = config.fetcher();
|
||||||
|
|
||||||
|
let mut stdout = io::stdout();
|
||||||
|
let mut writer: Box<&mut dyn Write> = Box::new(&mut stdout);
|
||||||
|
let mut parser = Parser::new(&mut writer, false);
|
||||||
|
|
||||||
|
let _res = fetcher
|
||||||
|
.fetch(&mut parser)
|
||||||
|
.context("Failed to parse variables intended for finder")?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::shell::{self, ShellSpawnError, EOF};
|
use crate::common::shell::{self, ShellSpawnError, EOF};
|
||||||
use anyhow::Result;
|
use crate::prelude::*;
|
||||||
|
|
||||||
pub fn copy(text: String) -> Result<()> {
|
pub fn copy(text: String) -> Result<()> {
|
||||||
let cmd = r#"
|
let cmd = r#"
|
21
src/common/component.rs.bkp
Normal file
21
src/common/component.rs.bkp
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
use super::prelude::*;
|
||||||
|
|
||||||
|
pub trait Component: Any + AsAny + Send + Sync {}
|
||||||
|
|
||||||
|
pub trait AsAny: Any {
|
||||||
|
fn as_any(&self) -> &dyn Any;
|
||||||
|
fn as_mut_any(&mut self) -> &mut dyn Any;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> AsAny for T
|
||||||
|
where
|
||||||
|
T: Any,
|
||||||
|
{
|
||||||
|
fn as_any(&self) -> &dyn Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_mut_any(&mut self) -> &mut dyn Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
7
src/common/deps.rs.bkp
Normal file
7
src/common/deps.rs.bkp
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
use super::prelude::*;
|
||||||
|
|
||||||
|
pub trait HasDeps {
|
||||||
|
fn deps(&self) -> HashSet<TypeId> {
|
||||||
|
HashSet::new()
|
||||||
|
}
|
||||||
|
}
|
50
src/common/deser.rs.bkp
Normal file
50
src/common/deser.rs.bkp
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
use super::prelude::*;
|
||||||
|
use serde::de::DeserializeOwned;
|
||||||
|
|
||||||
|
#[cfg(feature = "yaml")]
|
||||||
|
pub fn yaml_from_path<T>(path: &Path) -> Result<T>
|
||||||
|
where
|
||||||
|
T: DeserializeOwned,
|
||||||
|
{
|
||||||
|
let file = File::open(path)?;
|
||||||
|
let reader = BufReader::new(file);
|
||||||
|
let config: T = serde_yaml::from_reader(reader)?; // TODO: show path + original error message?
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "json")]
|
||||||
|
pub fn json_from_path<T>(path: &Path) -> Result<T>
|
||||||
|
where
|
||||||
|
T: DeserializeOwned,
|
||||||
|
{
|
||||||
|
let file = File::open(path)?;
|
||||||
|
let reader = BufReader::new(file);
|
||||||
|
let config: T = serde_json::from_reader(reader)?; // TODO: show path + original error message?
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "yaml")]
|
||||||
|
pub fn yaml_from_str<T>(text: &str) -> Result<T>
|
||||||
|
where
|
||||||
|
T: DeserializeOwned,
|
||||||
|
{
|
||||||
|
serde_yaml::from_str(text)
|
||||||
|
.with_context(|| format!("Failed to deserialize into yaml:\n{}", text))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "json")]
|
||||||
|
pub fn json_from_str<T>(text: &str) -> Result<T>
|
||||||
|
where
|
||||||
|
T: DeserializeOwned,
|
||||||
|
{
|
||||||
|
serde_json::from_str(text)
|
||||||
|
.with_context(|| format!("Failed to deserialize into json:\n{}", text))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "yaml")]
|
||||||
|
pub fn to_yaml_str<T>(t: &T) -> Result<String>
|
||||||
|
where
|
||||||
|
T: Serialize,
|
||||||
|
{
|
||||||
|
serde_yaml::to_string(t).with_context(|| "Failed to serialize into yaml") // TODO: debug struct?
|
||||||
|
}
|
|
@ -1,12 +1,34 @@
|
||||||
use anyhow::{Context, Error, Result};
|
use super::prelude::*;
|
||||||
|
|
||||||
use remove_dir_all::remove_dir_all;
|
use remove_dir_all::remove_dir_all;
|
||||||
use std::fmt::Debug;
|
use std::ffi::OsStr;
|
||||||
use std::fs::{self, create_dir_all, File};
|
use std::fs::{self, create_dir_all, File};
|
||||||
use std::io::{self, BufRead};
|
use std::io;
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
pub trait ToStringExt {
|
||||||
|
fn to_string(&self) -> String;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToStringExt for Path {
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
self.to_string_lossy().to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToStringExt for OsStr {
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
self.to_string_lossy().to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pub fn config_dir(project_name: &str) -> Result<PathBuf> {
|
||||||
|
// let base_dirs = BaseDirs::new().context("unable to get base dirs")?;
|
||||||
|
//
|
||||||
|
// let mut pathbuf = PathBuf::from(base_dirs.config_dir());
|
||||||
|
// pathbuf.push(project_name);
|
||||||
|
// Ok(pathbuf)
|
||||||
|
// }
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
#[error("Invalid path `{0}`")]
|
#[error("Invalid path `{0}`")]
|
||||||
pub struct InvalidPath(pub PathBuf);
|
pub struct InvalidPath(pub PathBuf);
|
||||||
|
@ -21,7 +43,7 @@ pub struct UnreadableDir {
|
||||||
|
|
||||||
pub fn open(filename: &Path) -> Result<File> {
|
pub fn open(filename: &Path) -> Result<File> {
|
||||||
File::open(filename).with_context(|| {
|
File::open(filename).with_context(|| {
|
||||||
let x = pathbuf_to_string(filename).unwrap_or_else(|e| format!("Unable to get path string: {}", e));
|
let x = filename.to_string();
|
||||||
format!("Failed to open file {}", &x)
|
format!("Failed to open file {}", &x)
|
||||||
})
|
})
|
||||||
}
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::shell::ShellSpawnError;
|
use crate::common::shell::ShellSpawnError;
|
||||||
use anyhow::{Context, Result};
|
use crate::prelude::*;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
pub fn shallow_clone(uri: &str, target: &str) -> Result<()> {
|
pub fn shallow_clone(uri: &str, target: &str) -> Result<()> {
|
13
src/common/mod.rs
Normal file
13
src/common/mod.rs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
// pub mod component;
|
||||||
|
// pub mod deps;
|
||||||
|
// pub mod deser;
|
||||||
|
pub mod clipboard;
|
||||||
|
pub mod fs;
|
||||||
|
pub mod git;
|
||||||
|
pub mod hash;
|
||||||
|
pub mod prelude;
|
||||||
|
pub mod shell;
|
||||||
|
pub mod terminal;
|
||||||
|
pub mod url;
|
||||||
|
// pub mod system;
|
||||||
|
// pub mod tracing;
|
29
src/common/prelude.rs
Normal file
29
src/common/prelude.rs
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
// pub use super::component::Component;
|
||||||
|
// pub use super::deps::HasDeps;
|
||||||
|
pub use super::fs::ToStringExt;
|
||||||
|
pub use anyhow::{anyhow, Context, Error, Result};
|
||||||
|
pub use serde::de::Deserializer;
|
||||||
|
pub use serde::ser::Serializer;
|
||||||
|
pub use serde::{Deserialize, Serialize};
|
||||||
|
pub use std::any::{Any, TypeId};
|
||||||
|
pub use std::collections::{HashMap, HashSet};
|
||||||
|
pub use std::convert::{TryFrom, TryInto};
|
||||||
|
pub use std::fmt::Debug;
|
||||||
|
pub use std::fs::File;
|
||||||
|
pub use std::io::{BufRead, BufReader};
|
||||||
|
pub use std::path::{Path, PathBuf};
|
||||||
|
pub use std::process::Stdio;
|
||||||
|
pub use std::str::FromStr;
|
||||||
|
pub use std::sync::{Arc, Mutex, RwLock};
|
||||||
|
// pub use tracing::{self, debug, error, event, info, instrument, span, trace, warn};
|
||||||
|
/*
|
||||||
|
pub extern crate anyhow;
|
||||||
|
pub extern crate serde;
|
||||||
|
pub extern crate tracing_subscriber;
|
||||||
|
|
||||||
|
#[cfg(feature = "yaml")]
|
||||||
|
pub extern crate serde_yaml;
|
||||||
|
|
||||||
|
#[cfg(feature = "json")]
|
||||||
|
pub extern crate serde_json;
|
||||||
|
*/
|
45
src/common/shell.rs
Normal file
45
src/common/shell.rs
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
use crate::prelude::*;
|
||||||
|
use std::process::Command;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
pub const EOF: &str = "NAVIEOF";
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Shell {
|
||||||
|
Bash,
|
||||||
|
Zsh,
|
||||||
|
Fish,
|
||||||
|
Elvish,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
#[error("Failed to spawn child process `bash` to execute `{command}`")]
|
||||||
|
pub struct ShellSpawnError {
|
||||||
|
command: String,
|
||||||
|
#[source]
|
||||||
|
source: anyhow::Error,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ShellSpawnError {
|
||||||
|
pub fn new<SourceError>(command: impl Into<String>, source: SourceError) -> Self
|
||||||
|
where
|
||||||
|
SourceError: std::error::Error + Sync + Send + 'static,
|
||||||
|
{
|
||||||
|
ShellSpawnError {
|
||||||
|
command: command.into(),
|
||||||
|
source: source.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn out() -> Command {
|
||||||
|
let words_str = CONFIG.shell();
|
||||||
|
let mut words_vec = shellwords::split(&words_str).expect("empty shell command");
|
||||||
|
let mut words = words_vec.iter_mut();
|
||||||
|
let first_cmd = words.next().expect("absent shell binary");
|
||||||
|
let mut cmd = Command::new(&first_cmd);
|
||||||
|
cmd.args(words);
|
||||||
|
let dash_c = if words_str.contains("cmd.exe") { "/c" } else { "-c" };
|
||||||
|
cmd.arg(dash_c);
|
||||||
|
cmd
|
||||||
|
}
|
57
src/common/system.rs.bkp
Normal file
57
src/common/system.rs.bkp
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
use super::prelude::*;
|
||||||
|
use std::collections::hash_map::Entry;
|
||||||
|
|
||||||
|
pub struct System<C> {
|
||||||
|
pub config: Arc<C>,
|
||||||
|
components: HashMap<TypeId, Arc<dyn Component>>,
|
||||||
|
type_ids: Option<HashSet<TypeId>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> System<C> {
|
||||||
|
pub fn new(config: C) -> Result<Self> {
|
||||||
|
Ok(System {
|
||||||
|
config: Arc::new(config),
|
||||||
|
components: HashMap::new(),
|
||||||
|
type_ids: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get<T>(&self) -> Result<&T>
|
||||||
|
where
|
||||||
|
T: Component,
|
||||||
|
{
|
||||||
|
let type_id = TypeId::of::<T>();
|
||||||
|
let c = self.components.get(&type_id).unwrap();
|
||||||
|
c.as_any().downcast_ref::<T>().context("invalid component")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_type_ids(&mut self, type_ids: HashSet<TypeId>) {
|
||||||
|
self.type_ids = Some(type_ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn maybe_add<T: Component, F: FnOnce(&Self) -> Result<T>>(
|
||||||
|
&mut self,
|
||||||
|
type_id: &TypeId,
|
||||||
|
f: F,
|
||||||
|
) -> Result<Option<Arc<T>>> {
|
||||||
|
let should_init = self
|
||||||
|
.type_ids
|
||||||
|
.as_ref()
|
||||||
|
.context("system has no typeIds")?
|
||||||
|
.contains(type_id);
|
||||||
|
|
||||||
|
if !should_init {
|
||||||
|
Ok(None)
|
||||||
|
} else {
|
||||||
|
let component = f(self)?;
|
||||||
|
let arc = Arc::new(component);
|
||||||
|
let entry = self.components.entry(*type_id);
|
||||||
|
if let Entry::Vacant(e) = entry {
|
||||||
|
e.insert(arc.clone());
|
||||||
|
Ok(Some(arc))
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("typeId already included in component map"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
65
src/common/terminal.rs
Normal file
65
src/common/terminal.rs
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
use crate::prelude::*;
|
||||||
|
use crossterm::style;
|
||||||
|
use crossterm::terminal;
|
||||||
|
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
const FALLBACK_WIDTH: u16 = 80;
|
||||||
|
|
||||||
|
fn width_with_shell_out() -> Result<u16> {
|
||||||
|
let output = if cfg!(target_os = "macos") {
|
||||||
|
Command::new("stty")
|
||||||
|
.arg("-f")
|
||||||
|
.arg("/dev/stderr")
|
||||||
|
.arg("size")
|
||||||
|
.stderr(Stdio::inherit())
|
||||||
|
.output()?
|
||||||
|
} else {
|
||||||
|
Command::new("stty")
|
||||||
|
.arg("size")
|
||||||
|
.arg("-F")
|
||||||
|
.arg("/dev/stderr")
|
||||||
|
.stderr(Stdio::inherit())
|
||||||
|
.output()?
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(0) = output.status.code() {
|
||||||
|
let stdout = String::from_utf8(output.stdout).expect("Invalid utf8 output from stty");
|
||||||
|
let mut data = stdout.split_whitespace();
|
||||||
|
data.next();
|
||||||
|
return data
|
||||||
|
.next()
|
||||||
|
.expect("Not enough data")
|
||||||
|
.parse::<u16>()
|
||||||
|
.map_err(|_| anyhow!("Invalid width"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(anyhow!("Invalid status code"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn width() -> u16 {
|
||||||
|
if let Ok((w, _)) = terminal::size() {
|
||||||
|
w
|
||||||
|
} else {
|
||||||
|
width_with_shell_out().unwrap_or(FALLBACK_WIDTH)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_ansi(ansi: &str) -> Option<style::Color> {
|
||||||
|
style::Color::parse_ansi(&format!("5;{}", ansi))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Color(pub style::Color);
|
||||||
|
|
||||||
|
impl FromStr for Color {
|
||||||
|
type Err = &'static str;
|
||||||
|
|
||||||
|
fn from_str(ansi: &str) -> Result<Self, Self::Err> {
|
||||||
|
if let Some(c) = parse_ansi(ansi) {
|
||||||
|
Ok(Color(c))
|
||||||
|
} else {
|
||||||
|
Err("Invalid color")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
36
src/common/tracing.rs.bkp
Normal file
36
src/common/tracing.rs.bkp
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
use std::env;
|
||||||
|
|
||||||
|
use super::prelude::*;
|
||||||
|
|
||||||
|
static RUST_LOG: &str = "RUST_LOG";
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
pub struct TracingConfig {
|
||||||
|
pub time: bool,
|
||||||
|
pub level: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(config: Option<&TracingConfig>) {
|
||||||
|
if let Some(tracing) = config {
|
||||||
|
let level_is_set = match env::var(RUST_LOG) {
|
||||||
|
Err(_) => false,
|
||||||
|
Ok(v) => !v.is_empty(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if !level_is_set {
|
||||||
|
env::set_var(RUST_LOG, &tracing.level);
|
||||||
|
}
|
||||||
|
|
||||||
|
let t = tracing_subscriber::fmt();
|
||||||
|
let res = if tracing.time {
|
||||||
|
t.try_init()
|
||||||
|
} else {
|
||||||
|
t.without_time().try_init()
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(e) = res {
|
||||||
|
error!("unable to set tracing subscriber: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::shell::{self, ShellSpawnError};
|
use crate::common::shell::{self, ShellSpawnError};
|
||||||
|
use crate::prelude::*;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use shell::EOF;
|
use shell::EOF;
|
||||||
|
|
|
@ -1,58 +1,9 @@
|
||||||
|
use crate::commands;
|
||||||
use crate::finder::FinderChoice;
|
use crate::finder::FinderChoice;
|
||||||
use crate::handler::func::Func;
|
use crate::prelude::*;
|
||||||
use crate::handler::info::Info;
|
|
||||||
use crate::shell::Shell;
|
|
||||||
|
|
||||||
use clap::{crate_version, AppSettings, Parser, Subcommand};
|
use clap::{crate_version, AppSettings, Parser, Subcommand};
|
||||||
|
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
const FINDER_POSSIBLE_VALUES: &[&str] = &["fzf", "skim"];
|
const FINDER_POSSIBLE_VALUES: &[&str] = &["fzf", "skim"];
|
||||||
const WIDGET_POSSIBLE_VALUES: &[&str] = &["bash", "zsh", "fish", "elvish"];
|
|
||||||
const FUNC_POSSIBLE_VALUES: &[&str] = &["url::open", "welcome", "widget::last_command", "map::expand"];
|
|
||||||
const INFO_POSSIBLE_VALUES: &[&str] = &["cheats-example", "cheats-path", "config-path", "config-example"];
|
|
||||||
|
|
||||||
impl FromStr for Shell {
|
|
||||||
type Err = &'static str;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
match s {
|
|
||||||
"bash" => Ok(Shell::Bash),
|
|
||||||
"zsh" => Ok(Shell::Zsh),
|
|
||||||
"fish" => Ok(Shell::Fish),
|
|
||||||
"elvish" => Ok(Shell::Elvish),
|
|
||||||
_ => Err("no match"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for Func {
|
|
||||||
type Err = &'static str;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
match s {
|
|
||||||
"url::open" => Ok(Func::UrlOpen),
|
|
||||||
"welcome" => Ok(Func::Welcome),
|
|
||||||
"widget::last_command" => Ok(Func::WidgetLastCommand),
|
|
||||||
"map::expand" => Ok(Func::MapExpand),
|
|
||||||
_ => Err("no match"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for Info {
|
|
||||||
type Err = &'static str;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
match s {
|
|
||||||
"cheats-example" => Ok(Info::CheatsExample),
|
|
||||||
"cheats-path" => Ok(Info::CheatsPath),
|
|
||||||
"config-example" => Ok(Info::ConfigExample),
|
|
||||||
"config-path" => Ok(Info::ConfigPath),
|
|
||||||
_ => Err("no match"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
#[clap(after_help = "\x1b[0;33mMORE INFO:\x1b[0;0m
|
#[clap(after_help = "\x1b[0;33mMORE INFO:\x1b[0;0m
|
||||||
|
@ -142,68 +93,34 @@ impl ClapConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Parser)]
|
// #[derive(Subcommand, Debug, Clone, Runnable, HasDeps)]
|
||||||
|
#[derive(Subcommand, Debug, Clone)]
|
||||||
pub enum Command {
|
pub enum Command {
|
||||||
/// [Experimental] Calls internal functions
|
/// [Experimental] Calls internal functions
|
||||||
Fn {
|
Fn(commands::func::Input),
|
||||||
/// Function name (example: "url::open")
|
|
||||||
#[clap(possible_values = FUNC_POSSIBLE_VALUES, ignore_case = true)]
|
|
||||||
func: Func,
|
|
||||||
/// List of arguments (example: "https://google.com")
|
|
||||||
args: Vec<String>,
|
|
||||||
},
|
|
||||||
/// Manages cheatsheet repositories
|
/// Manages cheatsheet repositories
|
||||||
#[cfg(not(feature = "disable-repo-management"))]
|
#[cfg(not(feature = "disable-repo-management"))]
|
||||||
Repo {
|
Repo(commands::repo::Input),
|
||||||
#[clap(subcommand)]
|
|
||||||
cmd: RepoCommand,
|
|
||||||
},
|
|
||||||
/// Used for fzf's preview window when selecting snippets
|
/// Used for fzf's preview window when selecting snippets
|
||||||
#[clap(setting = AppSettings::Hidden)]
|
#[clap(setting = AppSettings::Hidden)]
|
||||||
Preview {
|
Preview(commands::preview::Input),
|
||||||
/// Selection line
|
|
||||||
line: String,
|
|
||||||
},
|
|
||||||
/// Used for fzf's preview window when selecting variable suggestions
|
/// Used for fzf's preview window when selecting variable suggestions
|
||||||
#[clap(setting = AppSettings::Hidden)]
|
#[clap(setting = AppSettings::Hidden)]
|
||||||
PreviewVar {
|
PreviewVar(commands::preview::var::Input),
|
||||||
/// Selection line
|
|
||||||
selection: String,
|
|
||||||
/// Query match
|
|
||||||
query: String,
|
|
||||||
/// Typed text
|
|
||||||
variable: String,
|
|
||||||
},
|
|
||||||
/// Used for fzf's preview window when selecting variable suggestions
|
/// Used for fzf's preview window when selecting variable suggestions
|
||||||
#[clap(setting = AppSettings::Hidden)]
|
#[clap(setting = AppSettings::Hidden)]
|
||||||
PreviewVarStdin,
|
PreviewVarStdin(commands::preview::var_stdin::Input),
|
||||||
/// Outputs shell widget source code
|
/// Outputs shell widget source code
|
||||||
Widget {
|
Widget(commands::shell::Input),
|
||||||
#[clap(possible_values = WIDGET_POSSIBLE_VALUES, ignore_case = true, default_value = "bash")]
|
|
||||||
shell: Shell,
|
|
||||||
},
|
|
||||||
/// Shows info
|
/// Shows info
|
||||||
Info {
|
Info(commands::info::Input),
|
||||||
#[clap(possible_values = INFO_POSSIBLE_VALUES, ignore_case = true)]
|
|
||||||
info: Info,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Subcommand)]
|
|
||||||
pub enum RepoCommand {
|
|
||||||
/// Imports cheatsheets from a repo
|
|
||||||
Add {
|
|
||||||
/// A URI to a git repository containing .cheat files ("user/repo" will download cheats from github.com/user/repo)
|
|
||||||
uri: String,
|
|
||||||
},
|
|
||||||
/// Browses for featured cheatsheet repos
|
|
||||||
Browse,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum Source {
|
pub enum Source {
|
||||||
Filesystem(Option<String>, Option<String>),
|
Filesystem(Option<String>),
|
||||||
Tldr(String),
|
Tldr(String),
|
||||||
Cheats(String),
|
Cheats(String),
|
||||||
|
Welcome,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum Action {
|
pub enum Action {
|
||||||
|
@ -211,6 +128,7 @@ pub enum Action {
|
||||||
Execute,
|
Execute,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -243,3 +161,4 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
use crate::env_var;
|
use crate::env_var;
|
||||||
|
|
||||||
use crate::finder::FinderChoice;
|
use crate::finder::FinderChoice;
|
||||||
|
use crate::prelude::*;
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
pub struct EnvConfig {
|
pub struct EnvConfig {
|
||||||
pub config_yaml: Option<String>,
|
pub config_yaml: Option<String>,
|
||||||
|
|
|
@ -2,11 +2,17 @@ mod cli;
|
||||||
mod env;
|
mod env;
|
||||||
mod yaml;
|
mod yaml;
|
||||||
|
|
||||||
|
use crate::clients::cheatsh;
|
||||||
|
use crate::clients::tldr;
|
||||||
|
|
||||||
|
use crate::commands::func::Func;
|
||||||
|
use crate::config::Source;
|
||||||
|
use crate::filesystem;
|
||||||
use crate::finder::FinderChoice;
|
use crate::finder::FinderChoice;
|
||||||
|
use crate::structures::fetcher::Fetcher;
|
||||||
use crate::terminal::style::Color;
|
use crate::welcome;
|
||||||
pub use cli::*;
|
pub use cli::*;
|
||||||
|
use crossterm::style::Color;
|
||||||
use env::EnvConfig;
|
use env::EnvConfig;
|
||||||
use yaml::YamlConfig;
|
use yaml::YamlConfig;
|
||||||
|
|
||||||
|
@ -45,8 +51,23 @@ impl Config {
|
||||||
Source::Tldr(query)
|
Source::Tldr(query)
|
||||||
} else if let Some(query) = self.clap.cheatsh.clone() {
|
} else if let Some(query) = self.clap.cheatsh.clone() {
|
||||||
Source::Cheats(query)
|
Source::Cheats(query)
|
||||||
|
} else if let Some(Command::Fn(input)) = self.cmd() {
|
||||||
|
if let Func::Welcome = input.func {
|
||||||
|
Source::Welcome
|
||||||
|
} else {
|
||||||
|
Source::Filesystem(self.path())
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Source::Filesystem(self.path(), self.tag_rules())
|
Source::Filesystem(self.path())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fetcher(&self) -> Box<dyn Fetcher> {
|
||||||
|
match self.source() {
|
||||||
|
Source::Cheats(query) => Box::new(cheatsh::Fetcher::new(query)),
|
||||||
|
Source::Tldr(query) => Box::new(tldr::Fetcher::new(query)),
|
||||||
|
Source::Filesystem(path) => Box::new(filesystem::Fetcher::new(path)),
|
||||||
|
Source::Welcome => Box::new(welcome::Fetcher::new()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,10 @@
|
||||||
use super::env::EnvConfig;
|
use super::env::EnvConfig;
|
||||||
|
use crate::common::fs;
|
||||||
use crate::filesystem::default_config_pathbuf;
|
use crate::filesystem::default_config_pathbuf;
|
||||||
use crate::finder::FinderChoice;
|
use crate::finder::FinderChoice;
|
||||||
use crate::fs;
|
use crate::prelude::*;
|
||||||
use crate::terminal::style::Color as TerminalColor;
|
use crossterm::style::Color as TerminalColor;
|
||||||
use anyhow::Result;
|
use serde::de;
|
||||||
use serde::{de, Deserialize};
|
|
||||||
use std::convert::TryFrom;
|
|
||||||
use std::io::BufReader;
|
|
||||||
use std::path::Path;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct Color(#[serde(deserialize_with = "color_deserialize")] TerminalColor);
|
pub struct Color(#[serde(deserialize_with = "color_deserialize")] TerminalColor);
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
|
use crate::prelude::*;
|
||||||
pub use env::remove_var as remove;
|
pub use env::remove_var as remove;
|
||||||
pub use env::set_var as set;
|
pub use env::set_var as set;
|
||||||
pub use env::var as get;
|
pub use env::var as get;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
pub const PREVIEW_INITIAL_SNIPPET: &str = "NAVI_PREVIEW_INITIAL_SNIPPET";
|
pub const PREVIEW_INITIAL_SNIPPET: &str = "NAVI_PREVIEW_INITIAL_SNIPPET";
|
||||||
pub const PREVIEW_TAGS: &str = "NAVI_PREVIEW_TAGS";
|
pub const PREVIEW_TAGS: &str = "NAVI_PREVIEW_TAGS";
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
|
pub use crate::common::fs::{create_dir, exe_string, read_lines, remove_dir, InvalidPath, UnreadableDir};
|
||||||
use crate::env_var;
|
use crate::env_var;
|
||||||
pub use crate::fs::{
|
use crate::parser::Parser;
|
||||||
create_dir, exe_string, pathbuf_to_string, read_lines, remove_dir, InvalidPath, UnreadableDir,
|
use crate::prelude::*;
|
||||||
};
|
|
||||||
use crate::parser;
|
|
||||||
use crate::structures::cheat::VariableMap;
|
|
||||||
use crate::structures::fetcher;
|
use crate::structures::fetcher;
|
||||||
use anyhow::Result;
|
|
||||||
use directories_next::BaseDirs;
|
use directories_next::BaseDirs;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use std::collections::HashSet;
|
|
||||||
use std::path::{Path, PathBuf, MAIN_SEPARATOR};
|
use std::cell::RefCell;
|
||||||
|
use std::path::MAIN_SEPARATOR;
|
||||||
|
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
pub fn all_cheat_files(path: &Path) -> Vec<String> {
|
pub fn all_cheat_files(path: &Path) -> Vec<String> {
|
||||||
|
@ -76,7 +76,7 @@ pub fn cheat_paths(path: Option<String>) -> Result<String> {
|
||||||
if let Some(p) = path {
|
if let Some(p) = path {
|
||||||
Ok(p)
|
Ok(p)
|
||||||
} else {
|
} else {
|
||||||
pathbuf_to_string(&default_cheat_pathbuf()?)
|
Ok(default_cheat_pathbuf()?.to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,15 +86,6 @@ pub fn tmp_pathbuf() -> Result<PathBuf> {
|
||||||
Ok(root)
|
Ok(root)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn without_first(string: &str) -> String {
|
|
||||||
string
|
|
||||||
.char_indices()
|
|
||||||
.next()
|
|
||||||
.and_then(|(i, _)| string.get(i + 1..))
|
|
||||||
.expect("Should have at least one char")
|
|
||||||
.to_string()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn interpolate_paths(paths: String) -> String {
|
fn interpolate_paths(paths: String) -> String {
|
||||||
let re = Regex::new(r#"\$\{?[a-zA-Z_][a-zA-Z_0-9]*"#).unwrap();
|
let re = Regex::new(r#"\$\{?[a-zA-Z_][a-zA-Z_0-9]*"#).unwrap();
|
||||||
let mut newtext = paths.to_string();
|
let mut newtext = paths.to_string();
|
||||||
|
@ -111,63 +102,29 @@ fn interpolate_paths(paths: String) -> String {
|
||||||
newtext
|
newtext
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gen_lists(tag_rules: Option<String>) -> (Option<Vec<String>>, Option<Vec<String>>) {
|
|
||||||
let mut allowlist = None;
|
|
||||||
let mut denylist: Option<Vec<String>> = None;
|
|
||||||
|
|
||||||
if let Some(rules) = tag_rules {
|
|
||||||
let words: Vec<_> = rules.split(',').collect();
|
|
||||||
allowlist = Some(
|
|
||||||
words
|
|
||||||
.iter()
|
|
||||||
.filter(|w| !w.starts_with('!'))
|
|
||||||
.map(|w| w.to_string())
|
|
||||||
.collect(),
|
|
||||||
);
|
|
||||||
denylist = Some(
|
|
||||||
words
|
|
||||||
.iter()
|
|
||||||
.filter(|w| w.starts_with('!'))
|
|
||||||
.map(|w| without_first(w))
|
|
||||||
.collect(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
(allowlist, denylist)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Fetcher {
|
pub struct Fetcher {
|
||||||
path: Option<String>,
|
path: Option<String>,
|
||||||
allowlist: Option<Vec<String>>,
|
files: RefCell<Vec<String>>,
|
||||||
denylist: Option<Vec<String>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Fetcher {
|
impl Fetcher {
|
||||||
pub fn new(path: Option<String>, tag_rules: Option<String>) -> Self {
|
pub fn new(path: Option<String>) -> Self {
|
||||||
let (allowlist, denylist) = gen_lists(tag_rules);
|
|
||||||
Self {
|
Self {
|
||||||
path,
|
path,
|
||||||
allowlist,
|
files: Default::default(),
|
||||||
denylist,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fetcher::Fetcher for Fetcher {
|
impl fetcher::Fetcher for Fetcher {
|
||||||
fn fetch(
|
fn fetch(&self, parser: &mut Parser) -> Result<bool> {
|
||||||
&self,
|
|
||||||
stdin: &mut std::process::ChildStdin,
|
|
||||||
files: &mut Vec<String>,
|
|
||||||
) -> Result<Option<VariableMap>> {
|
|
||||||
let mut variables = VariableMap::new();
|
|
||||||
let mut found_something = false;
|
let mut found_something = false;
|
||||||
let mut visited_lines = HashSet::new();
|
|
||||||
|
|
||||||
let path = self.path.clone();
|
let path = self.path.clone();
|
||||||
let paths = cheat_paths(path);
|
let paths = cheat_paths(path);
|
||||||
|
|
||||||
if paths.is_err() {
|
if paths.is_err() {
|
||||||
return Ok(None);
|
return Ok(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
let paths = paths.expect("Unable to get paths");
|
let paths = paths.expect("Unable to get paths");
|
||||||
|
@ -175,7 +132,9 @@ impl fetcher::Fetcher for Fetcher {
|
||||||
let folders = paths_from_path_param(&interpolated_paths);
|
let folders = paths_from_path_param(&interpolated_paths);
|
||||||
|
|
||||||
let home_regex = Regex::new(r"^~").unwrap();
|
let home_regex = Regex::new(r"^~").unwrap();
|
||||||
let home = BaseDirs::new().and_then(|b| pathbuf_to_string(b.home_dir()).ok());
|
let home = BaseDirs::new().map(|b| b.home_dir().to_string());
|
||||||
|
|
||||||
|
// parser.filter = self.tag_rules.as_ref().map(|r| gen_lists(r.as_str()));
|
||||||
|
|
||||||
for folder in folders {
|
for folder in folders {
|
||||||
let interpolated_folder = match &home {
|
let interpolated_folder = match &home {
|
||||||
|
@ -184,21 +143,12 @@ impl fetcher::Fetcher for Fetcher {
|
||||||
};
|
};
|
||||||
let folder_pathbuf = PathBuf::from(interpolated_folder);
|
let folder_pathbuf = PathBuf::from(interpolated_folder);
|
||||||
for file in all_cheat_files(&folder_pathbuf) {
|
for file in all_cheat_files(&folder_pathbuf) {
|
||||||
files.push(file.clone());
|
self.files.borrow_mut().push(file.clone());
|
||||||
let index = files.len() - 1;
|
let index = self.files.borrow().len() - 1;
|
||||||
let read_file_result = {
|
let read_file_result = {
|
||||||
let path = PathBuf::from(&file);
|
let path = PathBuf::from(&file);
|
||||||
let lines = read_lines(&path)?;
|
let lines = read_lines(&path)?;
|
||||||
parser::read_lines(
|
parser.read_lines(lines, &file, Some(index))
|
||||||
lines,
|
|
||||||
&file,
|
|
||||||
index,
|
|
||||||
&mut variables,
|
|
||||||
&mut visited_lines,
|
|
||||||
stdin,
|
|
||||||
self.allowlist.as_ref(),
|
|
||||||
self.denylist.as_ref(),
|
|
||||||
)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if read_file_result.is_ok() && !found_something {
|
if read_file_result.is_ok() && !found_something {
|
||||||
|
@ -207,11 +157,11 @@ impl fetcher::Fetcher for Fetcher {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !found_something {
|
Ok(found_something)
|
||||||
return Ok(None);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Some(variables))
|
fn files(&self) -> Vec<String> {
|
||||||
|
self.files.borrow().clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -279,7 +229,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_default_config_pathbuf() {
|
fn test_default_config_pathbuf() {
|
||||||
let base_dirs = BaseDirs::new()
|
let base_dirs = BaseDirs::new()
|
||||||
.ok_or(anyhow!("bad"))
|
.ok_or_else(|| anyhow!("bad"))
|
||||||
.expect("could not determine base directories");
|
.expect("could not determine base directories");
|
||||||
|
|
||||||
let expected = {
|
let expected = {
|
||||||
|
@ -297,7 +247,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_default_cheat_pathbuf() {
|
fn test_default_cheat_pathbuf() {
|
||||||
let base_dirs = BaseDirs::new()
|
let base_dirs = BaseDirs::new()
|
||||||
.ok_or(anyhow!("bad"))
|
.ok_or_else(|| anyhow!("bad"))
|
||||||
.expect("could not determine base directories");
|
.expect("could not determine base directories");
|
||||||
|
|
||||||
let expected = {
|
let expected = {
|
||||||
|
|
|
@ -1,18 +1,16 @@
|
||||||
use crate::config::CONFIG;
|
use crate::prelude::*;
|
||||||
use crate::structures::cheat::VariableMap;
|
use crate::serializer;
|
||||||
use crate::writer;
|
|
||||||
use anyhow::Context;
|
use std::io::Write;
|
||||||
use anyhow::Result;
|
|
||||||
use std::process::{self, Output};
|
use std::process::{self, Output};
|
||||||
use std::process::{Command, Stdio};
|
use std::process::{Command, Stdio};
|
||||||
mod post;
|
|
||||||
pub mod structures;
|
pub mod structures;
|
||||||
pub use post::process;
|
pub use post::process;
|
||||||
use serde::Deserialize;
|
|
||||||
use std::str::FromStr;
|
|
||||||
use structures::Opts;
|
use structures::Opts;
|
||||||
use structures::SuggestionType;
|
use structures::SuggestionType;
|
||||||
|
|
||||||
|
mod post;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Deserialize)]
|
#[derive(Debug, Clone, Copy, Deserialize)]
|
||||||
pub enum FinderChoice {
|
pub enum FinderChoice {
|
||||||
Fzf,
|
Fzf,
|
||||||
|
@ -31,12 +29,6 @@ impl FromStr for FinderChoice {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Finder {
|
|
||||||
fn call<F>(&self, opts: Opts, stdin_fn: F) -> Result<(String, Option<VariableMap>, Vec<String>)>
|
|
||||||
where
|
|
||||||
F: Fn(&mut process::ChildStdin, &mut Vec<String>) -> Result<Option<VariableMap>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse(out: Output, opts: Opts) -> Result<String> {
|
fn parse(out: Output, opts: Opts) -> Result<String> {
|
||||||
let text = match out.status.code() {
|
let text = match out.status.code() {
|
||||||
Some(0) | Some(1) | Some(2) => {
|
Some(0) | Some(1) | Some(2) => {
|
||||||
|
@ -54,10 +46,10 @@ fn parse(out: Output, opts: Opts) -> Result<String> {
|
||||||
post::process(output, opts.column, opts.delimiter.as_deref(), opts.map)
|
post::process(output, opts.column, opts.delimiter.as_deref(), opts.map)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Finder for FinderChoice {
|
impl FinderChoice {
|
||||||
fn call<F>(&self, finder_opts: Opts, stdin_fn: F) -> Result<(String, Option<VariableMap>, Vec<String>)>
|
pub fn call<F, R>(&self, finder_opts: Opts, stdin_fn: F) -> Result<(String, R)>
|
||||||
where
|
where
|
||||||
F: Fn(&mut process::ChildStdin, &mut Vec<String>) -> Result<Option<VariableMap>>,
|
F: Fn(&mut dyn Write) -> Result<R>,
|
||||||
{
|
{
|
||||||
let finder_str = match self {
|
let finder_str = match self {
|
||||||
Self::Fzf => "fzf",
|
Self::Fzf => "fzf",
|
||||||
|
@ -86,7 +78,7 @@ impl Finder for FinderChoice {
|
||||||
"--with-nth",
|
"--with-nth",
|
||||||
"1,2,3",
|
"1,2,3",
|
||||||
"--delimiter",
|
"--delimiter",
|
||||||
writer::DELIMITER.to_string().as_str(),
|
serializer::DELIMITER.to_string().as_str(),
|
||||||
"--ansi",
|
"--ansi",
|
||||||
"--bind",
|
"--bind",
|
||||||
format!("ctrl-j:down,ctrl-k:up{}", bindings).as_str(),
|
format!("ctrl-j:down,ctrl-k:up{}", bindings).as_str(),
|
||||||
|
@ -188,12 +180,13 @@ impl Finder for FinderChoice {
|
||||||
.as_mut()
|
.as_mut()
|
||||||
.ok_or_else(|| anyhow!("Unable to acquire stdin of finder"))?;
|
.ok_or_else(|| anyhow!("Unable to acquire stdin of finder"))?;
|
||||||
|
|
||||||
let mut files = vec![];
|
let mut writer: Box<&mut dyn Write> = Box::new(stdin);
|
||||||
let result_map = stdin_fn(stdin, &mut files).context("Failed to pass data to finder")?;
|
|
||||||
|
let return_value = stdin_fn(&mut writer).context("Failed to pass data to finder")?;
|
||||||
|
|
||||||
let out = child.wait_with_output().context("Failed to wait for finder")?;
|
let out = child.wait_with_output().context("Failed to wait for finder")?;
|
||||||
|
|
||||||
let output = parse(out, finder_opts).context("Unable to get output")?;
|
let output = parse(out, finder_opts).context("Unable to get output")?;
|
||||||
Ok((output, result_map, files))
|
Ok((output, return_value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
use crate::config::CONFIG;
|
use crate::common::shell;
|
||||||
use crate::finder::structures::SuggestionType;
|
use crate::finder::structures::SuggestionType;
|
||||||
use crate::shell;
|
use crate::prelude::*;
|
||||||
use anyhow::Context;
|
|
||||||
use anyhow::Result;
|
|
||||||
use shell::EOF;
|
use shell::EOF;
|
||||||
use std::process::Stdio;
|
use std::process::Stdio;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::config::CONFIG;
|
|
||||||
use crate::filesystem;
|
use crate::filesystem;
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
pub struct Opts {
|
pub struct Opts {
|
||||||
|
|
|
@ -1,51 +0,0 @@
|
||||||
use crate::actor;
|
|
||||||
use crate::cheatsh;
|
|
||||||
use crate::config::Source;
|
|
||||||
use crate::config::CONFIG;
|
|
||||||
use crate::extractor;
|
|
||||||
use crate::filesystem;
|
|
||||||
use crate::finder::structures::Opts as FinderOpts;
|
|
||||||
use crate::finder::Finder;
|
|
||||||
use crate::structures::cheat::VariableMap;
|
|
||||||
use crate::structures::fetcher::Fetcher;
|
|
||||||
use crate::tldr;
|
|
||||||
use crate::welcome;
|
|
||||||
use anyhow::Context;
|
|
||||||
use anyhow::Result;
|
|
||||||
|
|
||||||
pub fn main() -> Result<()> {
|
|
||||||
let config = &CONFIG;
|
|
||||||
let opts = FinderOpts::snippet_default();
|
|
||||||
|
|
||||||
let (raw_selection, variables, files) = config
|
|
||||||
.finder()
|
|
||||||
.call(opts, |stdin, files| {
|
|
||||||
let fetcher: Box<dyn Fetcher> = match config.source() {
|
|
||||||
Source::Cheats(query) => Box::new(cheatsh::Fetcher::new(query)),
|
|
||||||
Source::Tldr(query) => Box::new(tldr::Fetcher::new(query)),
|
|
||||||
Source::Filesystem(path, rules) => Box::new(filesystem::Fetcher::new(path, rules)),
|
|
||||||
};
|
|
||||||
|
|
||||||
let res = fetcher
|
|
||||||
.fetch(stdin, files)
|
|
||||||
.context("Failed to parse variables intended for finder")?;
|
|
||||||
|
|
||||||
if let Some(variables) = res {
|
|
||||||
Ok(Some(variables))
|
|
||||||
} else {
|
|
||||||
welcome::populate_cheatsheet(stdin)?;
|
|
||||||
Ok(Some(VariableMap::new()))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.context("Failed getting selection and variables from finder")?;
|
|
||||||
|
|
||||||
let extractions = extractor::extract_from_selections(&raw_selection, config.best_match());
|
|
||||||
|
|
||||||
if extractions.is_err() {
|
|
||||||
return main();
|
|
||||||
}
|
|
||||||
|
|
||||||
actor::act(extractions, files, variables)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
use crate::cheat_variable;
|
|
||||||
|
|
||||||
use crate::shell::{self};
|
|
||||||
|
|
||||||
use crate::url;
|
|
||||||
use crate::welcome;
|
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum Func {
|
|
||||||
UrlOpen,
|
|
||||||
Welcome,
|
|
||||||
WidgetLastCommand,
|
|
||||||
MapExpand,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn main(func: &Func, args: Vec<String>) -> Result<()> {
|
|
||||||
match func {
|
|
||||||
Func::UrlOpen => url::open(args),
|
|
||||||
Func::Welcome => welcome::main(),
|
|
||||||
Func::WidgetLastCommand => shell::widget_last_command(),
|
|
||||||
Func::MapExpand => cheat_variable::map_expand(),
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
use crate::filesystem;
|
|
||||||
use crate::fs::pathbuf_to_string;
|
|
||||||
use anyhow::Result;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum Info {
|
|
||||||
CheatsExample,
|
|
||||||
CheatsPath,
|
|
||||||
ConfigPath,
|
|
||||||
ConfigExample,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn main(info: &Info) -> Result<()> {
|
|
||||||
match info {
|
|
||||||
Info::CheatsExample => println!("{}", include_str!("../../docs/cheat_example.cheat")),
|
|
||||||
Info::CheatsPath => println!("{}", pathbuf_to_string(&filesystem::default_cheat_pathbuf()?)?),
|
|
||||||
Info::ConfigPath => println!("{}", pathbuf_to_string(&filesystem::default_config_pathbuf()?)?),
|
|
||||||
Info::ConfigExample => println!("{}", include_str!("../../docs/config_file_example.yaml")),
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
|
@ -1,60 +0,0 @@
|
||||||
pub mod core;
|
|
||||||
pub mod func;
|
|
||||||
pub mod info;
|
|
||||||
pub mod preview;
|
|
||||||
pub mod preview_var;
|
|
||||||
pub mod preview_var_stdin;
|
|
||||||
pub mod repo_add;
|
|
||||||
pub mod repo_browse;
|
|
||||||
pub mod shell;
|
|
||||||
|
|
||||||
#[cfg(not(feature = "disable-repo-management"))]
|
|
||||||
use crate::config::Command::Repo;
|
|
||||||
use crate::config::Command::{Fn, Info, Preview, PreviewVar, PreviewVarStdin, Widget};
|
|
||||||
use crate::config::{RepoCommand, CONFIG};
|
|
||||||
use crate::handler;
|
|
||||||
use anyhow::Context;
|
|
||||||
use anyhow::Result;
|
|
||||||
|
|
||||||
pub fn handle() -> Result<()> {
|
|
||||||
match CONFIG.cmd() {
|
|
||||||
None => handler::core::main(),
|
|
||||||
|
|
||||||
Some(c) => match c {
|
|
||||||
Preview { line } => handler::preview::main(line),
|
|
||||||
|
|
||||||
PreviewVarStdin => handler::preview_var_stdin::main(),
|
|
||||||
|
|
||||||
PreviewVar {
|
|
||||||
selection,
|
|
||||||
query,
|
|
||||||
variable,
|
|
||||||
} => handler::preview_var::main(selection, query, variable),
|
|
||||||
|
|
||||||
Widget { shell } => handler::shell::main(shell).context("Failed to print shell widget code"),
|
|
||||||
|
|
||||||
Fn { func, args } => handler::func::main(func, args.to_vec())
|
|
||||||
.with_context(|| format!("Failed to execute function `{:#?}`", func)),
|
|
||||||
|
|
||||||
Info { info } => {
|
|
||||||
handler::info::main(info).with_context(|| format!("Failed to fetch info `{:#?}`", info))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "disable-repo-management"))]
|
|
||||||
Repo { cmd } => match cmd {
|
|
||||||
RepoCommand::Add { uri } => {
|
|
||||||
handler::repo_add::main(uri.clone())
|
|
||||||
.with_context(|| format!("Failed to import cheatsheets from `{}`", uri))?;
|
|
||||||
handler::core::main()
|
|
||||||
}
|
|
||||||
RepoCommand::Browse => {
|
|
||||||
let repo =
|
|
||||||
handler::repo_browse::main().context("Failed to browse featured cheatsheets")?;
|
|
||||||
handler::repo_add::main(repo.clone())
|
|
||||||
.with_context(|| format!("Failed to import cheatsheets from `{}`", repo))?;
|
|
||||||
handler::core::main()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
use crate::config::CONFIG;
|
|
||||||
use crate::ui;
|
|
||||||
use crate::writer;
|
|
||||||
use anyhow::Result;
|
|
||||||
use crossterm::style::Stylize;
|
|
||||||
use std::process;
|
|
||||||
|
|
||||||
fn extract_elements(argstr: &str) -> (&str, &str, &str) {
|
|
||||||
let mut parts = argstr.split(writer::DELIMITER).skip(3);
|
|
||||||
let tags = parts.next().expect("No `tags` element provided.");
|
|
||||||
let comment = parts.next().expect("No `comment` element provided.");
|
|
||||||
let snippet = parts.next().expect("No `snippet` element provided.");
|
|
||||||
(tags, comment, snippet)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn main(line: &str) -> Result<()> {
|
|
||||||
let (tags, comment, snippet) = extract_elements(line);
|
|
||||||
|
|
||||||
println!(
|
|
||||||
"{comment} {tags} \n{snippet}",
|
|
||||||
comment = ui::style(comment).with(CONFIG.comment_color()),
|
|
||||||
tags = ui::style(format!("[{}]", tags)).with(CONFIG.tag_color()),
|
|
||||||
snippet = ui::style(writer::fix_newlines(snippet)).with(CONFIG.snippet_color()),
|
|
||||||
);
|
|
||||||
|
|
||||||
process::exit(0)
|
|
||||||
}
|
|
|
@ -1,91 +0,0 @@
|
||||||
use crate::config::CONFIG;
|
|
||||||
use crate::env_var;
|
|
||||||
use crate::finder;
|
|
||||||
use crate::terminal::style::style;
|
|
||||||
use crate::writer;
|
|
||||||
use anyhow::Result;
|
|
||||||
use crossterm::style::Stylize;
|
|
||||||
use std::collections::HashSet;
|
|
||||||
use std::iter;
|
|
||||||
use std::process;
|
|
||||||
|
|
||||||
pub fn main(selection: &str, query: &str, variable: &str) -> Result<()> {
|
|
||||||
let snippet = env_var::must_get(env_var::PREVIEW_INITIAL_SNIPPET);
|
|
||||||
let tags = env_var::must_get(env_var::PREVIEW_TAGS);
|
|
||||||
let comment = env_var::must_get(env_var::PREVIEW_COMMENT);
|
|
||||||
let column = env_var::parse(env_var::PREVIEW_COLUMN);
|
|
||||||
let delimiter = env_var::get(env_var::PREVIEW_DELIMITER).ok();
|
|
||||||
let map = env_var::get(env_var::PREVIEW_MAP).ok();
|
|
||||||
|
|
||||||
let active_color = CONFIG.tag_color();
|
|
||||||
let inactive_color = CONFIG.comment_color();
|
|
||||||
|
|
||||||
let mut colored_snippet = String::from(&snippet);
|
|
||||||
let mut visited_vars: HashSet<&str> = HashSet::new();
|
|
||||||
|
|
||||||
let mut variables = String::from("");
|
|
||||||
|
|
||||||
println!(
|
|
||||||
"{comment} {tags}",
|
|
||||||
comment = style(comment).with(CONFIG.comment_color()),
|
|
||||||
tags = style(format!("[{}]", tags)).with(CONFIG.tag_color()),
|
|
||||||
);
|
|
||||||
|
|
||||||
let bracketed_current_variable = format!("<{}>", variable);
|
|
||||||
|
|
||||||
let bracketed_variables: Vec<&str> = {
|
|
||||||
if snippet.contains(&bracketed_current_variable) {
|
|
||||||
writer::VAR_REGEX
|
|
||||||
.find_iter(&snippet)
|
|
||||||
.map(|m| m.as_str())
|
|
||||||
.collect()
|
|
||||||
} else {
|
|
||||||
iter::once(&bracketed_current_variable)
|
|
||||||
.map(|s| s.as_str())
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
for bracketed_variable_name in bracketed_variables {
|
|
||||||
let variable_name = &bracketed_variable_name[1..bracketed_variable_name.len() - 1];
|
|
||||||
|
|
||||||
if visited_vars.contains(variable_name) {
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
visited_vars.insert(variable_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
let is_current = variable_name == variable;
|
|
||||||
let variable_color = if is_current { active_color } else { inactive_color };
|
|
||||||
let env_variable_name = env_var::escape(variable_name);
|
|
||||||
|
|
||||||
let value = if is_current {
|
|
||||||
let v = selection.trim_matches('\'');
|
|
||||||
if v.is_empty() { query.trim_matches('\'') } else { v }.to_string()
|
|
||||||
} else if let Ok(v) = env_var::get(&env_variable_name) {
|
|
||||||
v
|
|
||||||
} else {
|
|
||||||
"".to_string()
|
|
||||||
};
|
|
||||||
|
|
||||||
let replacement = format!(
|
|
||||||
"{variable}",
|
|
||||||
variable = style(bracketed_variable_name).with(variable_color),
|
|
||||||
);
|
|
||||||
|
|
||||||
colored_snippet = colored_snippet.replace(bracketed_variable_name, &replacement);
|
|
||||||
|
|
||||||
variables = format!(
|
|
||||||
"{variables}\n{variable} = {value}",
|
|
||||||
variables = variables,
|
|
||||||
variable = style(variable_name).with(variable_color),
|
|
||||||
value = finder::process(value, column, delimiter.as_deref(), map.clone())
|
|
||||||
.expect("Unable to process value"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("{snippet}", snippet = writer::fix_newlines(&colored_snippet));
|
|
||||||
println!("{variables}", variables = variables);
|
|
||||||
|
|
||||||
process::exit(0)
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
use crate::shell::{self, ShellSpawnError, EOF};
|
|
||||||
use anyhow::Result;
|
|
||||||
use std::io::{self, Read};
|
|
||||||
|
|
||||||
pub fn main() -> Result<()> {
|
|
||||||
let mut text = String::new();
|
|
||||||
io::stdin().read_to_string(&mut text)?;
|
|
||||||
|
|
||||||
let mut parts = text.split(EOF);
|
|
||||||
let selection = parts.next().expect("Unable to get selection");
|
|
||||||
let query = parts.next().expect("Unable to get query");
|
|
||||||
let variable = parts.next().expect("Unable to get variable").trim();
|
|
||||||
|
|
||||||
super::handler::preview_var::main(selection, query, variable)?;
|
|
||||||
|
|
||||||
if let Some(extra) = parts.next() {
|
|
||||||
if !extra.is_empty() {
|
|
||||||
print!("");
|
|
||||||
|
|
||||||
shell::out()
|
|
||||||
.arg(extra)
|
|
||||||
.spawn()
|
|
||||||
.map_err(|e| ShellSpawnError::new(extra, e))?
|
|
||||||
.wait()?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
use crate::shell::Shell;
|
|
||||||
use anyhow::Result;
|
|
||||||
|
|
||||||
pub fn main(shell: &Shell) -> Result<()> {
|
|
||||||
let content = match shell {
|
|
||||||
Shell::Bash => include_str!("../../shell/navi.plugin.bash"),
|
|
||||||
Shell::Zsh => include_str!("../../shell/navi.plugin.zsh"),
|
|
||||||
Shell::Fish => include_str!("../../shell/navi.plugin.fish"),
|
|
||||||
Shell::Elvish => include_str!("../../shell/navi.plugin.elv"),
|
|
||||||
};
|
|
||||||
|
|
||||||
println!("{}", content);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
25
src/lib.rs
25
src/lib.rs
|
@ -1,29 +1,20 @@
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate lazy_static;
|
extern crate lazy_static;
|
||||||
#[macro_use]
|
// #[macro_use]
|
||||||
extern crate anyhow;
|
// extern crate anyhow;
|
||||||
|
|
||||||
mod actor;
|
mod clients;
|
||||||
mod cheat_variable;
|
mod commands;
|
||||||
mod cheatsh;
|
mod common;
|
||||||
mod clipboard;
|
|
||||||
mod config;
|
mod config;
|
||||||
mod env_var;
|
mod env_var;
|
||||||
mod extractor;
|
|
||||||
mod filesystem;
|
mod filesystem;
|
||||||
mod finder;
|
mod finder;
|
||||||
mod fs;
|
|
||||||
mod git;
|
|
||||||
mod handler;
|
|
||||||
mod hash;
|
|
||||||
mod parser;
|
mod parser;
|
||||||
mod shell;
|
mod prelude;
|
||||||
|
mod serializer;
|
||||||
mod structures;
|
mod structures;
|
||||||
mod terminal;
|
mod terminal;
|
||||||
mod tldr;
|
|
||||||
mod ui;
|
|
||||||
mod url;
|
|
||||||
mod welcome;
|
mod welcome;
|
||||||
mod writer;
|
|
||||||
|
|
||||||
pub use handler::handle;
|
pub use commands::handle;
|
||||||
|
|
324
src/parser.rs
324
src/parser.rs
|
@ -1,12 +1,9 @@
|
||||||
|
use crate::common::fs;
|
||||||
use crate::finder::structures::{Opts as FinderOpts, SuggestionType};
|
use crate::finder::structures::{Opts as FinderOpts, SuggestionType};
|
||||||
use crate::fs;
|
use crate::prelude::*;
|
||||||
use crate::hash::fnv;
|
use crate::serializer;
|
||||||
use crate::structures::cheat::VariableMap;
|
use crate::structures::cheat::VariableMap;
|
||||||
use crate::structures::item::Item;
|
use crate::structures::item::Item;
|
||||||
use crate::writer;
|
|
||||||
use anyhow::{Context, Result};
|
|
||||||
use regex::Regex;
|
|
||||||
use std::collections::HashSet;
|
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
|
@ -108,49 +105,6 @@ fn parse_variable_line(line: &str) -> Result<(&str, &str, Option<FinderOpts>)> {
|
||||||
Ok((variable, command, command_options))
|
Ok((variable, command, command_options))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_cmd(
|
|
||||||
item: &Item,
|
|
||||||
stdin: &mut std::process::ChildStdin,
|
|
||||||
allowlist: Option<&Vec<String>>,
|
|
||||||
denylist: Option<&Vec<String>>,
|
|
||||||
visited_lines: &mut HashSet<u64>,
|
|
||||||
) -> Result<()> {
|
|
||||||
if item.comment.is_empty() || item.snippet.trim().is_empty() {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let hash = fnv(&format!("{}{}", &item.comment, &item.snippet));
|
|
||||||
if visited_lines.contains(&hash) {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
visited_lines.insert(hash);
|
|
||||||
|
|
||||||
if let Some(list) = denylist {
|
|
||||||
for v in list {
|
|
||||||
if item.tags.contains(v) {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(list) = allowlist {
|
|
||||||
let mut should_allow = false;
|
|
||||||
for v in list {
|
|
||||||
if item.tags.contains(v) {
|
|
||||||
should_allow = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !should_allow {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return stdin
|
|
||||||
.write_all(writer::write(item).as_bytes())
|
|
||||||
.context("Failed to write command to finder's stdin");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn without_prefix(line: &str) -> String {
|
fn without_prefix(line: &str) -> String {
|
||||||
if line.len() > 2 {
|
if line.len() > 2 {
|
||||||
String::from(line[2..].trim())
|
String::from(line[2..].trim())
|
||||||
|
@ -159,96 +113,214 @@ fn without_prefix(line: &str) -> String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[derive(Clone, Default)]
|
||||||
pub fn read_lines(
|
pub struct FilterOpts {
|
||||||
lines: impl Iterator<Item = Result<String>>,
|
pub allowlist: Vec<String>,
|
||||||
id: &str,
|
pub denylist: Vec<String>,
|
||||||
file_index: usize,
|
pub hash: Option<u64>,
|
||||||
variables: &mut VariableMap,
|
}
|
||||||
visited_lines: &mut HashSet<u64>,
|
|
||||||
stdin: &mut std::process::ChildStdin,
|
|
||||||
allowlist: Option<&Vec<String>>,
|
|
||||||
denylist: Option<&Vec<String>>,
|
|
||||||
) -> Result<()> {
|
|
||||||
let mut item = Item::new();
|
|
||||||
item.file_index = file_index;
|
|
||||||
|
|
||||||
let mut should_break = false;
|
pub struct Parser<'a> {
|
||||||
|
pub variables: VariableMap,
|
||||||
|
visited_lines: HashSet<u64>,
|
||||||
|
filter: FilterOpts,
|
||||||
|
writer: &'a mut dyn Write,
|
||||||
|
write_fn: fn(&Item) -> String,
|
||||||
|
}
|
||||||
|
|
||||||
let mut variable_cmd = String::from("");
|
fn without_first(string: &str) -> String {
|
||||||
|
string
|
||||||
|
.char_indices()
|
||||||
|
.next()
|
||||||
|
.and_then(|(i, _)| string.get(i + 1..))
|
||||||
|
.expect("Should have at least one char")
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
for (line_nr, line_result) in lines.enumerate() {
|
fn gen_lists(tag_rules: &str) -> FilterOpts {
|
||||||
let line = line_result
|
let words: Vec<_> = tag_rules.split(',').collect();
|
||||||
.with_context(|| format!("Failed to read line number {} in cheatsheet `{}`", line_nr, id))?;
|
|
||||||
|
|
||||||
if should_break {
|
let allowlist = words
|
||||||
break;
|
.iter()
|
||||||
}
|
.filter(|w| !w.starts_with('!'))
|
||||||
|
.map(|w| w.to_string())
|
||||||
|
.collect();
|
||||||
|
|
||||||
// duplicate
|
let denylist = words
|
||||||
if !item.tags.is_empty() && !item.comment.is_empty() {}
|
.iter()
|
||||||
// blank
|
.filter(|w| w.starts_with('!'))
|
||||||
if line.is_empty() {
|
.map(|w| without_first(w))
|
||||||
if !(&item.snippet).is_empty() {
|
.collect();
|
||||||
item.snippet.push_str(writer::LINE_SEPARATOR);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// tag
|
|
||||||
else if line.starts_with('%') {
|
|
||||||
should_break = write_cmd(&item, stdin, allowlist, denylist, visited_lines).is_err();
|
|
||||||
item.snippet = String::from("");
|
|
||||||
item.tags = without_prefix(&line);
|
|
||||||
}
|
|
||||||
// dependency
|
|
||||||
else if line.starts_with('@') {
|
|
||||||
let tags_dependency = without_prefix(&line);
|
|
||||||
variables.insert_dependency(&item.tags, &tags_dependency);
|
|
||||||
}
|
|
||||||
// metacomment
|
|
||||||
else if line.starts_with(';') {
|
|
||||||
}
|
|
||||||
// comment
|
|
||||||
else if line.starts_with('#') {
|
|
||||||
should_break = write_cmd(&item, stdin, allowlist, denylist, visited_lines).is_err();
|
|
||||||
item.snippet = String::from("");
|
|
||||||
item.comment = without_prefix(&line);
|
|
||||||
}
|
|
||||||
// variable
|
|
||||||
else if !variable_cmd.is_empty() || (line.starts_with('$') && line.contains(':')) {
|
|
||||||
should_break = write_cmd(&item, stdin, allowlist, denylist, visited_lines).is_err();
|
|
||||||
|
|
||||||
item.snippet = String::from("");
|
FilterOpts {
|
||||||
|
allowlist,
|
||||||
|
denylist,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
variable_cmd.push_str(line.trim_end_matches('\\'));
|
impl<'a> Parser<'a> {
|
||||||
|
pub fn new(writer: &'a mut dyn Write, is_terminal: bool) -> Self {
|
||||||
|
let write_fn = if is_terminal {
|
||||||
|
serializer::write
|
||||||
|
} else {
|
||||||
|
serializer::write_raw
|
||||||
|
};
|
||||||
|
|
||||||
if !line.ends_with('\\') {
|
let filter = match CONFIG.tag_rules() {
|
||||||
let full_variable_cmd = variable_cmd.clone();
|
Some(tr) => gen_lists(&tr),
|
||||||
let (variable, command, opts) =
|
None => Default::default(),
|
||||||
parse_variable_line(&full_variable_cmd).with_context(|| {
|
};
|
||||||
format!(
|
|
||||||
"Failed to parse variable line. See line number {} in cheatsheet `{}`",
|
Self {
|
||||||
line_nr + 1,
|
variables: Default::default(),
|
||||||
id
|
visited_lines: Default::default(),
|
||||||
)
|
filter,
|
||||||
})?;
|
write_fn,
|
||||||
variable_cmd = String::from("");
|
writer,
|
||||||
variables.insert_suggestion(&item.tags, variable, (String::from(command), opts));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// snippet
|
|
||||||
else {
|
|
||||||
if !(&item.snippet).is_empty() {
|
|
||||||
item.snippet.push_str(writer::LINE_SEPARATOR);
|
|
||||||
}
|
|
||||||
item.snippet.push_str(&line);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !should_break {
|
pub fn set_hash(&mut self, hash: u64) {
|
||||||
let _ = write_cmd(&item, stdin, allowlist, denylist, visited_lines);
|
self.filter.hash = Some(hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
fn write_cmd(&mut self, item: &Item) -> Result<()> {
|
||||||
|
if item.comment.is_empty() || item.snippet.trim().is_empty() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let hash = item.hash();
|
||||||
|
if self.visited_lines.contains(&hash) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
self.visited_lines.insert(hash);
|
||||||
|
|
||||||
|
if !self.filter.denylist.is_empty() {
|
||||||
|
for v in &self.filter.denylist {
|
||||||
|
if item.tags.contains(v) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.filter.allowlist.is_empty() {
|
||||||
|
let mut should_allow = false;
|
||||||
|
for v in &self.filter.allowlist {
|
||||||
|
if item.tags.contains(v) {
|
||||||
|
should_allow = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !should_allow {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(h) = self.filter.hash {
|
||||||
|
if h != hash {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let write_fn = self.write_fn;
|
||||||
|
|
||||||
|
return self
|
||||||
|
.writer
|
||||||
|
.write_all(write_fn(item).as_bytes())
|
||||||
|
.context("Failed to write command to finder's stdin");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_lines(
|
||||||
|
&mut self,
|
||||||
|
lines: impl Iterator<Item = Result<String>>,
|
||||||
|
id: &str,
|
||||||
|
file_index: Option<usize>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let mut item = Item::new(file_index);
|
||||||
|
|
||||||
|
let mut should_break = false;
|
||||||
|
|
||||||
|
let mut variable_cmd = String::from("");
|
||||||
|
|
||||||
|
for (line_nr, line_result) in lines.enumerate() {
|
||||||
|
let line = line_result
|
||||||
|
.with_context(|| format!("Failed to read line number {} in cheatsheet `{}`", line_nr, id))?;
|
||||||
|
|
||||||
|
if should_break {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// duplicate
|
||||||
|
if !item.tags.is_empty() && !item.comment.is_empty() {}
|
||||||
|
// blank
|
||||||
|
if line.is_empty() {
|
||||||
|
if !(&item.snippet).is_empty() {
|
||||||
|
item.snippet.push_str(serializer::LINE_SEPARATOR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// tag
|
||||||
|
else if line.starts_with('%') {
|
||||||
|
should_break = self.write_cmd(&item).is_err();
|
||||||
|
item.snippet = String::from("");
|
||||||
|
item.tags = without_prefix(&line);
|
||||||
|
}
|
||||||
|
// dependency
|
||||||
|
else if line.starts_with('@') {
|
||||||
|
let tags_dependency = without_prefix(&line);
|
||||||
|
self.variables.insert_dependency(&item.tags, &tags_dependency);
|
||||||
|
}
|
||||||
|
// raycast icon
|
||||||
|
else if let Some(icon) = line.strip_prefix("; raycast.icon:") {
|
||||||
|
item.icon = Some(icon.trim().into());
|
||||||
|
}
|
||||||
|
// metacomment
|
||||||
|
else if line.starts_with(';') {
|
||||||
|
}
|
||||||
|
// comment
|
||||||
|
else if line.starts_with('#') {
|
||||||
|
should_break = self.write_cmd(&item).is_err();
|
||||||
|
item.snippet = String::from("");
|
||||||
|
item.comment = without_prefix(&line);
|
||||||
|
}
|
||||||
|
// variable
|
||||||
|
else if !variable_cmd.is_empty() || (line.starts_with('$') && line.contains(':')) {
|
||||||
|
should_break = self.write_cmd(&item).is_err();
|
||||||
|
|
||||||
|
item.snippet = String::from("");
|
||||||
|
|
||||||
|
variable_cmd.push_str(line.trim_end_matches('\\'));
|
||||||
|
|
||||||
|
if !line.ends_with('\\') {
|
||||||
|
let full_variable_cmd = variable_cmd.clone();
|
||||||
|
let (variable, command, opts) =
|
||||||
|
parse_variable_line(&full_variable_cmd).with_context(|| {
|
||||||
|
format!(
|
||||||
|
"Failed to parse variable line. See line number {} in cheatsheet `{}`",
|
||||||
|
line_nr + 1,
|
||||||
|
id
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
variable_cmd = String::from("");
|
||||||
|
self.variables
|
||||||
|
.insert_suggestion(&item.tags, variable, (String::from(command), opts));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// snippet
|
||||||
|
else {
|
||||||
|
if !(&item.snippet).is_empty() {
|
||||||
|
item.snippet.push_str(serializer::LINE_SEPARATOR);
|
||||||
|
}
|
||||||
|
item.snippet.push_str(&line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !should_break {
|
||||||
|
let _ = self.write_cmd(&item);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
9
src/prelude.rs
Normal file
9
src/prelude.rs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
pub use crate::common::prelude::*;
|
||||||
|
pub use crate::config::CONFIG; // TODO
|
||||||
|
pub use regex::Regex;
|
||||||
|
|
||||||
|
// pub use crate::common::fs::pathbuf_to_string; // TODO
|
||||||
|
|
||||||
|
pub trait Runnable {
|
||||||
|
fn run(&self) -> Result<()>;
|
||||||
|
}
|
121
src/serializer.rs
Normal file
121
src/serializer.rs
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
use crate::common::terminal;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use crate::structures::item::Item;
|
||||||
|
use crossterm::style::{style, Stylize};
|
||||||
|
use std::cmp::max;
|
||||||
|
|
||||||
|
pub fn get_widths() -> (usize, usize, usize) {
|
||||||
|
let width = terminal::width();
|
||||||
|
let tag_width_percentage = max(
|
||||||
|
CONFIG.tag_min_width(),
|
||||||
|
width * CONFIG.tag_width_percentage() / 100,
|
||||||
|
);
|
||||||
|
let comment_width_percentage = max(
|
||||||
|
CONFIG.comment_min_width(),
|
||||||
|
width * CONFIG.comment_width_percentage() / 100,
|
||||||
|
);
|
||||||
|
let snippet_width_percentage = max(
|
||||||
|
CONFIG.snippet_min_width(),
|
||||||
|
width * CONFIG.snippet_width_percentage() / 100,
|
||||||
|
);
|
||||||
|
(
|
||||||
|
usize::from(tag_width_percentage),
|
||||||
|
usize::from(comment_width_percentage),
|
||||||
|
usize::from(snippet_width_percentage),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const NEWLINE_ESCAPE_CHAR: char = '\x15';
|
||||||
|
const FIELD_SEP_ESCAPE_CHAR: char = '\x16';
|
||||||
|
pub const LINE_SEPARATOR: &str = " \x15 ";
|
||||||
|
pub const DELIMITER: &str = r" ⠀";
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
pub static ref NEWLINE_REGEX: Regex = Regex::new(r"\\\s+").expect("Invalid regex");
|
||||||
|
pub static ref VAR_REGEX: Regex = Regex::new(r"\\?<(\w[\w\d\-_]*)>").expect("Invalid regex");
|
||||||
|
pub static ref COLUMN_WIDTHS: (usize, usize, usize) = get_widths();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_new_lines(txt: String) -> String {
|
||||||
|
txt.replace(LINE_SEPARATOR, "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fix_newlines(txt: &str) -> String {
|
||||||
|
if txt.contains(NEWLINE_ESCAPE_CHAR) {
|
||||||
|
(*NEWLINE_REGEX)
|
||||||
|
.replace_all(txt.replace(LINE_SEPARATOR, " ").as_str(), "")
|
||||||
|
.to_string()
|
||||||
|
} else {
|
||||||
|
txt.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn limit_str(text: &str, length: usize) -> String {
|
||||||
|
if text.len() > length {
|
||||||
|
format!("{}…", text.chars().take(length - 1).collect::<String>())
|
||||||
|
} else {
|
||||||
|
format!("{:width$}", text, width = length)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write(item: &Item) -> String {
|
||||||
|
let (tag_width_percentage, comment_width_percentage, snippet_width_percentage) = *COLUMN_WIDTHS;
|
||||||
|
format!(
|
||||||
|
"{tags_short}{delimiter}{comment_short}{delimiter}{snippet_short}{delimiter}{tags}{delimiter}{comment}{delimiter}{snippet}{delimiter}{file_index}{delimiter}\n",
|
||||||
|
tags_short = style(limit_str(&item.tags, tag_width_percentage)).with(CONFIG.tag_color()),
|
||||||
|
comment_short = style(limit_str(&item.comment, comment_width_percentage)).with(CONFIG.comment_color()),
|
||||||
|
snippet_short = style(limit_str(&fix_newlines(&item.snippet), snippet_width_percentage)).with(CONFIG.snippet_color()),
|
||||||
|
tags = item.tags,
|
||||||
|
comment = item.comment,
|
||||||
|
delimiter = DELIMITER,
|
||||||
|
snippet = &item.snippet.trim_end_matches(LINE_SEPARATOR),
|
||||||
|
file_index = item.file_index.unwrap_or(0),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_raw(item: &Item) -> String {
|
||||||
|
format!(
|
||||||
|
"{hash}{delimiter}{tags}{delimiter}{comment}{delimiter}{icon}{delimiter}{snippet}\n",
|
||||||
|
hash = item.hash(),
|
||||||
|
tags = item.tags,
|
||||||
|
comment = item.comment,
|
||||||
|
delimiter = FIELD_SEP_ESCAPE_CHAR,
|
||||||
|
icon = item.icon.clone().unwrap_or_default(),
|
||||||
|
snippet = &item.snippet.trim_end_matches(LINE_SEPARATOR),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn raycast_deser(line: &str) -> Result<Item> {
|
||||||
|
let mut parts = line.split(FIELD_SEP_ESCAPE_CHAR);
|
||||||
|
let hash: u64 = parts
|
||||||
|
.next()
|
||||||
|
.context("no hash")?
|
||||||
|
.parse()
|
||||||
|
.context("hash not a u64")?;
|
||||||
|
let tags = parts.next().context("no tags")?.into();
|
||||||
|
let comment = parts.next().context("no comment")?.into();
|
||||||
|
let icon_str = parts.next().context("no icon")?;
|
||||||
|
let snippet = parts.next().context("no snippet")?.into();
|
||||||
|
|
||||||
|
let icon = if icon_str.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(icon_str.into())
|
||||||
|
};
|
||||||
|
|
||||||
|
let item = Item {
|
||||||
|
tags,
|
||||||
|
comment,
|
||||||
|
icon,
|
||||||
|
snippet,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
if item.hash() != hash {
|
||||||
|
dbg!(&item.hash());
|
||||||
|
dbg!(hash);
|
||||||
|
Err(anyhow!("Incorrect hash"))
|
||||||
|
} else {
|
||||||
|
Ok(item)
|
||||||
|
}
|
||||||
|
}
|
86
src/shell.rs
86
src/shell.rs
|
@ -1,86 +0,0 @@
|
||||||
use crate::config::CONFIG;
|
|
||||||
use anyhow::Result;
|
|
||||||
use std::fmt::Debug;
|
|
||||||
use std::io::{self, Read};
|
|
||||||
use std::process::Command;
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
pub const EOF: &str = "NAVIEOF";
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum Shell {
|
|
||||||
Bash,
|
|
||||||
Zsh,
|
|
||||||
Fish,
|
|
||||||
Elvish,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
|
||||||
#[error("Failed to spawn child process `bash` to execute `{command}`")]
|
|
||||||
pub struct ShellSpawnError {
|
|
||||||
command: String,
|
|
||||||
#[source]
|
|
||||||
source: anyhow::Error,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ShellSpawnError {
|
|
||||||
pub fn new<SourceError>(command: impl Into<String>, source: SourceError) -> Self
|
|
||||||
where
|
|
||||||
SourceError: std::error::Error + Sync + Send + 'static,
|
|
||||||
{
|
|
||||||
ShellSpawnError {
|
|
||||||
command: command.into(),
|
|
||||||
source: source.into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn out() -> Command {
|
|
||||||
let words_str = CONFIG.shell();
|
|
||||||
let mut words_vec = shellwords::split(&words_str).expect("empty shell command");
|
|
||||||
let mut words = words_vec.iter_mut();
|
|
||||||
let first_cmd = words.next().expect("absent shell binary");
|
|
||||||
let mut cmd = Command::new(&first_cmd);
|
|
||||||
cmd.args(words);
|
|
||||||
let dash_c = if words_str.contains("cmd.exe") { "/c" } else { "-c" };
|
|
||||||
cmd.arg(dash_c);
|
|
||||||
cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn widget_last_command() -> Result<()> {
|
|
||||||
let mut text = String::new();
|
|
||||||
io::stdin().read_to_string(&mut text)?;
|
|
||||||
|
|
||||||
let replacements = vec![("||", "ග"), ("|", "ඛ"), ("&&", "ඝ")];
|
|
||||||
|
|
||||||
let parts = shellwords::split(&text).unwrap_or_else(|_| text.split('|').map(|s| s.to_string()).collect());
|
|
||||||
|
|
||||||
for p in parts {
|
|
||||||
for (pattern, escaped) in replacements.clone() {
|
|
||||||
if p.contains(pattern) && p != pattern && p != format!("{}{}", pattern, pattern) {
|
|
||||||
let replacement = p.replace(pattern, escaped);
|
|
||||||
text = text.replace(&p, &replacement);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut extracted = text.clone();
|
|
||||||
|
|
||||||
for (pattern, _) in replacements.clone() {
|
|
||||||
let mut new_parts = text.rsplit(pattern);
|
|
||||||
if let Some(extracted_attempt) = new_parts.next() {
|
|
||||||
if extracted_attempt.len() <= extracted.len() {
|
|
||||||
extracted = extracted_attempt.to_string();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (pattern, escaped) in replacements.clone() {
|
|
||||||
text = text.replace(&escaped, pattern);
|
|
||||||
extracted = extracted.replace(&escaped, pattern);
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("{}", extracted.trim_start());
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
|
@ -1,23 +1,16 @@
|
||||||
|
use crate::common::hash::fnv;
|
||||||
use crate::finder::structures::Opts;
|
use crate::finder::structures::Opts;
|
||||||
use crate::hash::fnv;
|
use crate::prelude::*;
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
pub type Suggestion = (String, Option<Opts>);
|
pub type Suggestion = (String, Option<Opts>);
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Default)]
|
||||||
pub struct VariableMap {
|
pub struct VariableMap {
|
||||||
variables: HashMap<u64, HashMap<String, Suggestion>>,
|
variables: HashMap<u64, HashMap<String, Suggestion>>,
|
||||||
dependencies: HashMap<u64, Vec<u64>>,
|
dependencies: HashMap<u64, Vec<u64>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VariableMap {
|
impl VariableMap {
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
variables: HashMap::new(),
|
|
||||||
dependencies: HashMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn insert_dependency(&mut self, tags: &str, tags_dependency: &str) {
|
pub fn insert_dependency(&mut self, tags: &str, tags_dependency: &str) {
|
||||||
let k = fnv(&tags);
|
let k = fnv(&tags);
|
||||||
if let Some(v) = self.dependencies.get_mut(&k) {
|
if let Some(v) = self.dependencies.get_mut(&k) {
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
use crate::structures::cheat::VariableMap;
|
use crate::parser::Parser;
|
||||||
use anyhow::Result;
|
use crate::prelude::*;
|
||||||
|
|
||||||
pub trait Fetcher {
|
pub trait Fetcher {
|
||||||
fn fetch(
|
fn fetch(&self, parser: &mut Parser) -> Result<bool>;
|
||||||
&self,
|
|
||||||
stdin: &mut std::process::ChildStdin,
|
fn files(&self) -> Vec<String> {
|
||||||
files: &mut Vec<String>,
|
vec![]
|
||||||
) -> Result<Option<VariableMap>>;
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,23 @@
|
||||||
|
use crate::common::hash::fnv;
|
||||||
|
|
||||||
|
#[derive(Default, Debug)]
|
||||||
pub struct Item {
|
pub struct Item {
|
||||||
pub tags: String,
|
pub tags: String,
|
||||||
pub comment: String,
|
pub comment: String,
|
||||||
pub snippet: String,
|
pub snippet: String,
|
||||||
pub file_index: usize,
|
pub file_index: Option<usize>,
|
||||||
|
pub icon: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Item {
|
impl Item {
|
||||||
pub fn new() -> Self {
|
pub fn new(file_index: Option<usize>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
tags: "".to_string(),
|
file_index,
|
||||||
comment: "".to_string(),
|
..Default::default()
|
||||||
snippet: "".to_string(),
|
|
||||||
file_index: 0,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn hash(&self) -> u64 {
|
||||||
|
fnv(&format!("{}{}", &self.tags.trim(), &self.comment.trim()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,67 +1 @@
|
||||||
use anyhow::Result;
|
|
||||||
pub use crossterm::style;
|
|
||||||
use crossterm::terminal;
|
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
const FALLBACK_WIDTH: u16 = 80;
|
|
||||||
|
|
||||||
fn width_with_shell_out() -> Result<u16> {
|
|
||||||
use std::process::Command;
|
|
||||||
use std::process::Stdio;
|
|
||||||
|
|
||||||
let output = if cfg!(target_os = "macos") {
|
|
||||||
Command::new("stty")
|
|
||||||
.arg("-f")
|
|
||||||
.arg("/dev/stderr")
|
|
||||||
.arg("size")
|
|
||||||
.stderr(Stdio::inherit())
|
|
||||||
.output()?
|
|
||||||
} else {
|
|
||||||
Command::new("stty")
|
|
||||||
.arg("size")
|
|
||||||
.arg("-F")
|
|
||||||
.arg("/dev/stderr")
|
|
||||||
.stderr(Stdio::inherit())
|
|
||||||
.output()?
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(0) = output.status.code() {
|
|
||||||
let stdout = String::from_utf8(output.stdout).expect("Invalid utf8 output from stty");
|
|
||||||
let mut data = stdout.split_whitespace();
|
|
||||||
data.next();
|
|
||||||
return data
|
|
||||||
.next()
|
|
||||||
.expect("Not enough data")
|
|
||||||
.parse::<u16>()
|
|
||||||
.map_err(|_| anyhow!("Invalid width"));
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(anyhow!("Invalid status code"))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn width() -> u16 {
|
|
||||||
if let Ok((w, _)) = terminal::size() {
|
|
||||||
w
|
|
||||||
} else {
|
|
||||||
width_with_shell_out().unwrap_or(FALLBACK_WIDTH)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_ansi(ansi: &str) -> Option<style::Color> {
|
|
||||||
style::Color::parse_ansi(&format!("5;{}", ansi))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Color(pub style::Color);
|
|
||||||
|
|
||||||
impl FromStr for Color {
|
|
||||||
type Err = &'static str;
|
|
||||||
|
|
||||||
fn from_str(ansi: &str) -> Result<Self, Self::Err> {
|
|
||||||
if let Some(c) = parse_ansi(ansi) {
|
|
||||||
Ok(Color(c))
|
|
||||||
} else {
|
|
||||||
Err("Invalid color")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
25
src/ui.rs
25
src/ui.rs
|
@ -1,25 +0,0 @@
|
||||||
use crate::config::CONFIG;
|
|
||||||
use crate::terminal;
|
|
||||||
pub use crate::terminal::style::style;
|
|
||||||
use std::cmp::max;
|
|
||||||
|
|
||||||
pub fn get_widths() -> (usize, usize, usize) {
|
|
||||||
let width = terminal::width();
|
|
||||||
let tag_width_percentage = max(
|
|
||||||
CONFIG.tag_min_width(),
|
|
||||||
width * CONFIG.tag_width_percentage() / 100,
|
|
||||||
);
|
|
||||||
let comment_width_percentage = max(
|
|
||||||
CONFIG.comment_min_width(),
|
|
||||||
width * CONFIG.comment_width_percentage() / 100,
|
|
||||||
);
|
|
||||||
let snippet_width_percentage = max(
|
|
||||||
CONFIG.snippet_min_width(),
|
|
||||||
width * CONFIG.snippet_width_percentage() / 100,
|
|
||||||
);
|
|
||||||
(
|
|
||||||
usize::from(tag_width_percentage),
|
|
||||||
usize::from(comment_width_percentage),
|
|
||||||
usize::from(snippet_width_percentage),
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,48 +1,30 @@
|
||||||
use crate::actor;
|
use crate::parser::Parser;
|
||||||
use crate::config::CONFIG;
|
use crate::prelude::*;
|
||||||
use crate::extractor;
|
use crate::structures::fetcher;
|
||||||
use crate::finder::structures::Opts as FinderOpts;
|
|
||||||
use crate::finder::Finder;
|
|
||||||
use crate::parser;
|
|
||||||
use crate::structures::cheat::VariableMap;
|
|
||||||
use anyhow::Context;
|
|
||||||
use anyhow::Result;
|
|
||||||
|
|
||||||
pub fn main() -> Result<()> {
|
pub fn populate_cheatsheet(parser: &mut Parser) -> Result<()> {
|
||||||
let config = &CONFIG;
|
|
||||||
let opts = FinderOpts::snippet_default();
|
|
||||||
|
|
||||||
let (raw_selection, variables, files) = config
|
|
||||||
.finder()
|
|
||||||
.call(opts, |stdin, _| {
|
|
||||||
populate_cheatsheet(stdin)?;
|
|
||||||
Ok(Some(VariableMap::new()))
|
|
||||||
})
|
|
||||||
.context("Failed getting selection and variables from finder")?;
|
|
||||||
|
|
||||||
let extractions = extractor::extract_from_selections(&raw_selection, config.best_match());
|
|
||||||
|
|
||||||
if extractions.is_err() {
|
|
||||||
return main();
|
|
||||||
}
|
|
||||||
|
|
||||||
actor::act(extractions, files, variables)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn populate_cheatsheet(stdin: &mut std::process::ChildStdin) -> Result<()> {
|
|
||||||
let cheatsheet = include_str!("../docs/navi.cheat");
|
let cheatsheet = include_str!("../docs/navi.cheat");
|
||||||
|
|
||||||
parser::read_lines(
|
parser.read_lines(
|
||||||
cheatsheet.split('\n').into_iter().map(|s| Ok(s.to_string())),
|
cheatsheet.split('\n').into_iter().map(|s| Ok(s.to_string())),
|
||||||
"welcome",
|
"welcome",
|
||||||
0,
|
|
||||||
&mut VariableMap::new(),
|
|
||||||
&mut Default::default(),
|
|
||||||
stdin,
|
|
||||||
None,
|
|
||||||
None,
|
None,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct Fetcher {}
|
||||||
|
|
||||||
|
impl Fetcher {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fetcher::Fetcher for Fetcher {
|
||||||
|
fn fetch(&self, parser: &mut Parser) -> Result<bool> {
|
||||||
|
populate_cheatsheet(parser)?;
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,52 +0,0 @@
|
||||||
use crate::config::CONFIG;
|
|
||||||
use crate::structures::item::Item;
|
|
||||||
use crate::ui;
|
|
||||||
use crossterm::style::Stylize;
|
|
||||||
use regex::Regex;
|
|
||||||
|
|
||||||
const NEWLINE_ESCAPE_CHAR: char = '\x15';
|
|
||||||
pub const LINE_SEPARATOR: &str = " \x15 ";
|
|
||||||
pub const DELIMITER: &str = r" ⠀";
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
pub static ref NEWLINE_REGEX: Regex = Regex::new(r"\\\s+").expect("Invalid regex");
|
|
||||||
pub static ref VAR_REGEX: Regex = Regex::new(r"\\?<(\w[\w\d\-_]*)>").expect("Invalid regex");
|
|
||||||
pub static ref COLUMN_WIDTHS: (usize, usize, usize) = ui::get_widths();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_new_lines(txt: String) -> String {
|
|
||||||
txt.replace(LINE_SEPARATOR, "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn fix_newlines(txt: &str) -> String {
|
|
||||||
if txt.contains(NEWLINE_ESCAPE_CHAR) {
|
|
||||||
(*NEWLINE_REGEX)
|
|
||||||
.replace_all(txt.replace(LINE_SEPARATOR, " ").as_str(), "")
|
|
||||||
.to_string()
|
|
||||||
} else {
|
|
||||||
txt.to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn limit_str(text: &str, length: usize) -> String {
|
|
||||||
if text.len() > length {
|
|
||||||
format!("{}…", text.chars().take(length - 1).collect::<String>())
|
|
||||||
} else {
|
|
||||||
format!("{:width$}", text, width = length)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn write(item: &Item) -> String {
|
|
||||||
let (tag_width_percentage, comment_width_percentage, snippet_width_percentage) = *COLUMN_WIDTHS;
|
|
||||||
format!(
|
|
||||||
"{tags_short}{delimiter}{comment_short}{delimiter}{snippet_short}{delimiter}{tags}{delimiter}{comment}{delimiter}{snippet}{delimiter}{file_index}{delimiter}\n",
|
|
||||||
tags_short = ui::style(limit_str(&item.tags, tag_width_percentage)).with(CONFIG.tag_color()),
|
|
||||||
comment_short = ui::style(limit_str(&item.comment, comment_width_percentage)).with(CONFIG.comment_color()),
|
|
||||||
snippet_short = ui::style(limit_str(&fix_newlines(&item.snippet), snippet_width_percentage)).with(CONFIG.snippet_color()),
|
|
||||||
tags = item.tags,
|
|
||||||
comment = item.comment,
|
|
||||||
delimiter = DELIMITER,
|
|
||||||
snippet = &item.snippet.trim_end_matches(LINE_SEPARATOR),
|
|
||||||
file_index = item.file_index,
|
|
||||||
)
|
|
||||||
}
|
|
Loading…
Reference in a new issue