From 14549afd522cfa102af2b523515ddba0360e6c04 Mon Sep 17 00:00:00 2001 From: Alex Clarke Date: Fri, 7 Nov 2025 15:50:55 -0700 Subject: [PATCH] refactor: Updated to the most recent Rust version with 2024 syntax --- Cargo.lock | 2 +- Cargo.toml | 10 +- src/cli/completer.rs | 6 +- src/cli/mod.rs | 8 +- src/client/access_token.rs | 2 +- src/client/bedrock.rs | 60 +++++------ src/client/cohere.rs | 20 ++-- src/client/common.rs | 18 ++-- src/client/message.rs | 8 +- src/client/model.rs | 13 ++- src/client/openai_compatible.rs | 14 +-- src/client/stream.rs | 14 +-- src/config/agent.rs | 36 +++---- src/config/input.rs | 8 +- src/config/macros.rs | 6 +- src/config/mod.rs | 176 ++++++++++++++++---------------- src/config/role.rs | 39 ++++--- src/config/session.rs | 42 ++++---- src/function/mod.rs | 4 +- src/main.rs | 9 +- src/mcp/mod.rs | 12 ++- src/parsers/bash.rs | 10 +- src/parsers/python.rs | 23 ++--- src/rag/mod.rs | 29 +++--- src/rag/serde_vectors.rs | 4 +- src/rag/splitter/mod.rs | 16 ++- src/render/markdown.rs | 10 +- src/render/mod.rs | 2 +- src/render/stream.rs | 4 +- src/repl/completer.rs | 2 +- src/repl/mod.rs | 20 ++-- src/utils/abort_signal.rs | 26 ++--- src/utils/clipboard.rs | 2 +- src/utils/command.rs | 2 +- src/utils/crypto.rs | 2 +- src/utils/html_to_md.rs | 2 +- src/utils/input.rs | 2 +- src/utils/loader.rs | 2 +- src/utils/mod.rs | 2 +- src/utils/path.rs | 39 ++++--- src/utils/request.rs | 4 +- src/utils/spinner.rs | 6 +- src/vault/mod.rs | 4 +- src/vault/utils.rs | 28 +++-- 44 files changed, 377 insertions(+), 371 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1907df1..dc8fa23 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3089,7 +3089,7 @@ dependencies = [ [[package]] name = "loki-ai" -version = "0.2.0" +version = "0.0.1" dependencies = [ "ansi_colours", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index bafea67..bbbe6a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,13 +1,17 @@ [package] name = "loki-ai" -version = "0.2.0" -edition = "2021" +version = "0.0.1" +edition = "2024" authors = ["Alex Clarke "] description = "An all-in-one, batteries included LLM CLI Tool" +keywords = ["chatgpt", "llm", "cli", "ai", "repl"] homepage = "https://github.com/Dark-Alex-17/loki" repository = "https://github.com/Dark-Alex-17/loki" categories = ["command-line-utilities"] -keywords = ["chatgpt", "llm", "cli", "ai", "repl"] +readme = "README.md" +license = "MIT" +rust-version = "1.89.0" +exclude = [".github", "CONTRIBUTING.md"] [dependencies] anyhow = "1.0.69" diff --git a/src/cli/completer.rs b/src/cli/completer.rs index 0c457b6..81bc604 100644 --- a/src/cli/completer.rs +++ b/src/cli/completer.rs @@ -1,6 +1,6 @@ -use crate::client::{list_models, ModelType}; -use crate::config::{list_agents, Config}; -use clap_complete::{generate, CompletionCandidate, Shell}; +use crate::client::{ModelType, list_models}; +use crate::config::{Config, list_agents}; +use clap_complete::{CompletionCandidate, Shell, generate}; use clap_complete_nushell::Nushell; use std::ffi::OsStr; use std::io; diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 649591a..6b6e101 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -1,15 +1,15 @@ mod completer; use crate::cli::completer::{ - agent_completer, macro_completer, model_completer, rag_completer, role_completer, - secrets_completer, session_completer, ShellCompletion, + ShellCompletion, agent_completer, macro_completer, model_completer, rag_completer, + role_completer, secrets_completer, session_completer, }; use anyhow::{Context, Result}; use clap::ValueHint; -use clap::{crate_authors, crate_description, crate_name, crate_version, Parser}; +use clap::{Parser, crate_authors, crate_description, crate_name, crate_version}; use clap_complete::ArgValueCompleter; use is_terminal::IsTerminal; -use std::io::{stdin, Read}; +use std::io::{Read, stdin}; #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] diff --git a/src/client/access_token.rs b/src/client/access_token.rs index e09e02c..ccbfd06 100644 --- a/src/client/access_token.rs +++ b/src/client/access_token.rs @@ -1,4 +1,4 @@ -use anyhow::{anyhow, Result}; +use anyhow::{Result, anyhow}; use chrono::Utc; use indexmap::IndexMap; use parking_lot::RwLock; diff --git a/src/client/bedrock.rs b/src/client/bedrock.rs index 6305219..3e9e6ff 100644 --- a/src/client/bedrock.rs +++ b/src/client/bedrock.rs @@ -2,7 +2,7 @@ use super::*; use crate::utils::{base64_decode, encode_uri, hex_encode, hmac_sha256, sha256, strip_think_tag}; -use anyhow::{bail, Context, Result}; +use anyhow::{Context, Result, bail}; use aws_smithy_eventstream::frame::{DecodedFrame, MessageFrameDecoder}; use aws_smithy_eventstream::smithy::parse_response_headers; use bytes::BytesMut; @@ -11,7 +11,7 @@ use futures_util::StreamExt; use indexmap::IndexMap; use reqwest::{Client as ReqwestClient, Method, RequestBuilder}; use serde::Deserialize; -use serde_json::{json, Value}; +use serde_json::{Value, json}; #[derive(Debug, Clone, Deserialize)] pub struct BedrockConfig { @@ -222,29 +222,29 @@ async fn chat_completions_streaming( debug!("stream-data: {smithy_type} {data}"); match smithy_type { "contentBlockStart" => { - if let Some(tool_use) = data["start"]["toolUse"].as_object() { - if let (Some(id), Some(name)) = ( + if let Some(tool_use) = data["start"]["toolUse"].as_object() + && let (Some(id), Some(name)) = ( json_str_from_map(tool_use, "toolUseId"), json_str_from_map(tool_use, "name"), - ) { - if !function_name.is_empty() { - if function_arguments.is_empty() { - function_arguments = String::from("{}"); - } - let arguments: Value = + ) + { + if !function_name.is_empty() { + if function_arguments.is_empty() { + function_arguments = String::from("{}"); + } + let arguments: Value = function_arguments.parse().with_context(|| { format!("Tool call '{function_name}' have non-JSON arguments '{function_arguments}'") })?; - handler.tool_call(ToolCall::new( - function_name.clone(), - arguments, - Some(function_id.clone()), - ))?; - } - function_arguments.clear(); - function_name = name.into(); - function_id = id.into(); + handler.tool_call(ToolCall::new( + function_name.clone(), + arguments, + Some(function_id.clone()), + ))?; } + function_arguments.clear(); + function_name = name.into(); + function_id = id.into(); } } "contentBlockDelta" => { @@ -291,7 +291,9 @@ async fn chat_completions_streaming( bail!("Invalid response data: {data} (smithy_type: {smithy_type})") } _ => { - bail!("Unrecognized message, message_type: {message_type}, smithy_type: {smithy_type}",); + bail!( + "Unrecognized message, message_type: {message_type}, smithy_type: {smithy_type}", + ); } } } @@ -494,18 +496,18 @@ fn extract_chat_completions(data: &Value) -> Result { if let Some(text) = json_str_from_map(reasoning_text, "text") { reasoning = Some(text.to_string()); } - } else if let Some(tool_use) = item["toolUse"].as_object() { - if let (Some(id), Some(name), Some(input)) = ( + } else if let Some(tool_use) = item["toolUse"].as_object() + && let (Some(id), Some(name), Some(input)) = ( json_str_from_map(tool_use, "toolUseId"), json_str_from_map(tool_use, "name"), tool_use.get("input"), - ) { - tool_calls.push(ToolCall::new( - name.to_string(), - input.clone(), - Some(id.to_string()), - )) - } + ) + { + tool_calls.push(ToolCall::new( + name.to_string(), + input.clone(), + Some(id.to_string()), + )) } } } diff --git a/src/client/cohere.rs b/src/client/cohere.rs index cb11120..b395915 100644 --- a/src/client/cohere.rs +++ b/src/client/cohere.rs @@ -2,10 +2,10 @@ use super::openai::*; use super::openai_compatible::*; use super::*; -use anyhow::{bail, Context, Result}; +use anyhow::{Context, Result, bail}; use reqwest::RequestBuilder; use serde::Deserialize; -use serde_json::{json, Value}; +use serde_json::{Value, json}; const API_BASE: &str = "https://api.cohere.ai/v2"; @@ -49,10 +49,10 @@ fn prepare_chat_completions( let url = format!("{}/chat", api_base.trim_end_matches('/')); let mut body = openai_build_chat_completions_body(data, &self_.model); - if let Some(obj) = body.as_object_mut() { - if let Some(top_p) = obj.remove("top_p") { - obj.insert("p".to_string(), top_p); - } + if let Some(obj) = body.as_object_mut() + && let Some(top_p) = obj.remove("top_p") + { + obj.insert("p".to_string(), top_p); } let mut request_data = RequestData::new(url, body); @@ -218,10 +218,10 @@ fn extract_chat_completions(data: &Value) -> Result { let mut tool_calls = vec![]; if let Some(calls) = data["message"]["tool_calls"].as_array() { - if text.is_empty() { - if let Some(tool_plain) = data["message"]["tool_plan"].as_str() { - text = tool_plain.to_string(); - } + if text.is_empty() + && let Some(tool_plain) = data["message"]["tool_plan"].as_str() + { + text = tool_plain.to_string(); } for call in calls { if let (Some(name), Some(arguments), Some(id)) = ( diff --git a/src/client/common.rs b/src/client/common.rs index 8339a6a..ae6d2a3 100644 --- a/src/client/common.rs +++ b/src/client/common.rs @@ -2,21 +2,21 @@ use super::*; use crate::{ config::{Config, GlobalConfig, Input}, - function::{eval_tool_calls, FunctionDeclaration, ToolCall, ToolResult}, + function::{FunctionDeclaration, ToolCall, ToolResult, eval_tool_calls}, render::render_stream, utils::*, }; use crate::vault::Vault; -use anyhow::{bail, Context, Result}; +use anyhow::{Context, Result, bail}; use fancy_regex::Regex; use indexmap::IndexMap; use inquire::{ - list_option::ListOption, required, validator::Validation, MultiSelect, Select, Text, + MultiSelect, Select, Text, list_option::ListOption, required, validator::Validation, }; use reqwest::{Client as ReqwestClient, RequestBuilder}; use serde::Deserialize; -use serde_json::{json, Value}; +use serde_json::{Value, json}; use std::sync::LazyLock; use std::time::Duration; use tokio::sync::mpsc::unbounded_channel; @@ -180,11 +180,11 @@ pub trait Client: Sync + Send { }; for (key, patch) in patch_map { let key = ESCAPE_SLASH_RE.replace_all(&key, r"\/"); - if let Ok(regex) = Regex::new(&format!("^({key})$")) { - if let Ok(true) = regex.is_match(self.model().name()) { - request_data.apply_patch(patch); - return; - } + if let Ok(regex) = Regex::new(&format!("^({key})$")) + && let Ok(true) = regex.is_match(self.model().name()) + { + request_data.apply_patch(patch); + return; } } } diff --git a/src/client/message.rs b/src/client/message.rs index 5ca7f78..3fded76 100644 --- a/src/client/message.rs +++ b/src/client/message.rs @@ -119,10 +119,10 @@ impl MessageContent { } for tool_result in tool_results { let mut parts = vec!["Call".to_string()]; - if let Some((agent_name, functions)) = agent_info { - if functions.contains(&tool_result.call.name) { - parts.push(agent_name.clone()) - } + if let Some((agent_name, functions)) = agent_info + && functions.contains(&tool_result.call.name) + { + parts.push(agent_name.clone()) } parts.push(tool_result.call.name.clone()); parts.push(tool_result.call.arguments.to_string()); diff --git a/src/client/model.rs b/src/client/model.rs index ab288af..6d2dfbd 100644 --- a/src/client/model.rs +++ b/src/client/model.rs @@ -1,13 +1,12 @@ use super::{ - list_all_models, list_client_names, + ApiPatch, MessageContentToolCalls, RequestPatch, list_all_models, list_client_names, message::{Message, MessageContent, MessageContentPart}, - ApiPatch, MessageContentToolCalls, RequestPatch, }; use crate::config::Config; use crate::utils::{estimate_token_length, strip_think_tag}; -use anyhow::{bail, Result}; +use anyhow::{Result, bail}; use serde::{Deserialize, Serialize}; use serde_json::Value; use std::fmt::Display; @@ -275,10 +274,10 @@ impl Model { pub fn guard_max_input_tokens(&self, messages: &[Message]) -> Result<()> { let total_tokens = self.total_tokens(messages) + BASIS_TOKENS; - if let Some(max_input_tokens) = self.data.max_input_tokens { - if total_tokens >= max_input_tokens { - bail!("Exceed max_input_tokens limit") - } + if let Some(max_input_tokens) = self.data.max_input_tokens + && total_tokens >= max_input_tokens + { + bail!("Exceed max_input_tokens limit") } Ok(()) } diff --git a/src/client/openai_compatible.rs b/src/client/openai_compatible.rs index 4f77e88..3ee28ba 100644 --- a/src/client/openai_compatible.rs +++ b/src/client/openai_compatible.rs @@ -4,7 +4,7 @@ use super::*; use anyhow::{Context, Result}; use reqwest::RequestBuilder; use serde::Deserialize; -use serde_json::{json, Value}; +use serde_json::{Value, json}; #[derive(Debug, Clone, Deserialize)] pub struct OpenAICompatibleConfig { @@ -124,12 +124,12 @@ pub async fn generic_rerank(builder: RequestBuilder, _model: &Model) -> Result { self.balances.pop(); - if self.balances.is_empty() { - if let Some(start) = self.start.take() { - let value: String = self.buffer[start..=i].iter().collect(); - handle(&value)?; - } + if self.balances.is_empty() + && let Some(start) = self.start.take() + { + let value: String = self.buffer[start..=i].iter().collect(); + handle(&value)?; } } ']' => { diff --git a/src/config/agent.rs b/src/config/agent.rs index 7ade6f2..df097cb 100644 --- a/src/config/agent.rs +++ b/src/config/agent.rs @@ -2,13 +2,13 @@ use super::*; use crate::{ client::Model, - function::{run_llm_function, Functions}, + function::{Functions, run_llm_function}, }; use crate::vault::SECRET_RE; use anyhow::{Context, Result}; use fancy_regex::Captures; -use inquire::{validator::Validation, Text}; +use inquire::{Text, validator::Validation}; use rust_embed::Embed; use serde::{Deserialize, Serialize}; use std::{ffi::OsStr, path::Path}; @@ -530,23 +530,23 @@ impl AgentConfig { if let Some(v) = read_env_value::(&with_prefix("top_p")) { self.top_p = v; } - if let Ok(v) = env::var(with_prefix("global_tools")) { - if let Ok(v) = serde_json::from_str(&v) { - self.global_tools = v; - } + if let Ok(v) = env::var(with_prefix("global_tools")) + && let Ok(v) = serde_json::from_str(&v) + { + self.global_tools = v; } - if let Ok(v) = env::var(with_prefix("mcp_servers")) { - if let Ok(v) = serde_json::from_str(&v) { - self.mcp_servers = v; - } + if let Ok(v) = env::var(with_prefix("mcp_servers")) + && let Ok(v) = serde_json::from_str(&v) + { + self.mcp_servers = v; } if let Some(v) = read_env_value::(&with_prefix("agent_session")) { self.agent_session = v; } - if let Ok(v) = env::var(with_prefix("variables")) { - if let Ok(v) = serde_json::from_str(&v) { - self.variables = v; - } + if let Ok(v) = env::var(with_prefix("variables")) + && let Ok(v) = serde_json::from_str(&v) + { + self.variables = v; } } @@ -619,10 +619,10 @@ pub fn list_agents() -> Vec { let mut agents = Vec::new(); if let Ok(entries) = read_dir(agents_data_dir) { for entry in entries.flatten() { - if entry.path().is_dir() { - if let Some(name) = entry.file_name().to_str() { - agents.push(name.to_string()); - } + if entry.path().is_dir() + && let Some(name) = entry.file_name().to_str() + { + agents.push(name.to_string()); } } } diff --git a/src/config/input.rs b/src/config/input.rs index c35ff4c..8578b9c 100644 --- a/src/config/input.rs +++ b/src/config/input.rs @@ -1,13 +1,13 @@ use super::*; use crate::client::{ - init_client, patch_messages, ChatCompletionsData, Client, ImageUrl, Message, MessageContent, - MessageContentPart, MessageContentToolCalls, MessageRole, Model, + ChatCompletionsData, Client, ImageUrl, Message, MessageContent, MessageContentPart, + MessageContentToolCalls, MessageRole, Model, init_client, patch_messages, }; use crate::function::ToolResult; -use crate::utils::{base64_encode, is_loader_protocol, sha256, AbortSignal}; +use crate::utils::{AbortSignal, base64_encode, is_loader_protocol, sha256}; -use anyhow::{bail, Context, Result}; +use anyhow::{Context, Result, bail}; use indexmap::IndexSet; use std::{collections::HashMap, fs::File, io::Read}; use unicode_width::{UnicodeWidthChar, UnicodeWidthStr}; diff --git a/src/config/macros.rs b/src/config/macros.rs index ca7d8ed..2b93c5d 100644 --- a/src/config/macros.rs +++ b/src/config/macros.rs @@ -1,7 +1,7 @@ -use crate::config::{ensure_parent_exists, Config, GlobalConfig, RoleLike}; +use crate::config::{Config, GlobalConfig, RoleLike, ensure_parent_exists}; use crate::repl::{run_repl_command, split_args_text}; -use crate::utils::{multiline_text, AbortSignal}; -use anyhow::{anyhow, Result}; +use crate::utils::{AbortSignal, multiline_text}; +use anyhow::{Result, anyhow}; use indexmap::IndexMap; use parking_lot::RwLock; use rust_embed::Embed; diff --git a/src/config/mod.rs b/src/config/mod.rs index ef0f3fe..25969d7 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -4,18 +4,18 @@ mod macros; mod role; mod session; -pub use self::agent::{complete_agent_variables, list_agents, Agent, AgentVariables}; +pub use self::agent::{Agent, AgentVariables, complete_agent_variables, list_agents}; pub use self::input::Input; pub use self::role::{ - Role, RoleLike, CODE_ROLE, CREATE_TITLE_ROLE, EXPLAIN_SHELL_ROLE, SHELL_ROLE, + CODE_ROLE, CREATE_TITLE_ROLE, EXPLAIN_SHELL_ROLE, Role, RoleLike, SHELL_ROLE, }; use self::session::Session; pub use macros::macro_execute; use mem::take; use crate::client::{ - create_client_config, list_client_types, list_models, ClientConfig, MessageContentToolCalls, - Model, ModelType, ProviderModels, OPENAI_COMPATIBLE_PROVIDERS, + ClientConfig, MessageContentToolCalls, Model, ModelType, OPENAI_COMPATIBLE_PROVIDERS, + ProviderModels, create_client_config, list_client_types, list_models, }; use crate::function::{FunctionDeclaration, Functions, ToolResult}; use crate::rag::Rag; @@ -24,14 +24,14 @@ use crate::utils::*; use crate::config::macros::Macro; use crate::mcp::{ - McpRegistry, MCP_INVOKE_META_FUNCTION_NAME_PREFIX, MCP_LIST_META_FUNCTION_NAME_PREFIX, + MCP_INVOKE_META_FUNCTION_NAME_PREFIX, MCP_LIST_META_FUNCTION_NAME_PREFIX, McpRegistry, }; -use crate::vault::{create_vault_password_file, interpolate_secrets, GlobalVault, Vault}; -use anyhow::{anyhow, bail, Context, Result}; +use crate::vault::{GlobalVault, Vault, create_vault_password_file, interpolate_secrets}; +use anyhow::{Context, Result, anyhow, bail}; use fancy_regex::Regex; use indexmap::IndexMap; use indoc::formatdoc; -use inquire::{list_option::ListOption, validator::Validation, Confirm, MultiSelect, Select, Text}; +use inquire::{Confirm, MultiSelect, Select, Text, list_option::ListOption, validator::Validation}; use log::LevelFilter; use parking_lot::RwLock; use serde::{Deserialize, Serialize}; @@ -41,7 +41,7 @@ use std::sync::LazyLock; use std::{ env, fs::{ - create_dir_all, read_dir, read_to_string, remove_dir_all, remove_file, File, OpenOptions, + File, OpenOptions, create_dir_all, read_dir, read_to_string, remove_dir_all, remove_file, }, io::Write, mem, @@ -50,7 +50,7 @@ use std::{ sync::{Arc, OnceLock}, }; use syntect::highlighting::ThemeSet; -use terminal_colorsaurus::{color_scheme, ColorScheme, QueryOptions}; +use terminal_colorsaurus::{ColorScheme, QueryOptions, color_scheme}; use tokio::runtime::Handle; pub const TEMP_ROLE_NAME: &str = "temp"; @@ -547,10 +547,10 @@ impl Config { for entry in read_dir(Self::agent_data_dir(name))? { let entry = entry?; - if let Some(file) = entry.file_name().to_str() { - if allowed.contains(&file) { - return Ok(entry.path()); - } + if let Some(file) = entry.file_name().to_str() + && allowed.contains(&file) + { + return Ok(entry.path()); } } @@ -783,24 +783,24 @@ impl Config { } "enabled_mcp_servers" => { let value: Option = parse_value(value)?; - if let Some(servers) = value.as_ref() { - if let Some(registry) = &config.read().mcp_registry { - if registry.list_configured_servers().is_empty() { - bail!( - "No MCP servers are configured. Please configure MCP servers first before setting 'enabled_mcp_servers'." - ); - } + if let Some(servers) = value.as_ref() + && let Some(registry) = &config.read().mcp_registry + { + if registry.list_configured_servers().is_empty() { + bail!( + "No MCP servers are configured. Please configure MCP servers first before setting 'enabled_mcp_servers'." + ); + } - if !servers.split(',').all(|s| { - registry - .list_configured_servers() - .contains(&s.trim().to_string()) - || s == "all" - }) { - bail!( - "Some of the specified MCP servers in 'enabled_mcp_servers' are configured. Please check your MCP server configuration." - ); - } + if !servers.split(',').all(|s| { + registry + .list_configured_servers() + .contains(&s.trim().to_string()) + || s == "all" + }) { + bail!( + "Some of the specified MCP servers in 'enabled_mcp_servers' are configured. Please check your MCP server configuration." + ); } } config.write().set_enabled_mcp_servers(value.clone()); @@ -1399,18 +1399,16 @@ impl Config { output, continuous, }) = &self.last_message + && (*continuous && !output.is_empty()) + && self.agent.is_some() == input.with_agent() { - if (*continuous && !output.is_empty()) - && self.agent.is_some() == input.with_agent() - { - let ans = Confirm::new( - "Start a session that incorporates the last question and answer?", - ) - .with_default(false) - .prompt()?; - if ans { - session.add_message(input, output)?; - } + let ans = Confirm::new( + "Start a session that incorporates the last question and answer?", + ) + .with_default(false) + .prompt()?; + if ans { + session.add_message(input, output)?; } } } @@ -1520,11 +1518,11 @@ impl Config { { let mut config = config.write(); let compression_threshold = config.compression_threshold; - if let Some(session) = config.session.as_mut() { - if session.needs_compression(compression_threshold) { - session.set_compressing(true); - needs_compression = true; - } + if let Some(session) = config.session.as_mut() + && session.needs_compression(compression_threshold) + { + session.set_compressing(true); + needs_compression = true; } }; if !needs_compression { @@ -1587,11 +1585,11 @@ impl Config { pub fn maybe_autoname_session(config: GlobalConfig) { let mut need_autoname = false; - if let Some(session) = config.write().session.as_mut() { - if session.need_autoname() { - session.set_autonaming(true); - need_autoname = true; - } + if let Some(session) = config.write().session.as_mut() + && session.need_autoname() + { + session.set_autonaming(true); + need_autoname = true; } if !need_autoname { return; @@ -2439,15 +2437,15 @@ impl Config { .unwrap_or_default() .to_string(), ); - if let Some(temperature) = role.temperature() { - if temperature != 0.0 { - output.insert("temperature", temperature.to_string()); - } + if let Some(temperature) = role.temperature() + && temperature != 0.0 + { + output.insert("temperature", temperature.to_string()); } - if let Some(top_p) = role.top_p() { - if top_p != 0.0 { - output.insert("top_p", top_p.to_string()); - } + if let Some(top_p) = role.top_p() + && top_p != 0.0 + { + output.insert("top_p", top_p.to_string()); } if self.dry_run { output.insert("dry_run", "true".to_string()); @@ -2458,10 +2456,10 @@ impl Config { if self.save { output.insert("save", "true".to_string()); } - if let Some(wrap) = &self.wrap { - if wrap != "no" { - output.insert("wrap", wrap.clone()); - } + if let Some(wrap) = &self.wrap + && wrap != "no" + { + output.insert("wrap", wrap.clone()); } if !role.is_derived() { output.insert("role", role.name().to_string()); @@ -2724,10 +2722,10 @@ impl Config { if let Some(Some(v)) = read_env_bool(&get_env_name("save")) { self.save = v; } - if let Ok(v) = env::var(get_env_name("keybindings")) { - if v == "vi" { - self.keybindings = v; - } + if let Ok(v) = env::var(get_env_name("keybindings")) + && v == "vi" + { + self.keybindings = v; } if let Some(v) = read_env_value::(&get_env_name("editor")) { self.editor = v; @@ -2742,10 +2740,10 @@ impl Config { if let Some(Some(v)) = read_env_bool(&get_env_name("function_calling_support")) { self.function_calling_support = v; } - if let Ok(v) = env::var(get_env_name("mapping_tools")) { - if let Ok(v) = serde_json::from_str(&v) { - self.mapping_tools = v; - } + if let Ok(v) = env::var(get_env_name("mapping_tools")) + && let Ok(v) = serde_json::from_str(&v) + { + self.mapping_tools = v; } if let Some(v) = read_env_value::(&get_env_name("enabled_tools")) { self.enabled_tools = v; @@ -2754,10 +2752,10 @@ impl Config { if let Some(Some(v)) = read_env_bool(&get_env_name("mcp_server_support")) { self.mcp_server_support = v; } - if let Ok(v) = env::var(get_env_name("mapping_mcp_servers")) { - if let Ok(v) = serde_json::from_str(&v) { - self.mapping_mcp_servers = v; - } + if let Ok(v) = env::var(get_env_name("mapping_mcp_servers")) + && let Ok(v) = serde_json::from_str(&v) + { + self.mapping_mcp_servers = v; } if let Some(v) = read_env_value::(&get_env_name("enabled_mcp_servers")) { self.enabled_mcp_servers = v; @@ -2805,10 +2803,10 @@ impl Config { self.rag_template = v; } - if let Ok(v) = env::var(get_env_name("document_loaders")) { - if let Ok(v) = serde_json::from_str(&v) { - self.document_loaders = v; - } + if let Ok(v) = env::var(get_env_name("document_loaders")) + && let Ok(v) = serde_json::from_str(&v) + { + self.document_loaders = v; } if let Some(Some(v)) = read_env_bool(&get_env_name("highlight")) { @@ -2820,14 +2818,14 @@ impl Config { if self.highlight && self.theme.is_none() { if let Some(v) = read_env_value::(&get_env_name("theme")) { self.theme = v; - } else if *IS_STDOUT_TERMINAL { - if let Ok(color_scheme) = color_scheme(QueryOptions::default()) { - let theme = match color_scheme { - ColorScheme::Dark => "dark", - ColorScheme::Light => "light", - }; - self.theme = Some(theme.into()); - } + } else if *IS_STDOUT_TERMINAL + && let Ok(color_scheme) = color_scheme(QueryOptions::default()) + { + let theme = match color_scheme { + ColorScheme::Dark => "dark", + ColorScheme::Light => "light", + }; + self.theme = Some(theme.into()); } } if let Some(v) = read_env_value::(&get_env_name("left_prompt")) { @@ -2934,7 +2932,7 @@ pub fn load_env_file() -> Result<()> { continue; } if let Some((key, value)) = line.split_once('=') { - env::set_var(key.trim(), value.trim()); + unsafe { env::set_var(key.trim(), value.trim()) }; } } Ok(()) diff --git a/src/config/role.rs b/src/config/role.rs index f2cd45d..ebf57eb 100644 --- a/src/config/role.rs +++ b/src/config/role.rs @@ -64,11 +64,11 @@ impl Role { pub fn new(name: &str, content: &str) -> Self { let mut metadata = ""; let mut prompt = content.trim(); - if let Ok(Some(caps)) = RE_METADATA.captures(content) { - if let (Some(metadata_value), Some(prompt_value)) = (caps.get(1), caps.get(2)) { - metadata = metadata_value.as_str().trim(); - prompt = prompt_value.as_str().trim(); - } + if let Ok(Some(caps)) = RE_METADATA.captures(content) + && let (Some(metadata_value), Some(prompt_value)) = (caps.get(1), caps.get(2)) + { + metadata = metadata_value.as_str().trim(); + prompt = prompt_value.as_str().trim(); } let mut prompt = prompt.to_string(); interpolate_variables(&mut prompt); @@ -77,23 +77,20 @@ impl Role { prompt, ..Default::default() }; - if !metadata.is_empty() { - if let Ok(value) = serde_yaml::from_str::(metadata) { - if let Some(value) = value.as_object() { - for (key, value) in value { - match key.as_str() { - "model" => role.model_id = value.as_str().map(|v| v.to_string()), - "temperature" => role.temperature = value.as_f64(), - "top_p" => role.top_p = value.as_f64(), - "enabled_tools" => { - role.enabled_tools = value.as_str().map(|v| v.to_string()) - } - "enabled_mcp_servers" => { - role.enabled_mcp_servers = value.as_str().map(|v| v.to_string()) - } - _ => (), - } + if !metadata.is_empty() + && let Ok(value) = serde_yaml::from_str::(metadata) + && let Some(value) = value.as_object() + { + for (key, value) in value { + match key.as_str() { + "model" => role.model_id = value.as_str().map(|v| v.to_string()), + "temperature" => role.temperature = value.as_f64(), + "top_p" => role.top_p = value.as_f64(), + "enabled_tools" => role.enabled_tools = value.as_str().map(|v| v.to_string()), + "enabled_mcp_servers" => { + role.enabled_mcp_servers = value.as_str().map(|v| v.to_string()) } + _ => (), } } } diff --git a/src/config/session.rs b/src/config/session.rs index ec53999..cfc8e02 100644 --- a/src/config/session.rs +++ b/src/config/session.rs @@ -4,9 +4,9 @@ use super::*; use crate::client::{Message, MessageContent, MessageRole}; use crate::render::MarkdownRender; -use anyhow::{bail, Context, Result}; +use anyhow::{Context, Result, bail}; use fancy_regex::Regex; -use inquire::{validator::Validation, Confirm, Text}; +use inquire::{Confirm, Text, validator::Validation}; use serde::{Deserialize, Serialize}; use serde_json::json; use std::collections::HashMap; @@ -98,10 +98,10 @@ impl Session { session.path = Some(path.display().to_string()); } - if let Some(role_name) = &session.role_name { - if let Ok(role) = config.retrieve_role(role_name) { - session.role_prompt = role.prompt().to_string(); - } + if let Some(role_name) = &session.role_name + && let Ok(role) = config.retrieve_role(role_name) + { + session.role_prompt = role.prompt().to_string(); } session.update_tokens(); @@ -473,23 +473,25 @@ impl Session { pub fn guard_empty(&self) -> Result<()> { if !self.is_empty() { - bail!("Cannot perform this operation because the session has messages, please `.empty session` first."); + bail!( + "Cannot perform this operation because the session has messages, please `.empty session` first." + ); } Ok(()) } pub fn add_message(&mut self, input: &Input, output: &str) -> Result<()> { if input.continue_output().is_some() { - if let Some(message) = self.messages.last_mut() { - if let MessageContent::Text(text) = &mut message.content { - *text = format!("{text}{output}"); - } + if let Some(message) = self.messages.last_mut() + && let MessageContent::Text(text) = &mut message.content + { + *text = format!("{text}{output}"); } } else if input.regenerate() { - if let Some(message) = self.messages.last_mut() { - if let MessageContent::Text(text) = &mut message.content { - *text = output.to_string(); - } + if let Some(message) = self.messages.last_mut() + && let MessageContent::Text(text) = &mut message.content + { + *text = output.to_string(); } } else { if self.messages.is_empty() { @@ -553,14 +555,14 @@ impl Session { if len == 0 { messages = input.role().build_messages(input); need_add_msg = false; - } else if len == 1 && self.compressed_messages.len() >= 2 { - if let Some(index) = self + } else if len == 1 + && self.compressed_messages.len() >= 2 + && let Some(index) = self .compressed_messages .iter() .rposition(|v| v.role == MessageRole::User) - { - messages.extend(self.compressed_messages[index..].to_vec()); - } + { + messages.extend(self.compressed_messages[index..].to_vec()); } if need_add_msg { messages.push(Message::new(MessageRole::User, input.message_content())); diff --git a/src/function/mod.rs b/src/function/mod.rs index ce5cf88..20810d9 100644 --- a/src/function/mod.rs +++ b/src/function/mod.rs @@ -6,12 +6,12 @@ use crate::{ use crate::config::ensure_parent_exists; use crate::mcp::{MCP_INVOKE_META_FUNCTION_NAME_PREFIX, MCP_LIST_META_FUNCTION_NAME_PREFIX}; use crate::parsers::{bash, python}; -use anyhow::{anyhow, bail, Context, Result}; +use anyhow::{Context, Result, anyhow, bail}; use indexmap::IndexMap; use indoc::formatdoc; use rust_embed::Embed; use serde::{Deserialize, Serialize}; -use serde_json::{json, Value}; +use serde_json::{Value, json}; use std::ffi::OsStr; use std::fs::File; use std::io::Write; diff --git a/src/main.rs b/src/main.rs index 6df0480..86b67de 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,11 +15,12 @@ mod vault; extern crate log; use crate::client::{ - call_chat_completions, call_chat_completions_streaming, list_models, ModelType, + ModelType, call_chat_completions, call_chat_completions_streaming, list_models, }; use crate::config::{ - ensure_parent_exists, list_agents, load_env_file, macro_execute, Agent, Config, GlobalConfig, - Input, WorkingMode, CODE_ROLE, EXPLAIN_SHELL_ROLE, SHELL_ROLE, TEMP_SESSION_NAME, + Agent, CODE_ROLE, Config, EXPLAIN_SHELL_ROLE, GlobalConfig, Input, SHELL_ROLE, + TEMP_SESSION_NAME, WorkingMode, ensure_parent_exists, list_agents, load_env_file, + macro_execute, }; use crate::render::render_error; use crate::repl::Repl; @@ -27,7 +28,7 @@ use crate::utils::*; use crate::cli::Cli; use crate::vault::Vault; -use anyhow::{bail, Result}; +use anyhow::{Result, bail}; use clap::{CommandFactory, Parser}; use clap_complete::CompleteEnv; use inquire::Text; diff --git a/src/mcp/mod.rs b/src/mcp/mod.rs index b9e8b97..32781b9 100644 --- a/src/mcp/mod.rs +++ b/src/mcp/mod.rs @@ -1,16 +1,16 @@ use crate::config::Config; -use crate::utils::{abortable_run_with_spinner, AbortSignal}; +use crate::utils::{AbortSignal, abortable_run_with_spinner}; use crate::vault::interpolate_secrets; -use anyhow::{anyhow, Context, Result}; +use anyhow::{Context, Result, anyhow}; use futures_util::future::BoxFuture; -use futures_util::{stream, StreamExt, TryStreamExt}; +use futures_util::{StreamExt, TryStreamExt, stream}; use indoc::formatdoc; use rmcp::model::{CallToolRequestParam, CallToolResult}; use rmcp::service::RunningService; use rmcp::transport::TokioChildProcess; use rmcp::{RoleClient, ServiceExt}; use serde::Deserialize; -use serde_json::{json, Value}; +use serde_json::{Value, json}; use std::borrow::Cow; use std::collections::{HashMap, HashSet}; use std::fs::OpenOptions; @@ -148,7 +148,9 @@ impl McpRegistry { enabled_mcp_servers: Option, ) -> Result<()> { if self.config.is_none() { - debug!("MCP config is not present; assuming MCP servers are disabled globally. Skipping MCP initialization"); + debug!( + "MCP config is not present; assuming MCP servers are disabled globally. Skipping MCP initialization" + ); return Ok(()); } diff --git a/src/parsers/bash.rs b/src/parsers/bash.rs index c237c81..c00269c 100644 --- a/src/parsers/bash.rs +++ b/src/parsers/bash.rs @@ -1,5 +1,5 @@ use crate::function::{FunctionDeclaration, JsonSchema}; -use anyhow::{bail, Context, Result}; +use anyhow::{Context, Result, bail}; use argc::{ChoiceValue, CommandValue, FlagOptionValue}; use indexmap::IndexMap; use std::fs::File; @@ -95,10 +95,10 @@ fn with_description(mut schema: JsonSchema, describe: &str) -> JsonSchema { } fn with_enum(mut schema: JsonSchema, choice: &Option) -> JsonSchema { - if let Some(ChoiceValue::Values(values)) = choice { - if !values.is_empty() { - schema.enum_value = Some(values.clone()); - } + if let Some(ChoiceValue::Values(values)) = choice + && !values.is_empty() + { + schema.enum_value = Some(values.clone()); } schema } diff --git a/src/parsers/python.rs b/src/parsers/python.rs index d963e5e..147ae9b 100644 --- a/src/parsers/python.rs +++ b/src/parsers/python.rs @@ -1,9 +1,9 @@ use crate::function::{FunctionDeclaration, JsonSchema}; -use anyhow::{bail, Context, Result}; +use anyhow::{Context, Result, bail}; use ast::{Stmt, StmtFunctionDef}; use indexmap::IndexMap; use rustpython_ast::{Constant, Expr, UnaryOp}; -use rustpython_parser::{ast, Mode}; +use rustpython_parser::{Mode, ast}; use serde_json::Value; use std::fs::File; use std::io::Read; @@ -104,12 +104,11 @@ fn python_to_function_declarations( fn get_docstring_from_body(body: &[Stmt]) -> Option { let first = body.first()?; - if let Stmt::Expr(expr_stmt) = first { - if let Expr::Constant(constant) = &*expr_stmt.value { - if let Constant::Str(s) = &constant.value { - return Some(s.clone()); - } - } + if let Stmt::Expr(expr_stmt) = first + && let Expr::Constant(constant) = &*expr_stmt.value + && let Constant::Str(s) = &constant.value + { + return Some(s.clone()); } None } @@ -351,10 +350,10 @@ fn build_parameters_schema(params: &[Param], _description: &str) -> JsonSchema { "str" }; - if let Some(d) = &p.doc_desc { - if !d.is_empty() { - schema.description = Some(d.clone()); - } + if let Some(d) = &p.doc_desc + && !d.is_empty() + { + schema.description = Some(d.clone()); } apply_type_to_schema(ty, &mut schema); diff --git a/src/rag/mod.rs b/src/rag/mod.rs index be12040..8d5f6bf 100644 --- a/src/rag/mod.rs +++ b/src/rag/mod.rs @@ -7,11 +7,11 @@ use crate::utils::*; mod serde_vectors; mod splitter; -use anyhow::{anyhow, bail, Context, Result}; +use anyhow::{Context, Result, anyhow, bail}; use bm25::{Language, SearchEngine, SearchEngineBuilder}; use hnsw_rs::prelude::*; use indexmap::{IndexMap, IndexSet}; -use inquire::{required, validator::Validation, Confirm, Select, Text}; +use inquire::{Confirm, Select, Text, required, validator::Validation}; use parking_lot::RwLock; use serde::{Deserialize, Serialize}; use serde_json::json; @@ -434,19 +434,18 @@ impl Rag { } in loaded_documents { let hash = sha256(&contents); - if let Some(file_ids) = to_deleted.get_mut(&hash) { - if let Some((i, _)) = file_ids + if let Some(file_ids) = to_deleted.get_mut(&hash) + && let Some((i, _)) = file_ids .iter() .enumerate() .find(|(_, v)| self.data.files[*v].path == path) - { - if file_ids.len() == 1 { - to_deleted.swap_remove(&hash); - } else { - file_ids.remove(i); - } - continue; + { + if file_ids.len() == 1 { + to_deleted.swap_remove(&hash); + } else { + file_ids.remove(i); } + continue; } let extension = metadata .swap_remove(EXTENSION_METADATA) @@ -679,7 +678,7 @@ impl Rag { Err(e) => { return Err(e).with_context(|| { format!("Failed to create embedding after {retry_limit} attempts") - })? + })?; } } }; @@ -926,11 +925,7 @@ fn add_documents() -> Result> { .split(';') .filter_map(|v| { let v = v.trim().to_string(); - if v.is_empty() { - None - } else { - Some(v) - } + if v.is_empty() { None } else { Some(v) } }) .collect(); Ok(paths) diff --git a/src/rag/serde_vectors.rs b/src/rag/serde_vectors.rs index 1f66230..db41be9 100644 --- a/src/rag/serde_vectors.rs +++ b/src/rag/serde_vectors.rs @@ -1,7 +1,7 @@ use super::*; -use base64::{engine::general_purpose::STANDARD, Engine}; -use serde::{de, Deserializer, Serializer}; +use base64::{Engine, engine::general_purpose::STANDARD}; +use serde::{Deserializer, Serializer, de}; pub fn serialize( vectors: &IndexMap>, diff --git a/src/rag/splitter/mod.rs b/src/rag/splitter/mod.rs index 8143a14..c6bb6ff 100644 --- a/src/rag/splitter/mod.rs +++ b/src/rag/splitter/mod.rs @@ -120,10 +120,10 @@ impl RecursiveCharacterTextSplitter { } }; - if prev_chunk.is_some() { - if let Some(chunk_overlap_header) = chunk_overlap_header { - page_content += chunk_overlap_header; - } + if prev_chunk.is_some() + && let Some(chunk_overlap_header) = chunk_overlap_header + { + page_content += chunk_overlap_header; } let metadata = metadatas[i].clone(); @@ -240,11 +240,7 @@ impl RecursiveCharacterTextSplitter { fn join_docs(&self, docs: &[String], separator: &str) -> Option { let text = docs.join(separator).trim().to_string(); - if text.is_empty() { - None - } else { - Some(text) - } + if text.is_empty() { None } else { Some(text) } } } @@ -309,7 +305,7 @@ mod tests { use super::*; use indexmap::IndexMap; use pretty_assertions::assert_eq; - use serde_json::{json, Value}; + use serde_json::{Value, json}; fn build_metadata(source: &str) -> Value { json!({ "source": source }) diff --git a/src/render/markdown.rs b/src/render/markdown.rs index 9671961..3cb3821 100644 --- a/src/render/markdown.rs +++ b/src/render/markdown.rs @@ -1,7 +1,7 @@ use crate::utils::decode_bin; use ansi_colours::AsRGB; -use anyhow::{anyhow, Context, Result}; +use anyhow::{Context, Result, anyhow}; use crossterm::style::{Color, Stylize}; use crossterm::terminal; use std::collections::HashMap; @@ -122,10 +122,10 @@ impl MarkdownRender { line_type = LineType::Normal; } LineType::CodeBegin => { - if code_syntax.is_none() { - if let Some(syntax) = self.syntax_set.find_syntax_by_first_line(line) { - code_syntax = Some(syntax.clone()); - } + if code_syntax.is_none() + && let Some(syntax) = self.syntax_set.find_syntax_by_first_line(line) + { + code_syntax = Some(syntax.clone()); } line_type = LineType::CodeInner; is_code = true; diff --git a/src/render/mod.rs b/src/render/mod.rs index 0aa3184..3472095 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -4,7 +4,7 @@ mod stream; pub use self::markdown::{MarkdownRender, RenderOptions}; use self::stream::{markdown_stream, raw_stream}; -use crate::utils::{error_text, pretty_error, AbortSignal, IS_STDOUT_TERMINAL}; +use crate::utils::{AbortSignal, IS_STDOUT_TERMINAL, error_text, pretty_error}; use crate::{client::SseEvent, config::GlobalConfig}; use anyhow::Result; diff --git a/src/render/stream.rs b/src/render/stream.rs index ad473fb..0c0a3e1 100644 --- a/src/render/stream.rs +++ b/src/render/stream.rs @@ -1,6 +1,6 @@ use super::{MarkdownRender, SseEvent}; -use crate::utils::{poll_abort_signal, spawn_spinner, AbortSignal}; +use crate::utils::{AbortSignal, poll_abort_signal, spawn_spinner}; use anyhow::Result; use crossterm::{ @@ -8,7 +8,7 @@ use crossterm::{ terminal::{self, disable_raw_mode, enable_raw_mode}, }; use std::{ - io::{stdout, Stdout, Write}, + io::{Stdout, Write, stdout}, time::Duration, }; use textwrap::core::display_width; diff --git a/src/repl/completer.rs b/src/repl/completer.rs index 7ce82df..dd60a79 100644 --- a/src/repl/completer.rs +++ b/src/repl/completer.rs @@ -1,4 +1,4 @@ -use super::{ReplCommand, REPL_COMMANDS}; +use super::{REPL_COMMANDS, ReplCommand}; use crate::{config::GlobalConfig, utils::fuzzy_filter}; diff --git a/src/repl/mod.rs b/src/repl/mod.rs index e1ec252..f0cca2d 100644 --- a/src/repl/mod.rs +++ b/src/repl/mod.rs @@ -8,23 +8,23 @@ use self::prompt::ReplPrompt; use crate::client::{call_chat_completions, call_chat_completions_streaming}; use crate::config::{ - macro_execute, AgentVariables, AssertState, Config, GlobalConfig, Input, LastMessage, - StateFlags, + AgentVariables, AssertState, Config, GlobalConfig, Input, LastMessage, StateFlags, + macro_execute, }; use crate::render::render_error; use crate::utils::{ - abortable_run_with_spinner, create_abort_signal, dimmed_text, set_text, temp_file, AbortSignal, + AbortSignal, abortable_run_with_spinner, create_abort_signal, dimmed_text, set_text, temp_file, }; use crate::mcp::McpRegistry; -use anyhow::{bail, Context, Result}; +use anyhow::{Context, Result, bail}; use crossterm::cursor::SetCursorStyle; use fancy_regex::Regex; use reedline::CursorConfig; use reedline::{ - default_emacs_keybindings, default_vi_insert_keybindings, default_vi_normal_keybindings, ColumnarMenu, EditCommand, EditMode, Emacs, KeyCode, KeyModifiers, Keybindings, Reedline, - ReedlineEvent, ReedlineMenu, ValidationResult, Validator, Vi, + ReedlineEvent, ReedlineMenu, ValidationResult, Validator, Vi, default_emacs_keybindings, + default_vi_insert_keybindings, default_vi_normal_keybindings, }; use reedline::{MenuBuilder, Signal}; use std::sync::LazyLock; @@ -382,10 +382,10 @@ pub async fn run_repl_command( abort_signal: AbortSignal, mut line: &str, ) -> Result { - if let Ok(Some(captures)) = MULTILINE_RE.captures(line) { - if let Some(text_match) = captures.get(1) { - line = text_match.as_str(); - } + if let Ok(Some(captures)) = MULTILINE_RE.captures(line) + && let Some(text_match) = captures.get(1) + { + line = text_match.as_str(); } match parse_command(line) { Some((cmd, args)) => match cmd { diff --git a/src/utils/abort_signal.rs b/src/utils/abort_signal.rs index 7edff22..36f4bf1 100644 --- a/src/utils/abort_signal.rs +++ b/src/utils/abort_signal.rs @@ -2,8 +2,8 @@ use anyhow::Result; use crossterm::event::{self, Event, KeyCode, KeyModifiers}; use std::{ sync::{ - atomic::{AtomicBool, Ordering}, Arc, + atomic::{AtomicBool, Ordering}, }, time::Duration, }; @@ -69,19 +69,19 @@ pub async fn wait_abort_signal(abort_signal: &AbortSignal) { } pub fn poll_abort_signal(abort_signal: &AbortSignal) -> Result { - if event::poll(Duration::from_millis(25))? { - if let Event::Key(key) = event::read()? { - match key.code { - KeyCode::Char('c') if key.modifiers == KeyModifiers::CONTROL => { - abort_signal.set_ctrlc(); - return Ok(true); - } - KeyCode::Char('d') if key.modifiers == KeyModifiers::CONTROL => { - abort_signal.set_ctrld(); - return Ok(true); - } - _ => {} + if event::poll(Duration::from_millis(25))? + && let Event::Key(key) = event::read()? + { + match key.code { + KeyCode::Char('c') if key.modifiers == KeyModifiers::CONTROL => { + abort_signal.set_ctrlc(); + return Ok(true); } + KeyCode::Char('d') if key.modifiers == KeyModifiers::CONTROL => { + abort_signal.set_ctrld(); + return Ok(true); + } + _ => {} } } Ok(false) diff --git a/src/utils/clipboard.rs b/src/utils/clipboard.rs index 2361d9f..d849d14 100644 --- a/src/utils/clipboard.rs +++ b/src/utils/clipboard.rs @@ -3,7 +3,7 @@ use anyhow::Context; #[cfg(not(any(target_os = "android", target_os = "emscripten")))] mod internal { use arboard::Clipboard; - use base64::{engine::general_purpose::STANDARD, Engine as _}; + use base64::{Engine as _, engine::general_purpose::STANDARD}; use std::sync::{LazyLock, Mutex}; static CLIPBOARD: LazyLock>> = diff --git a/src/utils/command.rs b/src/utils/command.rs index 2143dc9..76ee213 100644 --- a/src/utils/command.rs +++ b/src/utils/command.rs @@ -10,7 +10,7 @@ use std::{ process::Command, }; -use anyhow::{anyhow, bail, Context, Result}; +use anyhow::{Context, Result, anyhow, bail}; use dirs::home_dir; use std::sync::LazyLock; diff --git a/src/utils/crypto.rs b/src/utils/crypto.rs index 494ea84..b0697e3 100644 --- a/src/utils/crypto.rs +++ b/src/utils/crypto.rs @@ -1,4 +1,4 @@ -use base64::{engine::general_purpose::STANDARD, Engine}; +use base64::{Engine, engine::general_purpose::STANDARD}; use hmac::{Hmac, Mac}; use sha2::{Digest, Sha256}; diff --git a/src/utils/html_to_md.rs b/src/utils/html_to_md.rs index 2bc5e54..1fbe642 100644 --- a/src/utils/html_to_md.rs +++ b/src/utils/html_to_md.rs @@ -1,6 +1,6 @@ use std::{cell::RefCell, rc::Rc}; -use html_to_markdown::{markdown, TagHandler}; +use html_to_markdown::{TagHandler, markdown}; pub fn html_to_md(html: &str) -> String { let mut handlers: Vec = vec![ diff --git a/src/utils/input.rs b/src/utils/input.rs index ebcb3cf..2dd75dc 100644 --- a/src/utils/input.rs +++ b/src/utils/input.rs @@ -1,7 +1,7 @@ use anyhow::Result; use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyModifiers}; use crossterm::terminal::{disable_raw_mode, enable_raw_mode}; -use std::io::{stdout, Write}; +use std::io::{Write, stdout}; /// Reads a single character from stdin without requiring Enter /// Returns the character if it's one of the valid options, or the default if Enter is pressed diff --git a/src/utils/loader.rs b/src/utils/loader.rs index b36bf4a..77db9fa 100644 --- a/src/utils/loader.rs +++ b/src/utils/loader.rs @@ -1,6 +1,6 @@ use super::*; -use anyhow::{anyhow, Context, Result}; +use anyhow::{Context, Result, anyhow}; use indexmap::IndexMap; use serde::{Deserialize, Serialize}; use std::collections::HashMap; diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 2142812..f0e0648 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -29,7 +29,7 @@ pub use self::variables::*; use anyhow::{Context, Result}; use fancy_regex::Regex; -use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher}; +use fuzzy_matcher::{FuzzyMatcher, skim::SkimMatcherV2}; use is_terminal::IsTerminal; use std::borrow::Cow; use std::sync::LazyLock; diff --git a/src/utils/path.rs b/src/utils/path.rs index 2a2ab4d..8bc6b19 100644 --- a/src/utils/path.rs +++ b/src/utils/path.rs @@ -1,7 +1,7 @@ use std::fs; use std::path::{Component, Path, PathBuf}; -use anyhow::{bail, Result}; +use anyhow::{Result, bail}; use fancy_regex::Regex; use indexmap::IndexSet; use path_absolutize::Absolutize; @@ -97,11 +97,12 @@ pub fn to_absolute_path(path: &str) -> Result { pub fn resolve_home_dir(path: &str) -> String { let mut path = path.to_string(); - if path.starts_with("~/") || path.starts_with("~\\") { - if let Some(home_dir) = dirs::home_dir() { - path.replace_range(..1, &home_dir.display().to_string()); - } + if (path.starts_with("~/") || path.starts_with("~\\")) + && let Some(home_dir) = dirs::home_dir() + { + path.replace_range(..1, &home_dir.display().to_string()); } + path } @@ -241,22 +242,20 @@ fn add_file(files: &mut IndexSet, suffixes: Option<&Vec>, path: fn is_valid_extension(suffixes: Option<&Vec>, path: &Path) -> bool { let filename_regex = Regex::new(r"^.+\.*").unwrap(); - if let Some(suffixes) = suffixes { - if !suffixes.is_empty() { - if let Ok(Some(_)) = filename_regex.find(&suffixes.join(",")) { - let file_name = path - .file_name() - .and_then(|v| v.to_str()) - .expect("invalid filename") - .to_string(); - return suffixes.contains(&file_name); - } else if let Some(extension) = - path.extension().map(|v| v.to_string_lossy().to_string()) - { - return suffixes.contains(&extension); - } - return false; + if let Some(suffixes) = suffixes + && !suffixes.is_empty() + { + if let Ok(Some(_)) = filename_regex.find(&suffixes.join(",")) { + let file_name = path + .file_name() + .and_then(|v| v.to_str()) + .expect("invalid filename") + .to_string(); + return suffixes.contains(&file_name); + } else if let Some(extension) = path.extension().map(|v| v.to_string_lossy().to_string()) { + return suffixes.contains(&extension); } + return false; } true } diff --git a/src/utils/request.rs b/src/utils/request.rs index 95c4f9c..c00ef0d 100644 --- a/src/utils/request.rs +++ b/src/utils/request.rs @@ -1,8 +1,8 @@ use super::*; -use anyhow::{anyhow, bail, Context, Result}; +use anyhow::{Context, Result, anyhow, bail}; use fancy_regex::Regex; -use futures_util::{stream, StreamExt}; +use futures_util::{StreamExt, stream}; use http::header::CONTENT_TYPE; use reqwest::Url; use scraper::{Html, Selector}; diff --git a/src/utils/spinner.rs b/src/utils/spinner.rs index dab9af1..a38a6d6 100644 --- a/src/utils/spinner.rs +++ b/src/utils/spinner.rs @@ -1,10 +1,10 @@ -use super::{poll_abort_signal, wait_abort_signal, AbortSignal, IS_STDOUT_TERMINAL}; +use super::{AbortSignal, IS_STDOUT_TERMINAL, poll_abort_signal, wait_abort_signal}; -use anyhow::{bail, Result}; +use anyhow::{Result, bail}; use crossterm::{cursor, queue, style, terminal}; use std::{ future::Future, - io::{stdout, Write}, + io::{Write, stdout}, time::Duration, }; use tokio::{ diff --git a/src/vault/mod.rs b/src/vault/mod.rs index b12f1d2..e170075 100644 --- a/src/vault/mod.rs +++ b/src/vault/mod.rs @@ -9,9 +9,9 @@ use crate::config::Config; use crate::vault::utils::ensure_password_file_initialized; use anyhow::{Context, Result}; use fancy_regex::Regex; -use gman::providers::local::LocalProvider; use gman::providers::SecretProvider; -use inquire::{required, Password, PasswordDisplayMode}; +use gman::providers::local::LocalProvider; +use inquire::{Password, PasswordDisplayMode, required}; use std::sync::{Arc, LazyLock}; use tokio::runtime::Handle; diff --git a/src/vault/utils.rs b/src/vault/utils.rs index d6da91c..1a81003 100644 --- a/src/vault/utils.rs +++ b/src/vault/utils.rs @@ -1,11 +1,11 @@ use crate::config::ensure_parent_exists; -use crate::vault::{Vault, SECRET_RE}; -use anyhow::anyhow; +use crate::vault::{SECRET_RE, Vault}; use anyhow::Result; +use anyhow::anyhow; use gman::providers::local::LocalProvider; use indoc::formatdoc; use inquire::validator::Validation; -use inquire::{min_length, required, Confirm, Password, PasswordDisplayMode, Text}; +use inquire::{Confirm, Password, PasswordDisplayMode, Text, min_length, required}; use std::borrow::Cow; use std::path::PathBuf; @@ -21,11 +21,16 @@ pub fn ensure_password_file_initialized(local_provider: &mut LocalProvider) -> R if !file_contents.trim().is_empty() { Ok(()) } else { - Err(anyhow!("The configured password file '{}' is empty. Please populate it with a password and try again.", vault_password_file.display())) + Err(anyhow!( + "The configured password file '{}' is empty. Please populate it with a password and try again.", + vault_password_file.display() + )) } } } else { - Err(anyhow!("A password file is required to utilize the Loki vault. Please configure a password file in your config file and try again.")) + Err(anyhow!( + "A password file is required to utilize the Loki vault. Please configure a password file in your config file and try again." + )) } } @@ -40,7 +45,9 @@ pub fn create_vault_password_file(vault: &mut Vault) -> Result<()> { { let file_contents = std::fs::read_to_string(&vault_password_file)?; if !file_contents.trim().is_empty() { - debug!("create_vault_password_file was called but the password file already exists and is non-empty"); + debug!( + "create_vault_password_file was called but the password file already exists and is non-empty" + ); return Ok(()); } } @@ -56,7 +63,10 @@ pub fn create_vault_password_file(vault: &mut Vault) -> Result<()> { .prompt()?; if !ans { - return Err(anyhow!("The configured password file '{}' is empty. Please populate it with a password and try again.", vault_password_file.display())); + return Err(anyhow!( + "The configured password file '{}' is empty. Please populate it with a password and try again.", + vault_password_file.display() + )); } let password = Password::new("Enter a password to encrypt all vault secrets:") @@ -85,7 +95,9 @@ pub fn create_vault_password_file(vault: &mut Vault) -> Result<()> { .prompt()?; if !ans { - return Err(anyhow!("A password file is required to utilize the Loki vault. Please configure a password file in your config file and try again.")); + return Err(anyhow!( + "A password file is required to utilize the Loki vault. Please configure a password file in your config file and try again." + )); } let password_file: PathBuf = Text::new("Enter the path to the password file to create:")