Add support for Alfred (#347)

This commit is contained in:
Denis Isidoro 2020-04-19 15:51:04 -03:00 committed by GitHub
parent 019adf82cf
commit c7f919eaa0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 850 additions and 54 deletions

View file

@ -6,8 +6,27 @@ on:
- '*'
jobs:
publish:
name: Publish for ${{ matrix.os }}
alfred:
name: Publish Alfred workflow
os: macos-latest
steps:
- uses: actions/checkout@v1
- name: Zip
run: scripts/action workflow
- name: Get the version
id: get_version
run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//}
- name: Upload workflow to release
uses: svenstaro/upload-release-action@v1-release
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: target/workflow/navi.zip
tag: ${{ github.ref }}
asset_name: navi-${{ steps.get_version.outputs.VERSION }}.alfredworkflow
binary:
name: Publish binary for ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
matrix:

2
Cargo.lock generated
View file

@ -174,7 +174,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "navi"
version = "2.5.2"
version = "2.6.0"
dependencies = [
"anyhow 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
"dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)",

View file

@ -1,6 +1,6 @@
[package]
name = "navi"
version = "2.5.2"
version = "2.6.0"
authors = ["Denis Isidoro <denis_isidoro@live.com>"]
edition = "2018"
description = "An interactive cheatsheet tool for the command-line"

View file

@ -2,9 +2,9 @@
> :information_source: If you're here because you upgraded **navi** and are having some issues, please check [this thread](https://github.com/denisidoro/navi/issues/201).
An interactive cheatsheet tool for the command-line.
An interactive cheatsheet tool for the command-line and application launchers.
![Demo](https://user-images.githubusercontent.com/3226564/76437136-ddc35900-6397-11ea-823c-d2da7615fe60.gif)
![Terminal demo](https://user-images.githubusercontent.com/3226564/76437136-ddc35900-6397-11ea-823c-d2da7615fe60.gif)
**navi** allows you to browse through cheatsheets (that you may write yourself or download from maintainers) and execute commands. Argument suggestions are prompted to you.
@ -13,7 +13,9 @@ An interactive cheatsheet tool for the command-line.
- it will spare you from knowing CLIs by heart;
- it will teach you new one-liners.
It uses [fzf](https://github.com/junegunn/fzf) or [skim](https://github.com/lotabout/skim) under the hood and it can be either used as a command or as a shell widget (*à la* Ctrl-R).
It uses [fzf](https://github.com/junegunn/fzf), [skim](https://github.com/lotabout/skim), or [Alfred](https://www.alfredapp.com/) under the hood and it can be either used as a command or as a shell widget (*à la* Ctrl-R).
![Alfred demo](https://user-images.githubusercontent.com/3226564/79696281-35d06380-8252-11ea-87d5-dc619d869e81.gif)
Table of contents
-----------------
@ -27,6 +29,7 @@ Table of contents
* [Other package managers](#other-package-managers)
* [Usage](#usage)
* [Shell widget](#shell-widget)
* [Alfred](#alfred)
* [More options](#more-options)
* [Trying out online](#trying-out-online)
* [Cheatsheets](#cheatsheets)
@ -112,6 +115,8 @@ navi widget fish | source
By default, `Ctrl+G` is assigned to launching **navi**.
### Alfred
### More options
Please refer to `navi --help` for more details.
@ -248,7 +253,7 @@ $ jsons: find . -iname '*.json' -type f -print --- --multi
List customization
------------------
Lists can be stylized with the [$FZF_DEFAULT_OPTS](https://github.com/junegunn/fzf#layout) environment variable or similar variables or parameters ( please refer to `navi --help`). This way, you can change the [color scheme](https://github.com/junegunn/fzf/wiki/Color-schemes), for example.
Lists can be stylized with the [$FZF_DEFAULT_OPTS](https://github.com/junegunn/fzf#layout) environment variable or similar variables/parameters (please refer to `navi --help`). This way, you can change the [color scheme](https://github.com/junegunn/fzf/wiki/Color-schemes), for example.
Related projects

9
alfred/alfred.bash Executable file
View file

@ -0,0 +1,9 @@
#!/bin/bash
source "${HOME}/.bashrc"
if [ -n "${snippet:-}" ]; then
navi alfred suggestions
else
navi alfred start
fi

15
alfred/alfred2.bash Executable file
View file

@ -0,0 +1,15 @@
#!/bin/bash
_interpolate() {
local -r snippet="$1"
local -r varname="$2"
local -r value="${!varname}"
echo "$snippet" | sed "s/<${varname}>/${value}/g"
}
if [ -n "${varname:-}" ]; then
echo -n "$(_interpolate "$snippet" "$varname" || echo "")"
else
echo -n "$snippet"
fi

485
alfred/info.plist Normal file
View file

@ -0,0 +1,485 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>bundleid</key>
<string>com.github.denisidoro.navi</string>
<key>category</key>
<string>Tools</string>
<key>connections</key>
<dict>
<key>3CE4BEC1-BEBE-4A11-B182-E63693AE2FE6</key>
<array>
<dict>
<key>destinationuid</key>
<string>98658166-32FC-4F9D-8247-52BEDE6394EE</string>
<key>modifiers</key>
<integer>0</integer>
<key>modifiersubtext</key>
<string></string>
<key>vitoclose</key>
<false/>
</dict>
</array>
<key>55C46852-4807-4374-95AB-CC055F4ECB7C</key>
<array>
<dict>
<key>destinationuid</key>
<string>7DD3BDE5-A157-42E5-9376-F681FB50A4EE</string>
<key>modifiers</key>
<integer>0</integer>
<key>modifiersubtext</key>
<string></string>
<key>vitoclose</key>
<false/>
</dict>
</array>
<key>7DD3BDE5-A157-42E5-9376-F681FB50A4EE</key>
<array>
<dict>
<key>destinationuid</key>
<string>3CE4BEC1-BEBE-4A11-B182-E63693AE2FE6</string>
<key>modifiers</key>
<integer>0</integer>
<key>modifiersubtext</key>
<string></string>
<key>vitoclose</key>
<false/>
</dict>
</array>
<key>98658166-32FC-4F9D-8247-52BEDE6394EE</key>
<array>
<dict>
<key>destinationuid</key>
<string>B55D209F-7FF3-4C23-AA39-BA8E37B91452</string>
<key>modifiers</key>
<integer>0</integer>
<key>modifiersubtext</key>
<string></string>
<key>vitoclose</key>
<false/>
</dict>
</array>
<key>B55D209F-7FF3-4C23-AA39-BA8E37B91452</key>
<array>
<dict>
<key>destinationuid</key>
<string>93437EDA-0308-467D-BE3F-6855C638D49D</string>
<key>modifiers</key>
<integer>0</integer>
<key>modifiersubtext</key>
<string></string>
<key>sourceoutputuid</key>
<string>F78DD511-9EC2-4272-8946-C6E3E269DE2E</string>
<key>vitoclose</key>
<false/>
</dict>
<dict>
<key>destinationuid</key>
<string>529BC67C-C77D-41CA-B095-996CC1317556</string>
<key>modifiers</key>
<integer>0</integer>
<key>modifiersubtext</key>
<string></string>
<key>vitoclose</key>
<false/>
</dict>
</array>
<key>F05BFEA8-AEE7-486A-9A54-B3162A233BAC</key>
<array>
<dict>
<key>destinationuid</key>
<string>7DD3BDE5-A157-42E5-9376-F681FB50A4EE</string>
<key>modifiers</key>
<integer>0</integer>
<key>modifiersubtext</key>
<string></string>
<key>vitoclose</key>
<false/>
</dict>
</array>
</dict>
<key>createdby</key>
<string>Denis Isidoro</string>
<key>description</key>
<string>Integration with navi, the interactive cheatsheet tool for the command-line</string>
<key>disabled</key>
<false/>
<key>name</key>
<string>navi</string>
<key>objects</key>
<array>
<dict>
<key>config</key>
<dict>
<key>externaltriggerid</key>
<string>play</string>
<key>passinputasargument</key>
<false/>
<key>passvariables</key>
<true/>
<key>workflowbundleid</key>
<string>self</string>
</dict>
<key>type</key>
<string>alfred.workflow.output.callexternaltrigger</string>
<key>uid</key>
<string>93437EDA-0308-467D-BE3F-6855C638D49D</string>
<key>version</key>
<integer>1</integer>
</dict>
<dict>
<key>config</key>
<dict>
<key>alfredfiltersresults</key>
<true/>
<key>alfredfiltersresultsmatchmode</key>
<integer>0</integer>
<key>argumenttreatemptyqueryasnil</key>
<false/>
<key>argumenttrimmode</key>
<integer>0</integer>
<key>argumenttype</key>
<integer>1</integer>
<key>escaping</key>
<integer>102</integer>
<key>keyword</key>
<string>n</string>
<key>queuedelaycustom</key>
<integer>3</integer>
<key>queuedelayimmediatelyinitially</key>
<false/>
<key>queuedelaymode</key>
<integer>0</integer>
<key>queuemode</key>
<integer>1</integer>
<key>runningsubtext</key>
<string>Generating results...</string>
<key>script</key>
<string>#!/bin/bash
#
# Alfred Script Filter JSON format
#
# This example demonstrates all fields available for populating results.
#
# For an in-depth explanation, use the (?) help button to the bottom left.
#
_print_snippets() {
IFS=$'\n'
items="{\"items\": ["
uid="$RANDOM"
snippets="$(cat uber.cheat | grep '^#' | sed 's/^# //')"
for line in $snippets; do
# title="$(echo "$line" | tr -d '"')"
title="$(echo "$line" | tr -d '"')"
uid="$title"
item=$(cat &lt;&lt;EOF
{
"uid": "$uid",
"type": "file",
"title": "$title",
"subtitle": "subtitle",
"arg": "echo &lt;foo&gt; &lt;bar&gt;",
"autocomplete": "Desktop",
"icon": {
"type": "fileicon",
"path": "~/Desktop"
}
},
EOF
)
items="$items $item"
done
echo "${items%?} ]}"
}
_interpolate() {
snippet="$1"
IFS=$'\n'
items="{\"items\": ["
uid="$RANDOM"
snippets="foo
bar"
for line in $snippets; do
# title="$(echo "$line" | tr -d '"')"
title="$(echo "$line" | tr -d '"')"
uid="$title"
item=$(cat &lt;&lt;EOF
{
"uid": "$uid",
"type": "file",
"title": "lorem",
"subtitle": "subtitle",
"arg": "ipsum",
"autocomplete": "Desktop",
"icon": {
"type": "fileicon",
"path": "~/Desktop"
}
},
EOF
)
items="$items $item"
done
echo "${items%?} ]}"
}
if [ -n "${snippet:-} "]; then
_interpolate "$snippet"
elif [ -n "${query:-} "]; then
_interpolate "$query"
else
_print_snippets
fi
</string>
<key>scriptargtype</key>
<integer>1</integer>
<key>scriptfile</key>
<string>alfred.bash</string>
<key>subtext</key>
<string></string>
<key>title</key>
<string>navi</string>
<key>type</key>
<integer>8</integer>
<key>withspace</key>
<true/>
</dict>
<key>type</key>
<string>alfred.workflow.input.scriptfilter</string>
<key>uid</key>
<string>7DD3BDE5-A157-42E5-9376-F681FB50A4EE</string>
<key>version</key>
<integer>3</integer>
</dict>
<dict>
<key>config</key>
<dict>
<key>triggerid</key>
<string>play</string>
</dict>
<key>type</key>
<string>alfred.workflow.trigger.external</string>
<key>uid</key>
<string>55C46852-4807-4374-95AB-CC055F4ECB7C</string>
<key>version</key>
<integer>1</integer>
</dict>
<dict>
<key>config</key>
<dict>
<key>concurrently</key>
<false/>
<key>escaping</key>
<integer>102</integer>
<key>script</key>
<string>echo asdsds</string>
<key>scriptargtype</key>
<integer>1</integer>
<key>scriptfile</key>
<string>alfred2.bash</string>
<key>type</key>
<integer>8</integer>
</dict>
<key>type</key>
<string>alfred.workflow.action.script</string>
<key>uid</key>
<string>3CE4BEC1-BEBE-4A11-B182-E63693AE2FE6</string>
<key>version</key>
<integer>2</integer>
</dict>
<dict>
<key>config</key>
<dict>
<key>conditions</key>
<array>
<dict>
<key>inputstring</key>
<string>{var:snippet}</string>
<key>matchcasesensitive</key>
<false/>
<key>matchmode</key>
<integer>4</integer>
<key>matchstring</key>
<string>&lt;[\w\d_\-]+&gt;</string>
<key>outputlabel</key>
<string>variable left</string>
<key>uid</key>
<string>F78DD511-9EC2-4272-8946-C6E3E269DE2E</string>
</dict>
</array>
<key>elselabel</key>
<string>no variables</string>
</dict>
<key>type</key>
<string>alfred.workflow.utility.conditional</string>
<key>uid</key>
<string>B55D209F-7FF3-4C23-AA39-BA8E37B91452</string>
<key>version</key>
<integer>1</integer>
</dict>
<dict>
<key>config</key>
<dict>
<key>argument</key>
<string></string>
<key>variables</key>
<dict>
<key>snippet</key>
<string>{query}</string>
</dict>
</dict>
<key>type</key>
<string>alfred.workflow.utility.argument</string>
<key>uid</key>
<string>98658166-32FC-4F9D-8247-52BEDE6394EE</string>
<key>version</key>
<integer>1</integer>
</dict>
<dict>
<key>config</key>
<dict>
<key>concurrently</key>
<true/>
<key>escaping</key>
<integer>0</integer>
<key>script</key>
<string>source $HOME/.bashrc
eval "$snippet"</string>
<key>scriptargtype</key>
<integer>0</integer>
<key>scriptfile</key>
<string>navi</string>
<key>type</key>
<integer>0</integer>
</dict>
<key>type</key>
<string>alfred.workflow.action.script</string>
<key>uid</key>
<string>529BC67C-C77D-41CA-B095-996CC1317556</string>
<key>version</key>
<integer>2</integer>
</dict>
<dict>
<key>config</key>
<dict>
<key>action</key>
<integer>0</integer>
<key>argument</key>
<integer>0</integer>
<key>focusedappvariable</key>
<false/>
<key>focusedappvariablename</key>
<string></string>
<key>hotkey</key>
<integer>6</integer>
<key>hotmod</key>
<integer>1966080</integer>
<key>hotstring</key>
<string>Z</string>
<key>leftcursor</key>
<false/>
<key>modsmode</key>
<integer>0</integer>
<key>relatedAppsMode</key>
<integer>0</integer>
</dict>
<key>type</key>
<string>alfred.workflow.trigger.hotkey</string>
<key>uid</key>
<string>F05BFEA8-AEE7-486A-9A54-B3162A233BAC</string>
<key>version</key>
<integer>2</integer>
</dict>
</array>
<key>readme</key>
<string></string>
<key>uidata</key>
<dict>
<key>3CE4BEC1-BEBE-4A11-B182-E63693AE2FE6</key>
<dict>
<key>xpos</key>
<integer>355</integer>
<key>ypos</key>
<integer>170</integer>
</dict>
<key>529BC67C-C77D-41CA-B095-996CC1317556</key>
<dict>
<key>xpos</key>
<integer>835</integer>
<key>ypos</key>
<integer>285</integer>
</dict>
<key>55C46852-4807-4374-95AB-CC055F4ECB7C</key>
<dict>
<key>xpos</key>
<integer>10</integer>
<key>ypos</key>
<integer>170</integer>
</dict>
<key>7DD3BDE5-A157-42E5-9376-F681FB50A4EE</key>
<dict>
<key>xpos</key>
<integer>175</integer>
<key>ypos</key>
<integer>170</integer>
</dict>
<key>93437EDA-0308-467D-BE3F-6855C638D49D</key>
<dict>
<key>xpos</key>
<integer>830</integer>
<key>ypos</key>
<integer>155</integer>
</dict>
<key>98658166-32FC-4F9D-8247-52BEDE6394EE</key>
<dict>
<key>xpos</key>
<integer>535</integer>
<key>ypos</key>
<integer>200</integer>
</dict>
<key>B55D209F-7FF3-4C23-AA39-BA8E37B91452</key>
<dict>
<key>xpos</key>
<integer>640</integer>
<key>ypos</key>
<integer>190</integer>
</dict>
<key>F05BFEA8-AEE7-486A-9A54-B3162A233BAC</key>
<dict>
<key>xpos</key>
<integer>10</integer>
<key>ypos</key>
<integer>310</integer>
</dict>
</dict>
<key>variablesdontexport</key>
<array/>
<key>version</key>
<string>0.1.0</string>
<key>webaddress</key>
<string>https://github.com/denisidoro</string>
</dict>
</plist>

View file

@ -35,14 +35,31 @@ release() {
cp "$bin_path" "$TAR_DIR"
cd "${NAVI_HOME}/target/tar"
cd "$TAR_DIR"
tar -czf navi.tar.gz *
}
workflow() {
WORKFLOW_DIR="${NAVI_HOME}/target/workflow"
cd "$NAVI_HOME"
rm -rf "${NAVI_HOME}/target" 2> /dev/null || true
mkdir -p "$WORKFLOW_DIR" 2> /dev/null || true
cp -r "${NAVI_HOME}/alfred" "$WORKFLOW_DIR"
cd "$WORKFLOW_DIR"
zip -r "navi.zip" "."
}
cmd="$1"
shift
case "$cmd" in
"release") release "$@" ;;
"workflow") workflow "$@" ;;
esac

View file

@ -1,3 +1,4 @@
use crate::structures::item::Item;
use crate::terminal;
use regex::Regex;
use std::cmp::max;
@ -12,11 +13,10 @@ pub const LINE_SEPARATOR: &str = " \x15 ";
pub const DELIMITER: &str = r" ";
lazy_static! {
pub static ref WIDTHS: (usize, usize) = get_widths();
pub static ref NEWLINE_REGEX: Regex = Regex::new(r"\\\s+").expect("Invalid regex");
}
fn get_widths() -> (usize, usize) {
pub fn get_widths() -> (usize, usize) {
let width = terminal::width();
let tag_width = max(4, width * 20 / 100);
let comment_width = max(4, width * 40 / 100);
@ -57,23 +57,61 @@ fn limit_str(text: &str, length: usize) -> String {
}
}
pub fn format_line(
tags: &str,
comment: &str,
snippet: &str,
tag_width: usize,
comment_width: usize,
) -> String {
format!(
pub trait Writer {
fn write(&mut self, item: Item) -> String;
}
pub struct FinderWriter {
pub tag_width: usize,
pub comment_width: usize,
}
pub struct AlfredWriter {
pub is_first: bool,
}
impl Writer for FinderWriter {
fn write(&mut self, item: Item) -> String {
format!(
"{tag_color}{tags_short}{delimiter}{comment_color}{comment_short}{delimiter}{snippet_color}{snippet_short}{delimiter}{tags}{delimiter}{comment}{delimiter}{snippet}{delimiter}\n",
tags_short = limit_str(tags, tag_width),
comment_short = limit_str(comment, comment_width),
snippet_short = fix_newlines(snippet),
tags_short = limit_str(item.tags, self.tag_width),
comment_short = limit_str(item.comment, self.comment_width),
snippet_short = fix_newlines(item.snippet),
comment_color = color::Fg(COMMENT_COLOR),
tag_color = color::Fg(TAG_COLOR),
snippet_color = color::Fg(SNIPPET_COLOR),
tags = tags,
comment = comment,
tags = item.tags,
comment = item.comment,
delimiter = DELIMITER,
snippet = &snippet)
snippet = &item.snippet)
}
}
fn escape_for_json(txt: &str) -> String {
txt.replace('\\', "\\\\")
.replace('"', "\\\"")
.replace(NEWLINE_ESCAPE_CHAR, " ")
}
impl Writer for AlfredWriter {
fn write(&mut self, item: Item) -> String {
let prefix = if self.is_first {
self.is_first = false;
""
} else {
","
};
let tags = escape_for_json(item.tags);
let comment = escape_for_json(item.comment);
let snippet = escape_for_json(item.snippet);
format!(
r#"{prefix}{{"type":"file","title":"{comment}","match":"{comment} {tags} {snippet}","subtitle":"{tags} :: {snippet}","variables":{{"tags":"{tags}","comment":"{comment}","snippet":"{snippet}"}},"autocomplete":"Desktop","icon":{{"type":"fileicon","path":"~/Desktop"}}}}"#,
prefix = prefix,
tags = tags,
comment = comment,
snippet = snippet
)
}
}

134
src/flows/alfred.rs Normal file
View file

@ -0,0 +1,134 @@
use crate::parser;
use crate::structures::cheat::Suggestion;
use crate::structures::{error::command::BashSpawnError, option::Config};
use anyhow::Context;
use anyhow::Error;
use regex::Regex;
use std::env;
use std::process::{Command, Stdio};
lazy_static! {
pub static ref VAR_REGEX: Regex = Regex::new(r"<(\w[\w\d\-_]*)>").expect("Invalid regex");
}
pub fn main(config: Config) -> Result<(), Error> {
let mut child = Command::new("cat").stdin(Stdio::piped()).spawn().unwrap();
let stdin = child.stdin.as_mut().unwrap();
println!(r#"{{"items": ["#);
parser::read_all(&config, stdin).context("Failed to parse variables intended for finder")?;
let _ = child.wait_with_output().context("Failed to wait for fzf")?;
println!(r#"]}}"#);
Ok(())
}
fn prompt_with_suggestions(
_variable_name: &str,
_config: &Config,
suggestion: &Suggestion,
) -> Result<String, Error> {
let (suggestion_command, _suggestion_opts) = suggestion;
let child = Command::new("bash")
.stdout(Stdio::piped())
.arg("-c")
.arg(&suggestion_command)
.spawn()
.map_err(|e| BashSpawnError::new(suggestion_command, e))?;
let suggestions = String::from_utf8(
child
.wait_with_output()
.context("Failed to wait and collect output from bash")?
.stdout,
)
.context("Suggestions are invalid utf8")?;
Ok(suggestions)
}
pub fn suggestions(config: Config) -> Result<(), Error> {
let mut child = Command::new("cat")
.stdin(Stdio::piped())
.stdout(Stdio::null())
.spawn()
.unwrap();
let stdin = child.stdin.as_mut().unwrap();
let variables = parser::read_all(&config, stdin)
.context("Failed to parse variables intended for finder")?;
let tags = env::var("tags").unwrap();
let _comment = env::var("comment").unwrap();
let snippet = env::var("snippet").unwrap();
let varname = VAR_REGEX.captures_iter(&snippet).next();
if let Some(varname) = varname {
let varname = &varname[0];
let varname = &varname[1..varname.len() - 1];
println!(
r#"{{"variables": {{"varname": "{varname}"}}, "items": ["#,
varname = varname
);
let lines = variables
.get(&tags, &varname)
.ok_or_else(|| anyhow!("No suggestions"))
.and_then(|suggestion| {
Ok(prompt_with_suggestions(&varname, &config, suggestion).unwrap())
})?;
let mut is_first = true;
for line in lines.split('\n') {
if line.len() < 3 {
continue;
}
let prefix = if is_first {
is_first = false;
""
} else {
","
};
println!(
r#"{prefix}{{
"type": "file",
"title": "{value}",
"subtitle": "{snippet}",
"autocomplete": "Desktop",
"variables": {{
"{varname}": "{value}"
}},
"icon": {{
"type": "fileicon",
"path": "~/Desktop"
}}
}}"#,
prefix = prefix,
snippet = snippet,
varname = varname,
value = line
);
}
} else {
println!(r#"{{"items": ["#);
}
println!(r#"]}}"#);
Ok(())
}
pub fn transform(_config: Config) -> Result<(), Error> {
Ok(())
}

View file

@ -213,8 +213,6 @@ fn with_new_lines(txt: String) -> String {
}
pub fn main(variant: Variant, config: Config, contains_key: bool) -> Result<(), Error> {
let _ = display::WIDTHS;
let opts =
gen_core_finder_opts(variant, &config).context("Failed to generate finder options")?;
let (raw_selection, variables) = config

View file

@ -1,3 +1,4 @@
pub mod alfred;
pub mod aux;
pub mod best;
pub mod core;

View file

@ -1,7 +1,7 @@
use crate::flows;
use crate::flows::core::Variant;
use crate::structures::option::Command::{Best, Fn, Preview, Query, Repo, Search, Widget};
use crate::structures::option::{Config, RepoCommand};
use crate::structures::option::Command::{Alfred, Best, Fn, Preview, Query, Repo, Search, Widget};
use crate::structures::option::{AlfredCommand, Config, RepoCommand};
use anyhow::Context;
use anyhow::Error;
@ -39,6 +39,12 @@ pub fn handle_config(config: Config) -> Result<(), Error> {
RepoCommand::Browse => flows::repo::browse(&config.finder)
.context("Failed to browse featured cheatsheets"),
},
Alfred { cmd } => match cmd {
AlfredCommand::Start => flows::alfred::main(config),
AlfredCommand::Suggestions => flows::alfred::suggestions(config),
AlfredCommand::Transform => flows::alfred::transform(config),
},
},
}
}

View file

@ -1,9 +1,10 @@
use crate::display;
use crate::display::{self, Writer};
use crate::filesystem;
use crate::structures::cheat::VariableMap;
use crate::structures::finder::{Opts as FinderOpts, SuggestionType};
use crate::structures::fnv::HashLine;
use crate::structures::{error::filesystem::InvalidPath, option::Config};
use crate::structures::option::Command::Alfred;
use crate::structures::{error::filesystem::InvalidPath, item::Item, option::Config};
use crate::welcome;
use anyhow::{Context, Error};
use regex::Regex;
@ -115,18 +116,19 @@ fn write_cmd(
tags: &str,
comment: &str,
snippet: &str,
tag_width: usize,
comment_width: usize,
writer: &mut Box<dyn Writer>,
stdin: &mut std::process::ChildStdin,
) -> Result<(), Error> {
if snippet.len() <= 1 {
Ok(())
} else {
let item = Item {
tags: &tags,
comment: &comment,
snippet: &snippet,
};
stdin
.write_all(
display::format_line(&tags, &comment, &snippet, tag_width, comment_width)
.as_bytes(),
)
.write_all(writer.write(item).as_bytes())
.context("Failed to write command to finder's stdin")
}
}
@ -135,6 +137,7 @@ fn read_file(
path: &str,
variables: &mut VariableMap,
visited_lines: &mut HashSet<u64>,
writer: &mut Box<dyn Writer>,
stdin: &mut std::process::ChildStdin,
) -> Result<(), Error> {
let mut tags = String::from("");
@ -142,8 +145,6 @@ fn read_file(
let mut snippet = String::from("");
let mut should_break = false;
let (tag_width, comment_width) = *display::WIDTHS;
for (line_nr, line_result) in filesystem::read_lines(path)?.enumerate() {
let line = line_result
.with_context(|| format!("Failed to read line nr.{} from `{}`", line_nr, path))?;
@ -160,7 +161,7 @@ fn read_file(
}
// tag
else if line.starts_with('%') {
if write_cmd(&tags, &comment, &snippet, tag_width, comment_width, stdin).is_err() {
if write_cmd(&tags, &comment, &snippet, writer, stdin).is_err() {
should_break = true
}
snippet = String::from("");
@ -175,7 +176,7 @@ fn read_file(
}
// comment
else if line.starts_with('#') {
if write_cmd(&tags, &comment, &snippet, tag_width, comment_width, stdin).is_err() {
if write_cmd(&tags, &comment, &snippet, writer, stdin).is_err() {
should_break = true
}
snippet = String::from("");
@ -187,13 +188,13 @@ fn read_file(
}
// variable
else if line.starts_with('$') {
if write_cmd(&tags, &comment, &snippet, tag_width, comment_width, stdin).is_err() {
if write_cmd(&tags, &comment, &snippet, writer, stdin).is_err() {
should_break = true
}
snippet = String::from("");
let (variable, command, opts) = parse_variable_line(&line).with_context(|| {
format!(
"Failed to parse variable line. See line nr.{} in cheatsheet `{}`",
"Failed to parse variable line. See line number {} in cheatsheet `{}`",
line_nr + 1,
path
)
@ -216,7 +217,7 @@ fn read_file(
}
if !should_break {
let _ = write_cmd(&tags, &comment, &snippet, tag_width, comment_width, stdin);
let _ = write_cmd(&tags, &comment, &snippet, writer, stdin);
}
Ok(())
@ -233,10 +234,19 @@ pub fn read_all(
let mut variables = VariableMap::new();
let mut found_something = false;
let mut visited_lines = HashSet::new();
let mut writer: Box<dyn Writer> = if let Some(Alfred { .. }) = &config.cmd {
Box::new(display::AlfredWriter { is_first: true })
} else {
let (tag_width, comment_width) = display::get_widths();
Box::new(display::FinderWriter {
tag_width,
comment_width,
})
};
let paths = filesystem::cheat_paths(config);
if paths.is_err() {
welcome::cheatsheet(stdin);
welcome::cheatsheet(&mut writer, stdin);
return Ok(variables);
}
@ -252,7 +262,14 @@ pub fn read_all(
.to_str()
.ok_or_else(|| InvalidPath(path.to_path_buf()))?;
if path_str.ends_with(".cheat")
&& read_file(path_str, &mut variables, &mut visited_lines, stdin).is_ok()
&& read_file(
path_str,
&mut variables,
&mut visited_lines,
&mut writer,
stdin,
)
.is_ok()
&& !found_something
{
found_something = true;
@ -263,7 +280,7 @@ pub fn read_all(
}
if !found_something {
welcome::cheatsheet(stdin);
welcome::cheatsheet(&mut writer, stdin);
}
Ok(variables)
@ -300,7 +317,18 @@ mod tests {
let mut child = Command::new("cat").stdin(Stdio::piped()).spawn().unwrap();
let child_stdin = child.stdin.as_mut().unwrap();
let mut visited_lines: HashSet<u64> = HashSet::new();
read_file(path, &mut variables, &mut visited_lines, child_stdin).unwrap();
let mut writer: Box<dyn Writer> = Box::new(display::FinderWriter {
comment_width: 20,
tag_width: 30,
});
read_file(
path,
&mut variables,
&mut visited_lines,
&mut writer,
child_stdin,
)
.unwrap();
let expected_suggestion = (
r#" echo -e "$(whoami)\nroot" "#.to_string(),
Some(FinderOpts {

View file

@ -3,7 +3,7 @@ use thiserror::Error;
#[derive(Error, Debug)]
#[error(
"\rHey listen! Navi encountered a problem.
"\rHey, listen! Navi encountered a problem.
Do you think this is a bug? File an issue at https://github.com/denisidoro/navi."
)]
pub struct FileAnIssue {

5
src/structures/item.rs Normal file
View file

@ -0,0 +1,5 @@
pub struct Item<'a> {
pub tags: &'a str,
pub comment: &'a str,
pub snippet: &'a str,
}

View file

@ -2,4 +2,5 @@ pub mod cheat;
pub mod error;
pub mod finder;
pub mod fnv;
pub mod item;
pub mod option;

View file

@ -106,6 +106,11 @@ pub enum Command {
/// bash, zsh or fish
shell: String,
},
/// Alfred
Alfred {
#[structopt(subcommand)]
cmd: AlfredCommand,
},
}
#[derive(Debug, StructOpt)]
@ -119,6 +124,16 @@ pub enum RepoCommand {
Browse,
}
#[derive(Debug, StructOpt)]
pub enum AlfredCommand {
/// Start
Start,
/// Suggestions
Suggestions,
/// Transform
Transform,
}
pub fn config_from_env() -> Config {
Config::from_args()
}

View file

@ -1,24 +1,44 @@
use crate::display;
use crate::display::Writer;
use crate::structures::item::Item;
use std::io::Write;
fn add_msg(tag: &str, comment: &str, snippet: &str, stdin: &mut std::process::ChildStdin) {
fn add_msg(
tags: &str,
comment: &str,
snippet: &str,
writer: &mut Box<dyn Writer>,
stdin: &mut std::process::ChildStdin,
) {
let item = Item {
tags: &tags,
comment: &comment,
snippet: &snippet,
};
stdin
.write_all(display::format_line(tag, comment, snippet, 20, 60).as_bytes())
.write_all(writer.write(item).as_bytes())
.expect("Could not write to fzf's stdin");
}
pub fn cheatsheet(stdin: &mut std::process::ChildStdin) {
pub fn cheatsheet(writer: &mut Box<dyn Writer>, stdin: &mut std::process::ChildStdin) {
add_msg(
"cheatsheets",
"Download default cheatsheets",
"navi repo add denisidoro/cheats",
writer,
stdin,
);
add_msg(
"cheatsheets",
"Browse for cheatsheet repos",
"navi repo browse",
writer,
stdin,
);
add_msg(
"more info",
"Read --help message",
"navi --help",
writer,
stdin,
);
add_msg("more info", "Read --help message", "navi --help", stdin);
}