mirror of
https://github.com/denisidoro/navi
synced 2024-11-24 20:43:06 +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
|
||||
- name: Build
|
||||
id: build
|
||||
run: scripts/release ${{ matrix.target }}
|
||||
run: scripts/dot rust release ${{ matrix.target }}
|
||||
- name: Get the version
|
||||
id: get_version
|
||||
run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//}
|
||||
|
|
86
.github/workflows/ci.yml
vendored
86
.github/workflows/ci.yml
vendored
|
@ -9,25 +9,25 @@ on: [push]
|
|||
name: CI
|
||||
|
||||
jobs:
|
||||
check:
|
||||
name: Check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v2
|
||||
# check:
|
||||
# name: Check
|
||||
# runs-on: ubuntu-latest
|
||||
# steps:
|
||||
# - name: Checkout sources
|
||||
# uses: actions/checkout@v2
|
||||
|
||||
- name: Install stable toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
# - name: Install stable toolchain
|
||||
# uses: actions-rs/toolchain@v1
|
||||
# with:
|
||||
# profile: minimal
|
||||
# toolchain: stable
|
||||
# override: true
|
||||
|
||||
- name: Run cargo check
|
||||
uses: actions-rs/cargo@v1
|
||||
continue-on-error: false
|
||||
with:
|
||||
command: check
|
||||
# - name: Run cargo check
|
||||
# uses: actions-rs/cargo@v1
|
||||
# continue-on-error: false
|
||||
# with:
|
||||
# command: check
|
||||
|
||||
test:
|
||||
name: Tests
|
||||
|
@ -64,21 +64,7 @@ jobs:
|
|||
command: test
|
||||
|
||||
- name: Install deps
|
||||
run: |
|
||||
_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
|
||||
run: ./scripts/dot pkg add git bash npm tmux
|
||||
|
||||
- name: Install fzf
|
||||
run: git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf; yes | ~/.fzf/install;
|
||||
|
@ -89,12 +75,12 @@ jobs:
|
|||
- name: Run bash tests
|
||||
run: ./tests/run
|
||||
|
||||
# lints:
|
||||
# name: Lints
|
||||
# runs-on: ubuntu-latest
|
||||
# steps:
|
||||
# - name: Checkout sources
|
||||
# uses: actions/checkout@v2
|
||||
lints:
|
||||
name: Lints
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v2
|
||||
|
||||
# - name: Install stable toolchain
|
||||
# uses: actions-rs/toolchain@v1
|
||||
|
@ -104,16 +90,16 @@ jobs:
|
|||
# override: true
|
||||
# components: rustfmt, clippy
|
||||
|
||||
# - name: Run cargo fmt
|
||||
# uses: actions-rs/cargo@v1
|
||||
# continue-on-error: false
|
||||
# with:
|
||||
# command: fmt
|
||||
# args: --all -- --check
|
||||
- name: Run cargo fmt
|
||||
uses: actions-rs/cargo@v1
|
||||
continue-on-error: false
|
||||
with:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
|
||||
# - name: Run cargo clippy
|
||||
# uses: actions-rs/cargo@v1
|
||||
# continue-on-error: false
|
||||
# with:
|
||||
# command: clippy
|
||||
# args: -- -D warnings
|
||||
- name: Run cargo clippy
|
||||
uses: actions-rs/cargo@v1
|
||||
continue-on-error: false
|
||||
with:
|
||||
command: clippy
|
||||
args: -- -D warnings
|
||||
|
|
26
Cargo.lock
generated
26
Cargo.lock
generated
|
@ -54,9 +54,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
|||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "3.2.8"
|
||||
version = "3.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "190814073e85d238f31ff738fcb0bf6910cedeb73376c87cd69291028966fd83"
|
||||
checksum = "54635806b078b7925d6e36810b1755f2a4b5b4d57560432c1ecf60bcbe10602b"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"bitflags",
|
||||
|
@ -304,7 +304,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "navi"
|
||||
version = "2.20.1"
|
||||
version = "2.21.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
|
@ -501,9 +501,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.5.6"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1"
|
||||
checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
|
@ -512,9 +512,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.26"
|
||||
version = "0.6.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64"
|
||||
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
|
||||
|
||||
[[package]]
|
||||
name = "remove_dir_all"
|
||||
|
@ -561,18 +561,18 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
|||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.138"
|
||||
version = "1.0.140"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1578c6245786b9d168c5447eeacfb96856573ca56c9d68fdcf394be134882a47"
|
||||
checksum = "fc855a42c7967b7c369eb5860f7164ef1f6f81c20c7cc1141f2a604e18723b03"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.138"
|
||||
version = "1.0.140"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "023e9b1467aef8a10fb88f25611870ada9800ef7e22afce356bb0d2387b6f27c"
|
||||
checksum = "6f2122636b9fe3b81f1cb25099fcf2d3f542cdb1d45940d56c713158884a05da"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -581,9 +581,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_yaml"
|
||||
version = "0.8.24"
|
||||
version = "0.8.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "707d15895415db6628332b737c838b88c598522e4dc70647e59b72312924aebc"
|
||||
checksum = "578a7433b776b56a35785ed5ce9a7e777ac0598aac5a6dd1b4b18a307c7fc71b"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"ryu",
|
||||
|
|
10
Cargo.toml
10
Cargo.toml
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "navi"
|
||||
version = "2.20.1"
|
||||
version = "2.21.0"
|
||||
authors = ["Denis Isidoro <denis_isidoro@live.com>"]
|
||||
edition = "2021"
|
||||
description = "An interactive cheatsheet tool for the command-line"
|
||||
|
@ -19,8 +19,8 @@ disable-repo-management = []
|
|||
travis-ci = { repository = "denisidoro/navi", branch = "master" }
|
||||
|
||||
[dependencies]
|
||||
regex = { version = "1.5.6", default-features = false, features = ["std", "unicode-perl"] }
|
||||
clap = { version = "3.2.8", features = ["derive", "cargo"] }
|
||||
regex = { version = "1.6.0", default-features = false, features = ["std", "unicode-perl"] }
|
||||
clap = { version = "3.2.14", features = ["derive", "cargo"] }
|
||||
crossterm = "0.24.0"
|
||||
lazy_static = "1.4.0"
|
||||
directories-next = "2.0.0"
|
||||
|
@ -31,8 +31,8 @@ thiserror = "1.0.31"
|
|||
strip-ansi-escapes = "0.1.1"
|
||||
edit = "0.1.4"
|
||||
remove_dir_all = "0.7.0"
|
||||
serde = { version = "1.0.138", features = ["derive"] }
|
||||
serde_yaml = "0.8.24"
|
||||
serde = { version = "1.0.140", features = ["derive"] }
|
||||
serde_yaml = "0.8.26"
|
||||
|
||||
[lib]
|
||||
name = "navi"
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
[toolchain]
|
||||
channel = "1.56.0"
|
||||
channel = "1.62.0"
|
||||
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::structures::cheat::VariableMap;
|
||||
use crate::parser::Parser;
|
||||
use crate::prelude::*;
|
||||
|
||||
use crate::structures::fetcher;
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
use std::collections::HashSet;
|
||||
use std::process::{self, Command, Stdio};
|
||||
use std::process::{self, Command};
|
||||
|
||||
fn map_line(line: &str) -> 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()
|
||||
}
|
||||
|
||||
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> {
|
||||
let args = ["-qO-", &format!("cheat.sh/{}", query)];
|
||||
|
||||
|
@ -109,12 +78,23 @@ impl Fetcher {
|
|||
}
|
||||
|
||||
impl fetcher::Fetcher for Fetcher {
|
||||
fn fetch(
|
||||
&self,
|
||||
stdin: &mut std::process::ChildStdin,
|
||||
_files: &mut Vec<String>,
|
||||
) -> Result<Option<VariableMap>> {
|
||||
let cheat = fetch(&self.query)?;
|
||||
read_all(&self.query, &cheat, stdin)
|
||||
fn fetch(&self, parser: &mut Parser) -> Result<bool> {
|
||||
let cheat = &fetch(&self.query)?;
|
||||
|
||||
if cheat.starts_with("Unknown topic.") {
|
||||
eprintln!(
|
||||
"`{}` not found in cheatsh.
|
||||
|
||||
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::structures::cheat::VariableMap;
|
||||
use crate::parser::Parser;
|
||||
use crate::prelude::*;
|
||||
use crate::structures::fetcher;
|
||||
use anyhow::{Context, Result};
|
||||
use regex::Regex;
|
||||
use std::collections::HashSet;
|
||||
|
||||
use std::process::{self, Command, Stdio};
|
||||
|
||||
lazy_static! {
|
||||
|
@ -58,26 +54,6 @@ fn markdown_lines(query: &str, markdown: &str) -> impl Iterator<Item = Result<St
|
|||
.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> {
|
||||
let args = [query, "--markdown"];
|
||||
|
||||
|
@ -148,12 +124,9 @@ impl Fetcher {
|
|||
}
|
||||
|
||||
impl fetcher::Fetcher for Fetcher {
|
||||
fn fetch(
|
||||
&self,
|
||||
stdin: &mut std::process::ChildStdin,
|
||||
_files: &mut Vec<String>,
|
||||
) -> Result<Option<VariableMap>> {
|
||||
fn fetch(&self, parser: &mut Parser) -> Result<bool> {
|
||||
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::CONFIG;
|
||||
use crate::env_var;
|
||||
use crate::extractor;
|
||||
use crate::finder::structures::{Opts as FinderOpts, SuggestionType};
|
||||
use crate::finder::Finder;
|
||||
use crate::fs;
|
||||
use crate::shell;
|
||||
use crate::shell::ShellSpawnError;
|
||||
use crate::prelude::*;
|
||||
use crate::serializer;
|
||||
use crate::structures::cheat::{Suggestion, VariableMap};
|
||||
use crate::writer;
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
use shell::EOF;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use std::process::Stdio;
|
||||
|
||||
fn prompt_finder(
|
||||
|
@ -128,13 +123,13 @@ fn prompt_finder(
|
|||
opts.suggestion_type = SuggestionType::Disabled;
|
||||
};
|
||||
|
||||
let (output, _, _) = CONFIG
|
||||
let (output, _) = CONFIG
|
||||
.finder()
|
||||
.call(opts, |stdin, _| {
|
||||
.call(opts, |stdin| {
|
||||
stdin
|
||||
.write_all(suggestions.as_bytes())
|
||||
.context("Could not write to finder's stdin")?;
|
||||
Ok(None)
|
||||
Ok(())
|
||||
})
|
||||
.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> {
|
||||
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);
|
||||
|
||||
for bracketed_variable_name in variables_found {
|
||||
|
@ -213,7 +211,7 @@ pub fn act(
|
|||
)
|
||||
.context("Failed to replace variables from snippet")?;
|
||||
s = with_absolute_path(s);
|
||||
s = writer::with_new_lines(s);
|
||||
s = serializer::with_new_lines(s);
|
||||
s
|
||||
};
|
||||
|
|
@ -1,7 +1,5 @@
|
|||
use crate::writer;
|
||||
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
use crate::prelude::*;
|
||||
use crate::serializer;
|
||||
|
||||
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
|
||||
.next()
|
||||
.context("No more parts in `selections`")?
|
||||
.split(writer::DELIMITER)
|
||||
.split(serializer::DELIMITER)
|
||||
.skip(3);
|
||||
|
||||
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 map_expand() -> Result<()> {
|
||||
pub fn expand() -> Result<()> {
|
||||
let cmd = r#"sed -e 's/^.*$/"&"/' | tr '\n' ' '"#;
|
||||
shell::out()
|
||||
.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::finder::structures::{Opts as FinderOpts, SuggestionType};
|
||||
use crate::finder::{Finder, FinderChoice};
|
||||
use crate::fs::pathbuf_to_string;
|
||||
use crate::git;
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
use crate::finder::FinderChoice;
|
||||
use crate::prelude::*;
|
||||
use std::fs;
|
||||
use std::io::Write;
|
||||
use std::path;
|
||||
|
||||
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()
|
||||
};
|
||||
|
||||
let (response, _, _) = finder
|
||||
.call(opts, |stdin, _| {
|
||||
let (response, _) = finder
|
||||
.call(opts, |stdin| {
|
||||
stdin
|
||||
.write_all(b"Yes\nNo")
|
||||
.context("Unable to writer alternatives")?;
|
||||
Ok(None)
|
||||
Ok(())
|
||||
})
|
||||
.context("Unable to get response")?;
|
||||
|
||||
if response.to_lowercase().starts_with('y') {
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
Ok(response.to_lowercase().starts_with('y'))
|
||||
}
|
||||
|
||||
pub fn main(uri: String) -> Result<()> {
|
||||
|
@ -41,14 +33,14 @@ pub fn main(uri: String) -> Result<()> {
|
|||
|
||||
let cheat_pathbuf = filesystem::default_cheat_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);
|
||||
filesystem::create_dir(&tmp_pathbuf)?;
|
||||
|
||||
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))?;
|
||||
|
||||
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 {
|
||||
all_files
|
||||
} else {
|
||||
let (files, _, _) = finder
|
||||
.call(opts, |stdin, _| {
|
||||
let (files, _) = finder
|
||||
.call(opts, |stdin| {
|
||||
stdin
|
||||
.write_all(all_files.as_bytes())
|
||||
.context("Unable to prompt cheats to import")?;
|
||||
Ok(None)
|
||||
Ok(())
|
||||
})
|
||||
.context("Failed to get cheatsheet files from finder")?;
|
||||
files
|
||||
|
@ -96,13 +88,8 @@ pub fn main(uri: String) -> Result<()> {
|
|||
p
|
||||
};
|
||||
fs::create_dir_all(&to_folder).unwrap_or(());
|
||||
fs::copy(&from, &to).with_context(|| {
|
||||
format!(
|
||||
"Failed to copy `{}` to `{}`",
|
||||
pathbuf_to_string(&from).expect("unable to parse {from}"),
|
||||
pathbuf_to_string(&to).expect("unable to parse {to}")
|
||||
)
|
||||
})?;
|
||||
fs::copy(&from, &to)
|
||||
.with_context(|| format!("Failed to copy `{}` to `{}`", &from.to_string(), &to.to_string()))?;
|
||||
}
|
||||
|
||||
filesystem::remove_dir(&tmp_pathbuf)?;
|
||||
|
@ -110,7 +97,7 @@ pub fn main(uri: String) -> Result<()> {
|
|||
eprintln!(
|
||||
"The following .cheat files were imported successfully:\n{}\n\nThey are now located at {}",
|
||||
files,
|
||||
pathbuf_to_string(&to_folder)?
|
||||
to_folder.to_string()
|
||||
);
|
||||
|
||||
Ok(())
|
|
@ -1,13 +1,9 @@
|
|||
use crate::config::CONFIG;
|
||||
use crate::filesystem;
|
||||
use crate::finder::structures::{Opts as FinderOpts, SuggestionType};
|
||||
use crate::finder::Finder;
|
||||
use crate::fs::pathbuf_to_string;
|
||||
use crate::git;
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::common::git;
|
||||
use crate::prelude::*;
|
||||
use std::fs;
|
||||
use std::io::Write;
|
||||
|
||||
pub fn main() -> Result<String> {
|
||||
let finder = CONFIG.finder();
|
||||
|
@ -18,13 +14,13 @@ pub fn main() -> Result<String> {
|
|||
p
|
||||
};
|
||||
|
||||
let repo_path_str = pathbuf_to_string(&repo_pathbuf)?;
|
||||
let repo_path_str = &repo_pathbuf.to_string();
|
||||
|
||||
let _ = filesystem::remove_dir(&repo_pathbuf);
|
||||
filesystem::create_dir(&repo_pathbuf)?;
|
||||
|
||||
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))?;
|
||||
|
||||
let feature_repos_file = {
|
||||
|
@ -41,12 +37,12 @@ pub fn main() -> Result<String> {
|
|||
..Default::default()
|
||||
};
|
||||
|
||||
let (repo, _, _) = finder
|
||||
.call(opts, |stdin, _| {
|
||||
let (repo, _) = finder
|
||||
.call(opts, |stdin| {
|
||||
stdin
|
||||
.write_all(repos.as_bytes())
|
||||
.context("Unable to prompt featured repositories")?;
|
||||
Ok(None)
|
||||
Ok(())
|
||||
})
|
||||
.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 anyhow::Result;
|
||||
use crate::common::shell::{self, ShellSpawnError, EOF};
|
||||
use crate::prelude::*;
|
||||
|
||||
pub fn copy(text: String) -> Result<()> {
|
||||
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 std::fmt::Debug;
|
||||
use std::ffi::OsStr;
|
||||
use std::fs::{self, create_dir_all, File};
|
||||
use std::io::{self, BufRead};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::io;
|
||||
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)]
|
||||
#[error("Invalid path `{0}`")]
|
||||
pub struct InvalidPath(pub PathBuf);
|
||||
|
@ -21,7 +43,7 @@ pub struct UnreadableDir {
|
|||
|
||||
pub fn open(filename: &Path) -> Result<File> {
|
||||
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)
|
||||
})
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
use crate::shell::ShellSpawnError;
|
||||
use anyhow::{Context, Result};
|
||||
use crate::common::shell::ShellSpawnError;
|
||||
use crate::prelude::*;
|
||||
use std::process::Command;
|
||||
|
||||
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 shell::EOF;
|
||||
|
|
@ -1,58 +1,9 @@
|
|||
use crate::commands;
|
||||
use crate::finder::FinderChoice;
|
||||
use crate::handler::func::Func;
|
||||
use crate::handler::info::Info;
|
||||
use crate::shell::Shell;
|
||||
|
||||
use crate::prelude::*;
|
||||
use clap::{crate_version, AppSettings, Parser, Subcommand};
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
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)]
|
||||
#[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 {
|
||||
/// [Experimental] Calls internal functions
|
||||
Fn {
|
||||
/// 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>,
|
||||
},
|
||||
Fn(commands::func::Input),
|
||||
/// Manages cheatsheet repositories
|
||||
#[cfg(not(feature = "disable-repo-management"))]
|
||||
Repo {
|
||||
#[clap(subcommand)]
|
||||
cmd: RepoCommand,
|
||||
},
|
||||
Repo(commands::repo::Input),
|
||||
/// Used for fzf's preview window when selecting snippets
|
||||
#[clap(setting = AppSettings::Hidden)]
|
||||
Preview {
|
||||
/// Selection line
|
||||
line: String,
|
||||
},
|
||||
Preview(commands::preview::Input),
|
||||
/// Used for fzf's preview window when selecting variable suggestions
|
||||
#[clap(setting = AppSettings::Hidden)]
|
||||
PreviewVar {
|
||||
/// Selection line
|
||||
selection: String,
|
||||
/// Query match
|
||||
query: String,
|
||||
/// Typed text
|
||||
variable: String,
|
||||
},
|
||||
PreviewVar(commands::preview::var::Input),
|
||||
/// Used for fzf's preview window when selecting variable suggestions
|
||||
#[clap(setting = AppSettings::Hidden)]
|
||||
PreviewVarStdin,
|
||||
PreviewVarStdin(commands::preview::var_stdin::Input),
|
||||
/// Outputs shell widget source code
|
||||
Widget {
|
||||
#[clap(possible_values = WIDGET_POSSIBLE_VALUES, ignore_case = true, default_value = "bash")]
|
||||
shell: Shell,
|
||||
},
|
||||
Widget(commands::shell::Input),
|
||||
/// Shows info
|
||||
Info {
|
||||
#[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,
|
||||
Info(commands::info::Input),
|
||||
}
|
||||
|
||||
pub enum Source {
|
||||
Filesystem(Option<String>, Option<String>),
|
||||
Filesystem(Option<String>),
|
||||
Tldr(String),
|
||||
Cheats(String),
|
||||
Welcome,
|
||||
}
|
||||
|
||||
pub enum Action {
|
||||
|
@ -211,6 +128,7 @@ pub enum Action {
|
|||
Execute,
|
||||
}
|
||||
|
||||
/*
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@ -243,3 +161,4 @@ mod tests {
|
|||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
use crate::env_var;
|
||||
|
||||
use crate::finder::FinderChoice;
|
||||
|
||||
use std::str::FromStr;
|
||||
use crate::prelude::*;
|
||||
|
||||
pub struct EnvConfig {
|
||||
pub config_yaml: Option<String>,
|
||||
|
|
|
@ -2,11 +2,17 @@ mod cli;
|
|||
mod env;
|
||||
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::terminal::style::Color;
|
||||
use crate::structures::fetcher::Fetcher;
|
||||
use crate::welcome;
|
||||
pub use cli::*;
|
||||
|
||||
use crossterm::style::Color;
|
||||
use env::EnvConfig;
|
||||
use yaml::YamlConfig;
|
||||
|
||||
|
@ -45,8 +51,23 @@ impl Config {
|
|||
Source::Tldr(query)
|
||||
} else if let Some(query) = self.clap.cheatsh.clone() {
|
||||
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(), self.tag_rules())
|
||||
Source::Filesystem(self.path())
|
||||
}
|
||||
} else {
|
||||
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 crate::common::fs;
|
||||
use crate::filesystem::default_config_pathbuf;
|
||||
use crate::finder::FinderChoice;
|
||||
use crate::fs;
|
||||
use crate::terminal::style::Color as TerminalColor;
|
||||
use anyhow::Result;
|
||||
use serde::{de, Deserialize};
|
||||
use std::convert::TryFrom;
|
||||
use std::io::BufReader;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use crate::prelude::*;
|
||||
use crossterm::style::Color as TerminalColor;
|
||||
use serde::de;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
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::set_var as set;
|
||||
pub use env::var as get;
|
||||
use std::env;
|
||||
use std::str::FromStr;
|
||||
|
||||
pub const PREVIEW_INITIAL_SNIPPET: &str = "NAVI_PREVIEW_INITIAL_SNIPPET";
|
||||
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;
|
||||
pub use crate::fs::{
|
||||
create_dir, exe_string, pathbuf_to_string, read_lines, remove_dir, InvalidPath, UnreadableDir,
|
||||
};
|
||||
use crate::parser;
|
||||
use crate::structures::cheat::VariableMap;
|
||||
use crate::parser::Parser;
|
||||
use crate::prelude::*;
|
||||
|
||||
use crate::structures::fetcher;
|
||||
use anyhow::Result;
|
||||
use directories_next::BaseDirs;
|
||||
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;
|
||||
|
||||
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 {
|
||||
Ok(p)
|
||||
} 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)
|
||||
}
|
||||
|
||||
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 {
|
||||
let re = Regex::new(r#"\$\{?[a-zA-Z_][a-zA-Z_0-9]*"#).unwrap();
|
||||
let mut newtext = paths.to_string();
|
||||
|
@ -111,63 +102,29 @@ fn interpolate_paths(paths: String) -> String {
|
|||
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 {
|
||||
path: Option<String>,
|
||||
allowlist: Option<Vec<String>>,
|
||||
denylist: Option<Vec<String>>,
|
||||
files: RefCell<Vec<String>>,
|
||||
}
|
||||
|
||||
impl Fetcher {
|
||||
pub fn new(path: Option<String>, tag_rules: Option<String>) -> Self {
|
||||
let (allowlist, denylist) = gen_lists(tag_rules);
|
||||
pub fn new(path: Option<String>) -> Self {
|
||||
Self {
|
||||
path,
|
||||
allowlist,
|
||||
denylist,
|
||||
files: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fetcher::Fetcher for Fetcher {
|
||||
fn fetch(
|
||||
&self,
|
||||
stdin: &mut std::process::ChildStdin,
|
||||
files: &mut Vec<String>,
|
||||
) -> Result<Option<VariableMap>> {
|
||||
let mut variables = VariableMap::new();
|
||||
fn fetch(&self, parser: &mut Parser) -> Result<bool> {
|
||||
let mut found_something = false;
|
||||
let mut visited_lines = HashSet::new();
|
||||
|
||||
let path = self.path.clone();
|
||||
let paths = cheat_paths(path);
|
||||
|
||||
if paths.is_err() {
|
||||
return Ok(None);
|
||||
return Ok(false);
|
||||
};
|
||||
|
||||
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 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 {
|
||||
let interpolated_folder = match &home {
|
||||
|
@ -184,21 +143,12 @@ impl fetcher::Fetcher for Fetcher {
|
|||
};
|
||||
let folder_pathbuf = PathBuf::from(interpolated_folder);
|
||||
for file in all_cheat_files(&folder_pathbuf) {
|
||||
files.push(file.clone());
|
||||
let index = files.len() - 1;
|
||||
self.files.borrow_mut().push(file.clone());
|
||||
let index = self.files.borrow().len() - 1;
|
||||
let read_file_result = {
|
||||
let path = PathBuf::from(&file);
|
||||
let lines = read_lines(&path)?;
|
||||
parser::read_lines(
|
||||
lines,
|
||||
&file,
|
||||
index,
|
||||
&mut variables,
|
||||
&mut visited_lines,
|
||||
stdin,
|
||||
self.allowlist.as_ref(),
|
||||
self.denylist.as_ref(),
|
||||
)
|
||||
parser.read_lines(lines, &file, Some(index))
|
||||
};
|
||||
|
||||
if read_file_result.is_ok() && !found_something {
|
||||
|
@ -207,11 +157,11 @@ impl fetcher::Fetcher for Fetcher {
|
|||
}
|
||||
}
|
||||
|
||||
if !found_something {
|
||||
return Ok(None);
|
||||
Ok(found_something)
|
||||
}
|
||||
|
||||
Ok(Some(variables))
|
||||
fn files(&self) -> Vec<String> {
|
||||
self.files.borrow().clone()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -279,7 +229,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_default_config_pathbuf() {
|
||||
let base_dirs = BaseDirs::new()
|
||||
.ok_or(anyhow!("bad"))
|
||||
.ok_or_else(|| anyhow!("bad"))
|
||||
.expect("could not determine base directories");
|
||||
|
||||
let expected = {
|
||||
|
@ -297,7 +247,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_default_cheat_pathbuf() {
|
||||
let base_dirs = BaseDirs::new()
|
||||
.ok_or(anyhow!("bad"))
|
||||
.ok_or_else(|| anyhow!("bad"))
|
||||
.expect("could not determine base directories");
|
||||
|
||||
let expected = {
|
||||
|
|
|
@ -1,18 +1,16 @@
|
|||
use crate::config::CONFIG;
|
||||
use crate::structures::cheat::VariableMap;
|
||||
use crate::writer;
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
use crate::prelude::*;
|
||||
use crate::serializer;
|
||||
|
||||
use std::io::Write;
|
||||
use std::process::{self, Output};
|
||||
use std::process::{Command, Stdio};
|
||||
mod post;
|
||||
pub mod structures;
|
||||
pub use post::process;
|
||||
use serde::Deserialize;
|
||||
use std::str::FromStr;
|
||||
use structures::Opts;
|
||||
use structures::SuggestionType;
|
||||
|
||||
mod post;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize)]
|
||||
pub enum FinderChoice {
|
||||
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> {
|
||||
let text = match out.status.code() {
|
||||
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)
|
||||
}
|
||||
|
||||
impl Finder for FinderChoice {
|
||||
fn call<F>(&self, finder_opts: Opts, stdin_fn: F) -> Result<(String, Option<VariableMap>, Vec<String>)>
|
||||
impl FinderChoice {
|
||||
pub fn call<F, R>(&self, finder_opts: Opts, stdin_fn: F) -> Result<(String, R)>
|
||||
where
|
||||
F: Fn(&mut process::ChildStdin, &mut Vec<String>) -> Result<Option<VariableMap>>,
|
||||
F: Fn(&mut dyn Write) -> Result<R>,
|
||||
{
|
||||
let finder_str = match self {
|
||||
Self::Fzf => "fzf",
|
||||
|
@ -86,7 +78,7 @@ impl Finder for FinderChoice {
|
|||
"--with-nth",
|
||||
"1,2,3",
|
||||
"--delimiter",
|
||||
writer::DELIMITER.to_string().as_str(),
|
||||
serializer::DELIMITER.to_string().as_str(),
|
||||
"--ansi",
|
||||
"--bind",
|
||||
format!("ctrl-j:down,ctrl-k:up{}", bindings).as_str(),
|
||||
|
@ -188,12 +180,13 @@ impl Finder for FinderChoice {
|
|||
.as_mut()
|
||||
.ok_or_else(|| anyhow!("Unable to acquire stdin of finder"))?;
|
||||
|
||||
let mut files = vec![];
|
||||
let result_map = stdin_fn(stdin, &mut files).context("Failed to pass data to finder")?;
|
||||
let mut writer: Box<&mut dyn Write> = Box::new(stdin);
|
||||
|
||||
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 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::shell;
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
use crate::prelude::*;
|
||||
use shell::EOF;
|
||||
use std::process::Stdio;
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::config::CONFIG;
|
||||
use crate::filesystem;
|
||||
use crate::prelude::*;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
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]
|
||||
extern crate lazy_static;
|
||||
#[macro_use]
|
||||
extern crate anyhow;
|
||||
// #[macro_use]
|
||||
// extern crate anyhow;
|
||||
|
||||
mod actor;
|
||||
mod cheat_variable;
|
||||
mod cheatsh;
|
||||
mod clipboard;
|
||||
mod clients;
|
||||
mod commands;
|
||||
mod common;
|
||||
mod config;
|
||||
mod env_var;
|
||||
mod extractor;
|
||||
mod filesystem;
|
||||
mod finder;
|
||||
mod fs;
|
||||
mod git;
|
||||
mod handler;
|
||||
mod hash;
|
||||
mod parser;
|
||||
mod shell;
|
||||
mod prelude;
|
||||
mod serializer;
|
||||
mod structures;
|
||||
mod terminal;
|
||||
mod tldr;
|
||||
mod ui;
|
||||
mod url;
|
||||
mod welcome;
|
||||
mod writer;
|
||||
|
||||
pub use handler::handle;
|
||||
pub use commands::handle;
|
||||
|
|
172
src/parser.rs
172
src/parser.rs
|
@ -1,12 +1,9 @@
|
|||
use crate::common::fs;
|
||||
use crate::finder::structures::{Opts as FinderOpts, SuggestionType};
|
||||
use crate::fs;
|
||||
use crate::hash::fnv;
|
||||
use crate::prelude::*;
|
||||
use crate::serializer;
|
||||
use crate::structures::cheat::VariableMap;
|
||||
use crate::structures::item::Item;
|
||||
use crate::writer;
|
||||
use anyhow::{Context, Result};
|
||||
use regex::Regex;
|
||||
use std::collections::HashSet;
|
||||
use std::io::Write;
|
||||
|
||||
lazy_static! {
|
||||
|
@ -108,34 +105,108 @@ fn parse_variable_line(line: &str) -> Result<(&str, &str, Option<FinderOpts>)> {
|
|||
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<()> {
|
||||
fn without_prefix(line: &str) -> String {
|
||||
if line.len() > 2 {
|
||||
String::from(line[2..].trim())
|
||||
} else {
|
||||
String::from("")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct FilterOpts {
|
||||
pub allowlist: Vec<String>,
|
||||
pub denylist: Vec<String>,
|
||||
pub hash: Option<u64>,
|
||||
}
|
||||
|
||||
pub struct Parser<'a> {
|
||||
pub variables: VariableMap,
|
||||
visited_lines: HashSet<u64>,
|
||||
filter: FilterOpts,
|
||||
writer: &'a mut dyn Write,
|
||||
write_fn: fn(&Item) -> String,
|
||||
}
|
||||
|
||||
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 gen_lists(tag_rules: &str) -> FilterOpts {
|
||||
let words: Vec<_> = tag_rules.split(',').collect();
|
||||
|
||||
let allowlist = words
|
||||
.iter()
|
||||
.filter(|w| !w.starts_with('!'))
|
||||
.map(|w| w.to_string())
|
||||
.collect();
|
||||
|
||||
let denylist = words
|
||||
.iter()
|
||||
.filter(|w| w.starts_with('!'))
|
||||
.map(|w| without_first(w))
|
||||
.collect();
|
||||
|
||||
FilterOpts {
|
||||
allowlist,
|
||||
denylist,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
let filter = match CONFIG.tag_rules() {
|
||||
Some(tr) => gen_lists(&tr),
|
||||
None => Default::default(),
|
||||
};
|
||||
|
||||
Self {
|
||||
variables: Default::default(),
|
||||
visited_lines: Default::default(),
|
||||
filter,
|
||||
write_fn,
|
||||
writer,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_hash(&mut self, hash: u64) {
|
||||
self.filter.hash = Some(hash)
|
||||
}
|
||||
|
||||
fn write_cmd(&mut self, item: &Item) -> 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) {
|
||||
let hash = item.hash();
|
||||
if self.visited_lines.contains(&hash) {
|
||||
return Ok(());
|
||||
}
|
||||
visited_lines.insert(hash);
|
||||
self.visited_lines.insert(hash);
|
||||
|
||||
if let Some(list) = denylist {
|
||||
for v in list {
|
||||
if !self.filter.denylist.is_empty() {
|
||||
for v in &self.filter.denylist {
|
||||
if item.tags.contains(v) {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(list) = allowlist {
|
||||
if !self.filter.allowlist.is_empty() {
|
||||
let mut should_allow = false;
|
||||
for v in list {
|
||||
for v in &self.filter.allowlist {
|
||||
if item.tags.contains(v) {
|
||||
should_allow = true;
|
||||
break;
|
||||
|
@ -146,32 +217,27 @@ fn write_cmd(
|
|||
}
|
||||
}
|
||||
|
||||
return stdin
|
||||
.write_all(writer::write(item).as_bytes())
|
||||
.context("Failed to write command to finder's stdin");
|
||||
}
|
||||
|
||||
fn without_prefix(line: &str) -> String {
|
||||
if line.len() > 2 {
|
||||
String::from(line[2..].trim())
|
||||
} else {
|
||||
String::from("")
|
||||
if let Some(h) = self.filter.hash {
|
||||
if h != hash {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn read_lines(
|
||||
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: usize,
|
||||
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;
|
||||
file_index: Option<usize>,
|
||||
) -> Result<()> {
|
||||
let mut item = Item::new(file_index);
|
||||
|
||||
let mut should_break = false;
|
||||
|
||||
|
@ -190,32 +256,36 @@ pub fn read_lines(
|
|||
// blank
|
||||
if line.is_empty() {
|
||||
if !(&item.snippet).is_empty() {
|
||||
item.snippet.push_str(writer::LINE_SEPARATOR);
|
||||
item.snippet.push_str(serializer::LINE_SEPARATOR);
|
||||
}
|
||||
}
|
||||
// tag
|
||||
else if line.starts_with('%') {
|
||||
should_break = write_cmd(&item, stdin, allowlist, denylist, visited_lines).is_err();
|
||||
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);
|
||||
variables.insert_dependency(&item.tags, &tags_dependency);
|
||||
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 = write_cmd(&item, stdin, allowlist, denylist, visited_lines).is_err();
|
||||
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 = write_cmd(&item, stdin, allowlist, denylist, visited_lines).is_err();
|
||||
should_break = self.write_cmd(&item).is_err();
|
||||
|
||||
item.snippet = String::from("");
|
||||
|
||||
|
@ -232,23 +302,25 @@ pub fn read_lines(
|
|||
)
|
||||
})?;
|
||||
variable_cmd = String::from("");
|
||||
variables.insert_suggestion(&item.tags, variable, (String::from(command), opts));
|
||||
self.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(serializer::LINE_SEPARATOR);
|
||||
}
|
||||
item.snippet.push_str(&line);
|
||||
}
|
||||
}
|
||||
|
||||
if !should_break {
|
||||
let _ = write_cmd(&item, stdin, allowlist, denylist, visited_lines);
|
||||
let _ = self.write_cmd(&item);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[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::hash::fnv;
|
||||
use std::collections::HashMap;
|
||||
use crate::prelude::*;
|
||||
|
||||
pub type Suggestion = (String, Option<Opts>);
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Default)]
|
||||
pub struct VariableMap {
|
||||
variables: HashMap<u64, HashMap<String, Suggestion>>,
|
||||
dependencies: HashMap<u64, Vec<u64>>,
|
||||
}
|
||||
|
||||
impl VariableMap {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
variables: HashMap::new(),
|
||||
dependencies: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert_dependency(&mut self, tags: &str, tags_dependency: &str) {
|
||||
let k = fnv(&tags);
|
||||
if let Some(v) = self.dependencies.get_mut(&k) {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use crate::structures::cheat::VariableMap;
|
||||
use anyhow::Result;
|
||||
use crate::parser::Parser;
|
||||
use crate::prelude::*;
|
||||
|
||||
pub trait Fetcher {
|
||||
fn fetch(
|
||||
&self,
|
||||
stdin: &mut std::process::ChildStdin,
|
||||
files: &mut Vec<String>,
|
||||
) -> Result<Option<VariableMap>>;
|
||||
fn fetch(&self, parser: &mut Parser) -> Result<bool>;
|
||||
|
||||
fn files(&self) -> Vec<String> {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,23 @@
|
|||
use crate::common::hash::fnv;
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Item {
|
||||
pub tags: String,
|
||||
pub comment: String,
|
||||
pub snippet: String,
|
||||
pub file_index: usize,
|
||||
pub file_index: Option<usize>,
|
||||
pub icon: Option<String>,
|
||||
}
|
||||
|
||||
impl Item {
|
||||
pub fn new() -> Self {
|
||||
pub fn new(file_index: Option<usize>) -> Self {
|
||||
Self {
|
||||
tags: "".to_string(),
|
||||
comment: "".to_string(),
|
||||
snippet: "".to_string(),
|
||||
file_index: 0,
|
||||
file_index,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
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::config::CONFIG;
|
||||
use crate::extractor;
|
||||
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;
|
||||
use crate::parser::Parser;
|
||||
use crate::prelude::*;
|
||||
use crate::structures::fetcher;
|
||||
|
||||
pub fn main() -> 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<()> {
|
||||
pub fn populate_cheatsheet(parser: &mut Parser) -> Result<()> {
|
||||
let cheatsheet = include_str!("../docs/navi.cheat");
|
||||
|
||||
parser::read_lines(
|
||||
parser.read_lines(
|
||||
cheatsheet.split('\n').into_iter().map(|s| Ok(s.to_string())),
|
||||
"welcome",
|
||||
0,
|
||||
&mut VariableMap::new(),
|
||||
&mut Default::default(),
|
||||
stdin,
|
||||
None,
|
||||
None,
|
||||
)?;
|
||||
|
||||
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