summaryrefslogtreecommitdiffstats
path: root/src/clients/tldr.rs
blob: cd040e4021dce58e78941c50515ae2c630bff61d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
use crate::prelude::*;
use std::process::{Command, Stdio};

lazy_static! {
    pub static ref VAR_TLDR_REGEX: Regex = Regex::new(r"\{\{(.*?)\}\}").expect("Invalid regex");
    pub static ref NON_VAR_CHARS_REGEX: Regex = Regex::new(r"[^\da-zA-Z_]").expect("Invalid regex");
}

static VERSION_DISCLAIMER: &str = "The tldr client written in C (the default one in Homebrew) doesn't support markdown files, so navi can't use it.
The client written in Rust is recommended. The one available in npm works, too.";

fn convert_tldr_vars(line: &str) -> String {
    let caps = VAR_TLDR_REGEX.find_iter(line);
    let mut new_line: String = line.to_string();
    for cap in caps {
        let braced_var = cap.as_str();
        let var = &braced_var[2..braced_var.len() - 2];
        let mut new_var = NON_VAR_CHARS_REGEX.replace_all(var, "_").to_string();
        if let Some(c) = new_var.chars().next() {
            if c.to_string().parse::<u8>().is_ok() {
                new_var = format!("example_{new_var}");
            }
        }
        let bracketed_var = format!("<{new_var}>");
        new_line = new_line.replace(braced_var, &bracketed_var);
    }
    new_line
}

fn convert_tldr(line: &str) -> String {
    let line = line.trim();
    if line.starts_with('-') {
        format!("{}{}", "# ", &line[2..line.len() - 1])
    } else if line.starts_with('`') {
        convert_tldr_vars(&line[1..line.len() - 1])
    } else if line.starts_with('%') {
        line.to_string()
    } else {
        "".to_string()
    }
}

fn markdown_lines(query: &str, markdown: &str) -> Vec<String> {
    format!(
        "% {query}, tldr
 {markdown}"
    )
    .lines()
    .map(convert_tldr)
    .collect()
}

pub fn call(query: &str) -> Result<Vec<String>> {
    let args = [query, "--markdown"];

    let child = Command::new("tldr")
        .args(args)
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn();

    let child = match child {
        Ok(x) => x,
        Err(_) => {
            let msg = format!(
                "navi was unable to call tldr.
Make sure tldr is correctly installed.
Refer to https://github.com/tldr-pages/tldr for more info.

Note:
{VERSION_DISCLAIMER}
"
            );
            return Err(anyhow!(msg));
        }
    };

    let out = child.wait_with_output().context("Failed to wait for tldr")?;

    if let Some(0) = out.status.code() {
        let stdout = out.stdout;

        let markdown = String::from_utf8(stdout).context("Output is invalid utf8")?;
        let lines = markdown_lines(query, &markdown);
        Ok(lines)
    } else {
        let msg = format!(
            "Failed to call: 
tldr {}
 
Output:
{}

Error:
{}

Note:
Please make sure you're using a version that supports the --markdown flag.
If you are already using a supported version you can ignore this message. 
{}
",
            args.join(" "),
            String::from_utf8(out.stdout).unwrap_or_else(|_e| "Unable to get output message".to_string()),
            String::from_utf8(out.stderr).unwrap_or_else(|_e| "Unable to get error message".to_string()),
            VERSION_DISCLAIMER,
        );
        Err(anyhow!(msg))
    }
}