Baseline project

This commit is contained in:
2025-10-07 10:45:42 -06:00
parent 88288a98b6
commit 650dbd92e0
54 changed files with 18982 additions and 0 deletions
+252
View File
@@ -0,0 +1,252 @@
mod abort_signal;
mod clipboard;
mod command;
mod crypto;
mod html_to_md;
mod input;
mod loader;
mod logs;
pub mod native;
mod path;
mod render_prompt;
mod request;
mod spinner;
mod variables;
pub use self::abort_signal::*;
pub use self::clipboard::set_text;
pub use self::command::*;
pub use self::crypto::*;
pub use self::html_to_md::*;
pub use self::input::*;
pub use self::loader::*;
pub use self::logs::*;
pub use self::path::*;
pub use self::render_prompt::render_prompt;
pub use self::request::*;
pub use self::spinner::*;
pub use self::variables::*;
use anyhow::{Context, Result};
use fancy_regex::Regex;
use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher};
use is_terminal::IsTerminal;
use std::borrow::Cow;
use std::sync::LazyLock;
use std::{env, path::PathBuf, process};
use unicode_segmentation::UnicodeSegmentation;
pub static CODE_BLOCK_RE: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"(?ms)```\w*(.*)```").unwrap());
pub static THINK_TAG_RE: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"(?s)^\s*<think>.*?</think>(\s*|$)").unwrap());
pub static IS_STDOUT_TERMINAL: LazyLock<bool> = LazyLock::new(|| std::io::stdout().is_terminal());
pub static NO_COLOR: LazyLock<bool> = LazyLock::new(|| {
env::var("NO_COLOR")
.ok()
.and_then(|v| parse_bool(&v))
.unwrap_or_default()
|| !*IS_STDOUT_TERMINAL
});
pub fn now() -> String {
chrono::Local::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, false)
}
pub fn now_timestamp() -> i64 {
chrono::Local::now().timestamp()
}
pub fn get_env_name(key: &str) -> String {
format!("{}_{key}", env!("CARGO_CRATE_NAME"),).to_ascii_uppercase()
}
pub fn normalize_env_name(value: &str) -> String {
value.replace('-', "_").to_ascii_uppercase()
}
pub fn parse_bool(value: &str) -> Option<bool> {
match value {
"1" | "true" => Some(true),
"0" | "false" => Some(false),
_ => None,
}
}
pub fn estimate_token_length(text: &str) -> usize {
let words: Vec<&str> = text.unicode_words().collect();
let mut output: f32 = 0.0;
for word in words {
if word.is_ascii() {
output += 1.3;
} else {
let count = word.chars().count();
if count == 1 {
output += 1.0
} else {
output += (count as f32) * 0.5;
}
}
}
output.ceil() as usize
}
pub fn strip_think_tag(text: &str) -> Cow<'_, str> {
THINK_TAG_RE.replace_all(text, "")
}
pub fn extract_code_block(text: &str) -> &str {
CODE_BLOCK_RE
.captures(text)
.ok()
.and_then(|v| v?.get(1).map(|v| v.as_str().trim()))
.unwrap_or(text)
}
pub fn convert_option_string(value: &str) -> Option<String> {
if value.is_empty() {
None
} else {
Some(value.to_string())
}
}
pub fn fuzzy_filter<T, F>(values: Vec<T>, get: F, pattern: &str) -> Vec<T>
where
F: Fn(&T) -> &str,
{
let matcher = SkimMatcherV2::default();
let mut list: Vec<(T, i64)> = values
.into_iter()
.filter_map(|v| {
let score = matcher.fuzzy_match(get(&v), pattern)?;
Some((v, score))
})
.collect();
list.sort_unstable_by(|a, b| b.1.cmp(&a.1));
list.into_iter().map(|(v, _)| v).collect()
}
pub fn pretty_error(err: &anyhow::Error) -> String {
let mut output = vec![];
output.push(format!("Error: {err}"));
let causes: Vec<_> = err.chain().skip(1).collect();
let causes_len = causes.len();
if causes_len > 0 {
output.push("\nCaused by:".to_string());
if causes_len == 1 {
output.push(format!(" {}", indent_text(causes[0], 4).trim()));
} else {
for (i, cause) in causes.into_iter().enumerate() {
output.push(format!("{i:5}: {}", indent_text(cause, 7).trim()));
}
}
}
output.join("\n")
}
pub fn indent_text<T: ToString>(s: T, size: usize) -> String {
let indent_str = " ".repeat(size);
s.to_string()
.split('\n')
.map(|line| format!("{indent_str}{line}"))
.collect::<Vec<String>>()
.join("\n")
}
pub fn error_text(input: &str) -> String {
color_text(input, nu_ansi_term::Color::Red)
}
pub fn warning_text(input: &str) -> String {
color_text(input, nu_ansi_term::Color::Yellow)
}
pub fn color_text(input: &str, color: nu_ansi_term::Color) -> String {
if *NO_COLOR {
return input.to_string();
}
nu_ansi_term::Style::new()
.fg(color)
.paint(input)
.to_string()
}
pub fn dimmed_text(input: &str) -> String {
if *NO_COLOR {
return input.to_string();
}
nu_ansi_term::Style::new().dimmed().paint(input).to_string()
}
pub fn multiline_text(input: &str) -> String {
input
.split('\n')
.enumerate()
.map(|(i, v)| {
if i == 0 {
v.to_string()
} else {
format!(".. {v}")
}
})
.collect::<Vec<String>>()
.join("\n")
}
pub fn temp_file(prefix: &str, suffix: &str) -> PathBuf {
env::temp_dir().join(format!(
"{}-{}{prefix}{}{suffix}",
env!("CARGO_CRATE_NAME").to_lowercase(),
process::id(),
uuid::Uuid::new_v4()
))
}
pub fn is_url(path: &str) -> bool {
path.starts_with("http://") || path.starts_with("https://")
}
pub fn set_proxy(
mut builder: reqwest::ClientBuilder,
proxy: &str,
) -> Result<reqwest::ClientBuilder> {
builder = builder.no_proxy();
if !proxy.is_empty() && proxy != "-" {
builder = builder
.proxy(reqwest::Proxy::all(proxy).with_context(|| format!("Invalid proxy `{proxy}`"))?);
};
Ok(builder)
}
pub fn decode_bin<T: serde::de::DeserializeOwned>(data: &[u8]) -> Result<T> {
let (v, _) = bincode::serde::decode_from_slice(data, bincode::config::legacy())?;
Ok(v)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[cfg(not(target_os = "windows"))]
fn test_safe_join_path() {
assert_eq!(
safe_join_path("/home/user/dir1", "files/file1"),
Some(PathBuf::from("/home/user/dir1/files/file1"))
);
assert!(safe_join_path("/home/user/dir1", "/files/file1").is_none());
assert!(safe_join_path("/home/user/dir1", "../file1").is_none());
}
#[test]
#[cfg(target_os = "windows")]
fn test_safe_join_path() {
assert_eq!(
safe_join_path("C:\\Users\\user\\dir1", "files/file1"),
Some(PathBuf::from("C:\\Users\\user\\dir1\\files\\file1"))
);
assert!(safe_join_path("C:\\Users\\user\\dir1", "/files/file1").is_none());
assert!(safe_join_path("C:\\Users\\user\\dir1", "../file1").is_none());
}
}