From ba60c8829b7a7928e4a4066d8083b1d1e613af46 Mon Sep 17 00:00:00 2001 From: Gly Date: Sat, 16 Sep 2023 15:40:18 +0200 Subject: [PATCH 01/37] Add `executor` --- src/execution/mod.rs | 66 ++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 1 + 2 files changed, 67 insertions(+) create mode 100644 src/execution/mod.rs diff --git a/src/execution/mod.rs b/src/execution/mod.rs new file mode 100644 index 0000000..f2ba113 --- /dev/null +++ b/src/execution/mod.rs @@ -0,0 +1,66 @@ +use anyhow::Result; +use super::Formatter; + +#[derive(Default, Debug)] +struct CodeBlock { + code: String, + language: String, +} + +impl CodeBlock { + fn new(language: String) -> Self { + Self { code: String::new(), language } + } +} +#[derive(Default, Debug)] +pub struct Executor{ + is_code: bool, + is_newline: bool, + current_token: String, + codes: Vec +} + +impl Formatter for Executor { + fn push(&mut self, text: &str) -> Result<()> { + for c in text.chars() { + match c { + '`' => { + if self.is_newline { + self.current_token.push(c); + } + }, + '\n' => { + if self.current_token.starts_with("```") { + self.switch_code_block(); + } else if self.is_code { + self.codes.last_mut().unwrap().code.push(c); + } + self.current_token.clear(); + }, + _ => { + if self.is_code { + self.codes.last_mut().unwrap().code.push(c); + } else if self.is_newline && self.current_token.starts_with("```") { + self.current_token.push(c); + } else { + self.is_newline = false; + } + }, + } + } + Ok(()) + } +} + +impl Executor { + fn switch_code_block(&mut self) { + self.is_code = !self.is_code; + if self.is_code { + let language = self.current_token[3..].trim(); + self.codes.push(CodeBlock::new(language.into())); + } else { + // remove last newline + self.codes.last_mut().unwrap().code.pop(); + } + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 0dd92ec..9627d7b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ pub mod arguments; +mod execution; mod generators; mod formatters; mod config; From 4b20c6d416a4044defc4d3528abc126aeebc2a20 Mon Sep 17 00:00:00 2001 From: Gly Date: Sat, 16 Sep 2023 16:31:33 +0200 Subject: [PATCH 02/37] add Executor new --- src/execution/mod.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/execution/mod.rs b/src/execution/mod.rs index f2ba113..13c8c0d 100644 --- a/src/execution/mod.rs +++ b/src/execution/mod.rs @@ -53,6 +53,9 @@ impl Formatter for Executor { } impl Executor { + pub fn new() -> Self { + Self::default() + } fn switch_code_block(&mut self) { self.is_code = !self.is_code; if self.is_code { From 80e735595388208260cf5e2c05ed3eb3e0ffd87d Mon Sep 17 00:00:00 2001 From: Gly Date: Sat, 16 Sep 2023 16:31:55 +0200 Subject: [PATCH 03/37] add debugger to read file --- Cargo.lock | 1 + Cargo.toml | 1 + src/generators/debug.rs | 14 ++++++++++++++ src/generators/mod.rs | 1 + 4 files changed, 17 insertions(+) create mode 100644 src/generators/debug.rs diff --git a/Cargo.lock b/Cargo.lock index 1e6a907..5fa3d2f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -51,6 +51,7 @@ dependencies = [ "thiserror", "tokio", "tokio-stream", + "tokio-util", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 5b45144..0ab56cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ smartstring = { version = "1.0", features = ["serde"] } thiserror = "1.0" tokio = { version = "1", features = ["full"] } tokio-stream = "0.1.12" +tokio-util = {version = "0.7", features = ["io"]} aio-cargo-info = { path = "./crates/aio-cargo-info", version = "0.1" } diff --git a/src/generators/debug.rs b/src/generators/debug.rs new file mode 100644 index 0000000..59e4c80 --- /dev/null +++ b/src/generators/debug.rs @@ -0,0 +1,14 @@ +use crate::args; +use super::{ResultRun, ResultStream, Error}; + +pub async fn run(config: crate::config::Config, args: args::ProcessedArgs) -> ResultRun { + use tokio_stream::StreamExt; + let input = args.input; + let file = tokio::fs::File::open(&input).await.map_err(|e| Error::Custom(std::borrow::Cow::Owned(e.to_string())))?; + + let stream = tokio_util::io::ReaderStream::new(file).map(|mut r| -> ResultStream { + let bytes = r.map_err(|e| Error::Custom(std::borrow::Cow::Owned(e.to_string())))?; + Ok(String::from_utf8(bytes.as_ref().to_vec()).map_err(|e| Error::Custom(std::borrow::Cow::Owned(e.to_string())))?) + }); + Ok(Box::pin(stream)) +} \ No newline at end of file diff --git a/src/generators/mod.rs b/src/generators/mod.rs index 9e81e8d..cddc92b 100644 --- a/src/generators/mod.rs +++ b/src/generators/mod.rs @@ -1,4 +1,5 @@ pub mod openai; +pub mod debug; use tokio_stream::Stream; use thiserror::Error; From 0bdc8387a802f0544e237803a1048046ff495b08 Mon Sep 17 00:00:00 2001 From: Gly Date: Sat, 16 Sep 2023 16:38:38 +0200 Subject: [PATCH 04/37] fix executor newline --- src/execution/mod.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/execution/mod.rs b/src/execution/mod.rs index 13c8c0d..b4f44b1 100644 --- a/src/execution/mod.rs +++ b/src/execution/mod.rs @@ -36,6 +36,7 @@ impl Formatter for Executor { self.codes.last_mut().unwrap().code.push(c); } self.current_token.clear(); + self.is_newline = true; }, _ => { if self.is_code { @@ -54,7 +55,10 @@ impl Formatter for Executor { impl Executor { pub fn new() -> Self { - Self::default() + Self { + is_newline: true, + .. Default::default() + } } fn switch_code_block(&mut self) { self.is_code = !self.is_code; From 4c4bb48d887f4806d7dc15ebb1c5dcf845c6529c Mon Sep 17 00:00:00 2001 From: Gly Date: Sat, 16 Sep 2023 16:39:01 +0200 Subject: [PATCH 05/37] implement executor --- src/main.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/main.rs b/src/main.rs index 9627d7b..4d30127 100644 --- a/src/main.rs +++ b/src/main.rs @@ -68,19 +68,25 @@ async fn main() -> Result<(), String> { args::FormatterChoice::Markdown => Box::new(formatters::new_markdown_formatter()), args::FormatterChoice::Raw => Box::new(formatters::new_raw_formatter()), }; + let mut runner = execution::Executor::new(); - let engine = args.engine + let (engine, prompt) = args.engine .find(':') - .map(|i| &args.engine[..i]) - .unwrap_or(args.engine.as_str()); + .map(|i| (&args.engine[..i], Some(&args.engine[i+1..]))) + .unwrap_or((args.engine.as_str(), None)); + let mut stream = match engine { "openai" => generators::openai::run(creds.openai, config, args).await, + "debug" => generators::debug::run(config, args).await, _ => panic!("Unknown engine: {}", engine), }.map_err(|e| format!("Failed to request OpenAI API: {}", e))?; loop { match stream.next().await { - Some(Ok(token)) => raise_str!(formatter.push(&token), "Failed to parse markdown: {}"), + Some(Ok(token)) => { + raise_str!(formatter.push(&token), "Failed to parse markdown: {}"); + raise_str!(runner.push(&token), "Failed push text for execution: {}"); + }, Some(Err(e)) => Err(e.to_string())?, None => break, } From b8f07508d7b628a67e8db18a009ace3fe11daf50 Mon Sep 17 00:00:00 2001 From: Gly Date: Sun, 17 Sep 2023 14:37:56 +0200 Subject: [PATCH 06/37] rename execution => runner --- src/main.rs | 2 +- src/{execution => runner}/mod.rs | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) rename src/{execution => runner}/mod.rs (95%) diff --git a/src/main.rs b/src/main.rs index 4d30127..04072f0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,5 @@ pub mod arguments; -mod execution; +mod runner; mod generators; mod formatters; mod config; diff --git a/src/execution/mod.rs b/src/runner/mod.rs similarity index 95% rename from src/execution/mod.rs rename to src/runner/mod.rs index b4f44b1..5866723 100644 --- a/src/execution/mod.rs +++ b/src/runner/mod.rs @@ -1,8 +1,9 @@ +mod run; use anyhow::Result; use super::Formatter; #[derive(Default, Debug)] -struct CodeBlock { +pub struct CodeBlock { code: String, language: String, } @@ -13,14 +14,14 @@ impl CodeBlock { } } #[derive(Default, Debug)] -pub struct Executor{ +pub struct Runner{ is_code: bool, is_newline: bool, current_token: String, codes: Vec } -impl Formatter for Executor { +impl Formatter for Runner { fn push(&mut self, text: &str) -> Result<()> { for c in text.chars() { match c { @@ -53,7 +54,7 @@ impl Formatter for Executor { } } -impl Executor { +impl Runner { pub fn new() -> Self { Self { is_newline: true, From c5f09b6c49cc2f22661cec917b4c3d0ea741c74d Mon Sep 17 00:00:00 2001 From: Gly Date: Sun, 17 Sep 2023 14:38:12 +0200 Subject: [PATCH 07/37] working execution of code --- src/main.rs | 5 ++- src/runner/mod.rs | 4 ++ src/runner/run.rs | 101 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 src/runner/run.rs diff --git a/src/main.rs b/src/main.rs index 04072f0..602aec5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -68,7 +68,7 @@ async fn main() -> Result<(), String> { args::FormatterChoice::Markdown => Box::new(formatters::new_markdown_formatter()), args::FormatterChoice::Raw => Box::new(formatters::new_raw_formatter()), }; - let mut runner = execution::Executor::new(); + let mut runner = runner::Runner::new(); let (engine, prompt) = args.engine .find(':') @@ -92,5 +92,8 @@ async fn main() -> Result<(), String> { } } raise_str!(formatter.end_of_document(), "Failed to end markdown: {}"); + raise_str!(runner.end_of_document(), "Failed to run code: {}"); + + println!("{:#?}", runner); Ok(()) } \ No newline at end of file diff --git a/src/runner/mod.rs b/src/runner/mod.rs index 5866723..50c9848 100644 --- a/src/runner/mod.rs +++ b/src/runner/mod.rs @@ -52,6 +52,10 @@ impl Formatter for Runner { } Ok(()) } + fn end_of_document(&mut self) -> Result<()> { + run::run(&self.codes[0])?; + Ok(()) + } } impl Runner { diff --git a/src/runner/run.rs b/src/runner/run.rs new file mode 100644 index 0000000..e28c918 --- /dev/null +++ b/src/runner/run.rs @@ -0,0 +1,101 @@ + +use std::borrow::Cow; +use thiserror::Error; +use super::CodeBlock; + +#[derive(Error, Debug)] +pub enum RunError { + #[error("search error: {0}")] + Search(#[from] SearchError), + #[error("program not found")] + ProgramNotFount, + #[error("io error: {0}")] + Io(#[from] std::io::Error), +} +#[derive(Error, Debug)] +pub enum SearchError { + #[error("env var {0} not found: {1}")] + EnvVarNotFound(Cow<'static, str>, std::env::VarError), + #[error("io error: {0}")] + Io(std::io::Error), + #[error("bad utf-8 encoding in path")] + BadUTF8, + #[error("no corresponding program found")] + NoCorrespondingProgram, +} + +#[cfg(target_family = "unix")] +fn search_program(program: &str) -> Result, SearchError> { + let path = std::env::var("PATH").map_err(|e| SearchError::EnvVarNotFound("PATH".into(), e))?; + let found = path.split(':').find_map(|p| { + let Ok(mut directory) = std::fs::read_dir(p) else { return None; }; + let found_program = directory.find(|res_item| { + let Ok(item) = res_item else { return false }; + let Ok(file_type) = item.file_type() else { return false }; + (file_type.is_file() || file_type.is_symlink()) && item.file_name() == program + }); + found_program + .and_then(Result::ok) + .map(|v| v.path()) + }); + match found { + None => Ok(None), + Some(found) => { + let found = found.to_str().ok_or(SearchError::BadUTF8)?; + Ok(Some(found.to_string())) + }, + } +} +trait IntoBox { + fn into_box(self) -> Result>, SearchError>; +} +impl IntoBox for Result, SearchError> { + fn into_box(self) -> Result>, SearchError> { + self.map(|opt_program| opt_program.map(|program| -> Box { Box::new(program) })) + } + +} + +fn get_program(language: &str) -> Result>, SearchError> { + match language { + "bash" | "sh" | "shell" | "zsh" => ShellProgram::search().into_box(), + _ => Err(SearchError::NoCorrespondingProgram), + } +} + +trait Program { + fn run(&self, code_block: &CodeBlock) -> Result; +} +struct ShellProgram(String); + +impl Program for ShellProgram { + + fn run(&self, code_block: &CodeBlock) -> Result { + let mut process = std::process::Command::new(&self.0); + process + .arg("-c") + .arg(&code_block.code); + let child = process.spawn()?; + Ok(child.wait_with_output()?) + } +} +impl ShellProgram { + fn search() -> Result, SearchError> { + if let Some(found) = search_program("zsh")? { + return Ok(Some(Self(found))); + } + if let Some(found) = search_program("bash")? { + return Ok(Some(Self(found))); + } + if let Some(found) = search_program("sh")? { + return Ok(Some(Self(found))); + } + Ok(None) + } +} +pub fn run(code_block: &CodeBlock) -> Result { + let program = get_program(code_block.language.as_str())?; + let program = program.expect("Program not found"); + Ok(program.run(code_block)?) +} + From 6a63f66f0181fe2a0126107f0385cf7ba12f4afe Mon Sep 17 00:00:00 2001 From: Gly Date: Sun, 17 Sep 2023 14:50:21 +0200 Subject: [PATCH 08/37] better error display --- src/runner/run.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/runner/run.rs b/src/runner/run.rs index e28c918..4a652fc 100644 --- a/src/runner/run.rs +++ b/src/runner/run.rs @@ -5,10 +5,10 @@ use super::CodeBlock; #[derive(Error, Debug)] pub enum RunError { - #[error("search error: {0}")] + #[error("error while searching a program: {0}")] Search(#[from] SearchError), - #[error("program not found")] - ProgramNotFount, + #[error("program not found for `{0}`")] + ProgramNotFound(String), #[error("io error: {0}")] Io(#[from] std::io::Error), } @@ -20,8 +20,8 @@ pub enum SearchError { Io(std::io::Error), #[error("bad utf-8 encoding in path")] BadUTF8, - #[error("no corresponding program found")] - NoCorrespondingProgram, + #[error("no corresponding program found for `{0}`")] + NoCorrespondingProgram(String), } #[cfg(target_family = "unix")] @@ -59,7 +59,7 @@ impl IntoBox for Result, SearchError> { fn get_program(language: &str) -> Result>, SearchError> { match language { "bash" | "sh" | "shell" | "zsh" => ShellProgram::search().into_box(), - _ => Err(SearchError::NoCorrespondingProgram), + _ => Err(SearchError::NoCorrespondingProgram(language.to_string())), } } @@ -95,7 +95,7 @@ impl ShellProgram { } pub fn run(code_block: &CodeBlock) -> Result { let program = get_program(code_block.language.as_str())?; - let program = program.expect("Program not found"); + let Some(program) = program else { return Err(RunError::ProgramNotFound(code_block.language.clone())); }; Ok(program.run(code_block)?) } From 57e7b9378da6e6266e50a248d4608368b7613423 Mon Sep 17 00:00:00 2001 From: Gly Date: Sun, 17 Sep 2023 14:50:41 +0200 Subject: [PATCH 09/37] execute every code blocks --- src/runner/mod.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/runner/mod.rs b/src/runner/mod.rs index 50c9848..fb535dc 100644 --- a/src/runner/mod.rs +++ b/src/runner/mod.rs @@ -53,7 +53,9 @@ impl Formatter for Runner { Ok(()) } fn end_of_document(&mut self) -> Result<()> { - run::run(&self.codes[0])?; + for code_block in self.codes.iter() { + run::run(code_block)?; + } Ok(()) } } @@ -75,4 +77,5 @@ impl Runner { self.codes.last_mut().unwrap().code.pop(); } } + } \ No newline at end of file From 1e7204ce4fb77c898f9b2b71b9f88b62d9a3d1e9 Mon Sep 17 00:00:00 2001 From: Gly Date: Mon, 18 Sep 2023 00:34:56 +0200 Subject: [PATCH 10/37] Restructure runner --- src/runner/mod.rs | 4 +- src/runner/program/mod.rs | 93 +++++++++++++++++++++++++++++++++ src/runner/program/shell.rs | 25 +++++++++ src/runner/run.rs | 101 ------------------------------------ 4 files changed, 120 insertions(+), 103 deletions(-) create mode 100644 src/runner/program/mod.rs create mode 100644 src/runner/program/shell.rs diff --git a/src/runner/mod.rs b/src/runner/mod.rs index fb535dc..192f97a 100644 --- a/src/runner/mod.rs +++ b/src/runner/mod.rs @@ -1,4 +1,4 @@ -mod run; +mod program; use anyhow::Result; use super::Formatter; @@ -54,7 +54,7 @@ impl Formatter for Runner { } fn end_of_document(&mut self) -> Result<()> { for code_block in self.codes.iter() { - run::run(code_block)?; + program::run(code_block)?; } Ok(()) } diff --git a/src/runner/program/mod.rs b/src/runner/program/mod.rs new file mode 100644 index 0000000..0c9dd1d --- /dev/null +++ b/src/runner/program/mod.rs @@ -0,0 +1,93 @@ +mod shell; +use shell::*; + +use std::borrow::Cow; +use thiserror::Error; +use super::CodeBlock; + +trait Program { + fn run(&self, code_block: &CodeBlock) -> Result; +} + +#[derive(Error, Debug)] +pub enum RunError { + #[error("error while searching a program: {0}")] + Search(#[from] SearchError), + #[error("program not found for `{0}`")] + ProgramNotFound(String), + #[error("io error: {0}")] + Io(#[from] std::io::Error), +} +#[derive(Error, Debug)] +pub enum SearchError { + #[error("env var {0} not found: {1}")] + EnvVarNotFound(Cow<'static, str>, std::env::VarError), + #[error("io error: {0}")] + Io(std::io::Error), + #[error("bad utf-8 encoding in path")] + BadUTF8, + #[error("no corresponding program found for `{0}`")] + NoCorrespondingProgram(String), +} + +enum SearchStatus { + Found(Box), + NotFound, + Error(SearchError) +} + +impl From for SearchStatus { + fn from(e: SearchError) -> Self { + SearchStatus::Error(e) + } +} + +#[cfg(target_family = "unix")] +fn search_program(program: &str) -> Result, SearchError> { + let path = std::env::var("PATH").map_err(|e| SearchError::EnvVarNotFound("PATH".into(), e))?; + let found = path.split(':').find_map(|p| { + let Ok(mut directory) = std::fs::read_dir(p) else { return None; }; + let found_program = directory.find(|res_item| { + let Ok(item) = res_item else { return false }; + let Ok(file_type) = item.file_type() else { return false }; + (file_type.is_file() || file_type.is_symlink()) && item.file_name() == program + }); + found_program + .and_then(Result::ok) + .map(|v| v.path()) + }); + match found { + None => Ok(None), + Some(found) => { + let found = found.to_str().ok_or(SearchError::BadUTF8)?; + Ok(Some(found.to_string())) + }, + } +} +// trait IntoBox { +// fn into_box(self) -> Result>, SearchError>; +// } +// impl IntoBox for Result, SearchError> { +// fn into_box(self) -> Result>, SearchError> { +// self.map(|opt_program| opt_program.map(|program| -> Box { Box::new(program) })) +// } + +// } + +fn get_program(language: &str) -> SearchStatus { + match language { + "bash" | "sh" | "shell" | "zsh" => ShellProgram::search(&["zsh", "bash", "sh"]), + _ => SearchStatus::Error(SearchError::NoCorrespondingProgram(language.to_string())), + } +} + + +pub fn run(code_block: &CodeBlock) -> Result { + let program = match get_program(code_block.language.as_str()) { + SearchStatus::Found(found) => found, + SearchStatus::NotFound => return Err(RunError::ProgramNotFound(code_block.language.clone())), + SearchStatus::Error(e) => return Err(RunError::Search(e)), + }; + Ok(program.run(code_block)?) +} + diff --git a/src/runner/program/shell.rs b/src/runner/program/shell.rs new file mode 100644 index 0000000..b141dc4 --- /dev/null +++ b/src/runner/program/shell.rs @@ -0,0 +1,25 @@ +use super::*; +pub struct ShellProgram(String); + +impl Program for ShellProgram { + fn run(&self, code_block: &CodeBlock) -> Result { + let mut process = std::process::Command::new(&self.0); + process + .arg("-c") + .arg(&code_block.code); + let child = process.spawn()?; + Ok(child.wait_with_output()?) + } +} +impl ShellProgram { + pub(super) fn search(list_shells: &[&'static str]) -> SearchStatus { + for shell in list_shells { + match search_program(shell) { + Ok(Some(found)) => return SearchStatus::Found(Box::new(Self(found))), + // Err(e) => println!("Warning during search for {}: {}", shell, e), + _ => continue + } + } + SearchStatus::NotFound + } +} \ No newline at end of file diff --git a/src/runner/run.rs b/src/runner/run.rs index 4a652fc..e69de29 100644 --- a/src/runner/run.rs +++ b/src/runner/run.rs @@ -1,101 +0,0 @@ - -use std::borrow::Cow; -use thiserror::Error; -use super::CodeBlock; - -#[derive(Error, Debug)] -pub enum RunError { - #[error("error while searching a program: {0}")] - Search(#[from] SearchError), - #[error("program not found for `{0}`")] - ProgramNotFound(String), - #[error("io error: {0}")] - Io(#[from] std::io::Error), -} -#[derive(Error, Debug)] -pub enum SearchError { - #[error("env var {0} not found: {1}")] - EnvVarNotFound(Cow<'static, str>, std::env::VarError), - #[error("io error: {0}")] - Io(std::io::Error), - #[error("bad utf-8 encoding in path")] - BadUTF8, - #[error("no corresponding program found for `{0}`")] - NoCorrespondingProgram(String), -} - -#[cfg(target_family = "unix")] -fn search_program(program: &str) -> Result, SearchError> { - let path = std::env::var("PATH").map_err(|e| SearchError::EnvVarNotFound("PATH".into(), e))?; - let found = path.split(':').find_map(|p| { - let Ok(mut directory) = std::fs::read_dir(p) else { return None; }; - let found_program = directory.find(|res_item| { - let Ok(item) = res_item else { return false }; - let Ok(file_type) = item.file_type() else { return false }; - (file_type.is_file() || file_type.is_symlink()) && item.file_name() == program - }); - found_program - .and_then(Result::ok) - .map(|v| v.path()) - }); - match found { - None => Ok(None), - Some(found) => { - let found = found.to_str().ok_or(SearchError::BadUTF8)?; - Ok(Some(found.to_string())) - }, - } -} -trait IntoBox { - fn into_box(self) -> Result>, SearchError>; -} -impl IntoBox for Result, SearchError> { - fn into_box(self) -> Result>, SearchError> { - self.map(|opt_program| opt_program.map(|program| -> Box { Box::new(program) })) - } - -} - -fn get_program(language: &str) -> Result>, SearchError> { - match language { - "bash" | "sh" | "shell" | "zsh" => ShellProgram::search().into_box(), - _ => Err(SearchError::NoCorrespondingProgram(language.to_string())), - } -} - -trait Program { - fn run(&self, code_block: &CodeBlock) -> Result; -} -struct ShellProgram(String); - -impl Program for ShellProgram { - - fn run(&self, code_block: &CodeBlock) -> Result { - let mut process = std::process::Command::new(&self.0); - process - .arg("-c") - .arg(&code_block.code); - let child = process.spawn()?; - Ok(child.wait_with_output()?) - } -} -impl ShellProgram { - fn search() -> Result, SearchError> { - if let Some(found) = search_program("zsh")? { - return Ok(Some(Self(found))); - } - if let Some(found) = search_program("bash")? { - return Ok(Some(Self(found))); - } - if let Some(found) = search_program("sh")? { - return Ok(Some(Self(found))); - } - Ok(None) - } -} -pub fn run(code_block: &CodeBlock) -> Result { - let program = get_program(code_block.language.as_str())?; - let Some(program) = program else { return Err(RunError::ProgramNotFound(code_block.language.clone())); }; - Ok(program.run(code_block)?) -} - From a3c2162926b734c56b29b6187eddb92d03d8d8d9 Mon Sep 17 00:00:00 2001 From: Gly Date: Mon, 18 Sep 2023 00:36:55 +0200 Subject: [PATCH 11/37] Remove impl from for SearchStatus --- src/runner/program/mod.rs | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/runner/program/mod.rs b/src/runner/program/mod.rs index 0c9dd1d..56cd331 100644 --- a/src/runner/program/mod.rs +++ b/src/runner/program/mod.rs @@ -36,12 +36,6 @@ enum SearchStatus { Error(SearchError) } -impl From for SearchStatus { - fn from(e: SearchError) -> Self { - SearchStatus::Error(e) - } -} - #[cfg(target_family = "unix")] fn search_program(program: &str) -> Result, SearchError> { let path = std::env::var("PATH").map_err(|e| SearchError::EnvVarNotFound("PATH".into(), e))?; @@ -64,15 +58,6 @@ fn search_program(program: &str) -> Result, SearchError> { }, } } -// trait IntoBox { -// fn into_box(self) -> Result>, SearchError>; -// } -// impl IntoBox for Result, SearchError> { -// fn into_box(self) -> Result>, SearchError> { -// self.map(|opt_program| opt_program.map(|program| -> Box { Box::new(program) })) -// } - -// } fn get_program(language: &str) -> SearchStatus { match language { From 088f26e0f809083faa83a5f9c7bf2f4ddcf90533 Mon Sep 17 00:00:00 2001 From: Gly Date: Mon, 18 Sep 2023 00:48:47 +0200 Subject: [PATCH 12/37] add nushell runner --- src/runner/program/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/runner/program/mod.rs b/src/runner/program/mod.rs index 56cd331..e18ec8d 100644 --- a/src/runner/program/mod.rs +++ b/src/runner/program/mod.rs @@ -62,6 +62,7 @@ fn search_program(program: &str) -> Result, SearchError> { fn get_program(language: &str) -> SearchStatus { match language { "bash" | "sh" | "shell" | "zsh" => ShellProgram::search(&["zsh", "bash", "sh"]), + "nu" => ShellProgram::search(&["nu"]), _ => SearchStatus::Error(SearchError::NoCorrespondingProgram(language.to_string())), } } From 29560f4cb8602f09245d7b765aeefd60c58823c1 Mon Sep 17 00:00:00 2001 From: Gly Date: Mon, 18 Sep 2023 00:48:55 +0200 Subject: [PATCH 13/37] powershell runner --- src/runner/program/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/runner/program/mod.rs b/src/runner/program/mod.rs index e18ec8d..bd013d9 100644 --- a/src/runner/program/mod.rs +++ b/src/runner/program/mod.rs @@ -63,6 +63,7 @@ fn get_program(language: &str) -> SearchStatus { match language { "bash" | "sh" | "shell" | "zsh" => ShellProgram::search(&["zsh", "bash", "sh"]), "nu" => ShellProgram::search(&["nu"]), + "pwsh" | "powershell" => ShellProgram::search(&["pwsh", "powershell"]), _ => SearchStatus::Error(SearchError::NoCorrespondingProgram(language.to_string())), } } From 2eff8e5f9c94bb84732898fcf0d7c2ad2c231a12 Mon Sep 17 00:00:00 2001 From: Gly Date: Mon, 18 Sep 2023 01:36:45 +0200 Subject: [PATCH 14/37] add tempfile dependency --- Cargo.lock | 48 ++++++++++++++++++++++++++++-------------------- Cargo.toml | 1 + 2 files changed, 29 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5fa3d2f..e7061a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -48,6 +48,7 @@ dependencies = [ "serde_json", "serde_yaml", "smartstring", + "tempfile", "thiserror", "tokio", "tokio-stream", @@ -354,12 +355,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "1.9.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] +checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" [[package]] name = "flate2" @@ -615,15 +613,6 @@ dependencies = [ "hashbrown 0.14.0", ] -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", -] - [[package]] name = "io-lifetimes" version = "1.0.10" @@ -649,7 +638,7 @@ checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" dependencies = [ "hermit-abi 0.3.1", "io-lifetimes", - "rustix", + "rustix 0.37.11", "windows-sys 0.48.0", ] @@ -686,6 +675,12 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d59d8c75012853d2e872fb56bc8a2e53718e2cafe1a4c823143141c6d90c322f" +[[package]] +name = "linux-raw-sys" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128" + [[package]] name = "lock_api" version = "0.4.9" @@ -1005,7 +1000,20 @@ dependencies = [ "errno", "io-lifetimes", "libc", - "linux-raw-sys", + "linux-raw-sys 0.3.1", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustix" +version = "0.38.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7db8590df6dfcd144d22afd1b83b36c21a18d7cbc1dc4bb5295a8712e9eb662" +dependencies = [ + "bitflags 2.4.0", + "errno", + "libc", + "linux-raw-sys 0.4.7", "windows-sys 0.48.0", ] @@ -1221,15 +1229,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.5.0" +version = "3.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" +checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" dependencies = [ "cfg-if", "fastrand", "redox_syscall 0.3.5", - "rustix", - "windows-sys 0.45.0", + "rustix 0.38.13", + "windows-sys 0.48.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 0ab56cc..781dc87 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0.95" serde_yaml = "0.9" smartstring = { version = "1.0", features = ["serde"] } +tempfile = "3.8" thiserror = "1.0" tokio = { version = "1", features = ["full"] } tokio-stream = "0.1.12" From f8cdf029f1244b5f0762b971afaae2b019d266c5 Mon Sep 17 00:00:00 2001 From: Gly Date: Mon, 18 Sep 2023 01:37:05 +0200 Subject: [PATCH 15/37] Add rust runner --- src/runner/program/mod.rs | 3 +++ src/runner/program/rust.rs | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 src/runner/program/rust.rs diff --git a/src/runner/program/mod.rs b/src/runner/program/mod.rs index bd013d9..4503c4a 100644 --- a/src/runner/program/mod.rs +++ b/src/runner/program/mod.rs @@ -1,5 +1,7 @@ mod shell; use shell::*; +mod rust; +use rust::*; use std::borrow::Cow; use thiserror::Error; @@ -64,6 +66,7 @@ fn get_program(language: &str) -> SearchStatus { "bash" | "sh" | "shell" | "zsh" => ShellProgram::search(&["zsh", "bash", "sh"]), "nu" => ShellProgram::search(&["nu"]), "pwsh" | "powershell" => ShellProgram::search(&["pwsh", "powershell"]), + "rust" | "rs" => RustProgram::search(), _ => SearchStatus::Error(SearchError::NoCorrespondingProgram(language.to_string())), } } diff --git a/src/runner/program/rust.rs b/src/runner/program/rust.rs new file mode 100644 index 0000000..ccbc759 --- /dev/null +++ b/src/runner/program/rust.rs @@ -0,0 +1,37 @@ +use super::*; +pub struct RustProgram(String); + +impl Program for RustProgram { + fn run(&self, code_block: &CodeBlock) -> Result { + use std::io::Write; + let tmp = tempfile::NamedTempFile::new()?.into_temp_path(); + let tmp_path = tmp.to_str().expect("Failed to convert temp path to string"); + + let mut rustc_process = std::process::Command::new(&self.0); + rustc_process + .args(&[ + "-o", + tmp_path, + "-", + ]) + .stdin(std::process::Stdio::piped()); + let mut child = rustc_process.spawn()?; + child.stdin.take().expect("Failed to get stdin of rustc").write_all(code_block.code.as_bytes())?; + child.wait_with_output()?; + + let output = std::process::Command::new(tmp_path) + .spawn()? + .wait_with_output()?; + + Ok(output) + } +} +impl RustProgram { + pub(super) fn search() -> SearchStatus { + match search_program("rustc") { + Ok(Some(found)) => return SearchStatus::Found(Box::new(Self(found))), + Err(e) => SearchStatus::Error(e), + _ => SearchStatus::NotFound + } + } +} \ No newline at end of file From 3bf8cf4e72a69d514a22f468c91254712318001b Mon Sep 17 00:00:00 2001 From: Gly Date: Mon, 18 Sep 2023 12:54:30 +0200 Subject: [PATCH 16/37] Remove unused SearchError variant --- src/runner/program/mod.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/runner/program/mod.rs b/src/runner/program/mod.rs index 4503c4a..2765437 100644 --- a/src/runner/program/mod.rs +++ b/src/runner/program/mod.rs @@ -24,8 +24,6 @@ pub enum RunError { pub enum SearchError { #[error("env var {0} not found: {1}")] EnvVarNotFound(Cow<'static, str>, std::env::VarError), - #[error("io error: {0}")] - Io(std::io::Error), #[error("bad utf-8 encoding in path")] BadUTF8, #[error("no corresponding program found for `{0}`")] From 19c6e98eaf6ee2cff2c9fba92bb226e3a83ad2c0 Mon Sep 17 00:00:00 2001 From: Gly Date: Mon, 18 Sep 2023 12:54:48 +0200 Subject: [PATCH 17/37] search_program windows compatible --- src/runner/program/mod.rs | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/runner/program/mod.rs b/src/runner/program/mod.rs index 2765437..bd624e1 100644 --- a/src/runner/program/mod.rs +++ b/src/runner/program/mod.rs @@ -36,15 +36,33 @@ enum SearchStatus { Error(SearchError) } -#[cfg(target_family = "unix")] fn search_program(program: &str) -> Result, SearchError> { + #[cfg(target_family = "unix")] + const SEPARATOR: char = ':'; + #[cfg(target_family = "windows")] + const SEPARATOR: char = ';'; + let path = std::env::var("PATH").map_err(|e| SearchError::EnvVarNotFound("PATH".into(), e))?; - let found = path.split(':').find_map(|p| { + let found = path.split(SEPARATOR).find_map(|p| { let Ok(mut directory) = std::fs::read_dir(p) else { return None; }; let found_program = directory.find(|res_item| { let Ok(item) = res_item else { return false }; let Ok(file_type) = item.file_type() else { return false }; - (file_type.is_file() || file_type.is_symlink()) && item.file_name() == program + if !(file_type.is_file() || file_type.is_symlink()) { + return false; + } + #[cfg(target_family = "unix")] + return item.file_name() == program; + #[cfg(target_family = "windows")] + { + use std::ffi::{OsString, OsStr}; + let os_program = OsString::from(program.to_lowercase()); + let os_extension = OsString::from("exe"); + let path = item.path(); + let Some(filestem) = path.file_stem().map(OsStr::to_ascii_lowercase) else { return false }; + let Some(extension) = path.file_stem().map(OsStr::to_ascii_lowercase) else { return false }; + return filestem == os_program && extension == os_extension; + } }); found_program .and_then(Result::ok) From d46f7740156e78a95963c4e18c2bd2909b323a16 Mon Sep 17 00:00:00 2001 From: Gly Date: Mon, 18 Sep 2023 13:26:16 +0200 Subject: [PATCH 18/37] remove debug output --- src/main.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 602aec5..f62fb39 100644 --- a/src/main.rs +++ b/src/main.rs @@ -93,7 +93,5 @@ async fn main() -> Result<(), String> { } raise_str!(formatter.end_of_document(), "Failed to end markdown: {}"); raise_str!(runner.end_of_document(), "Failed to run code: {}"); - - println!("{:#?}", runner); Ok(()) } \ No newline at end of file From 8516ff0b3a485bf9df25749ba8efa312c6076bfc Mon Sep 17 00:00:00 2001 From: Gly Date: Mon, 18 Sep 2023 13:26:30 +0200 Subject: [PATCH 19/37] Add interactive run --- src/runner/mod.rs | 50 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 3 deletions(-) diff --git a/src/runner/mod.rs b/src/runner/mod.rs index 192f97a..e7855f9 100644 --- a/src/runner/mod.rs +++ b/src/runner/mod.rs @@ -1,5 +1,6 @@ mod program; use anyhow::Result; +use crossterm::ExecutableCommand; use super::Formatter; #[derive(Default, Debug)] @@ -15,6 +16,7 @@ impl CodeBlock { } #[derive(Default, Debug)] pub struct Runner{ + interactive: bool, is_code: bool, is_newline: bool, current_token: String, @@ -53,9 +55,20 @@ impl Formatter for Runner { Ok(()) } fn end_of_document(&mut self) -> Result<()> { - for code_block in self.codes.iter() { - program::run(code_block)?; + use std::io::IsTerminal; + if !std::io::stdout().is_terminal() { + // No code execution allowed if not in a terminal + return Ok(()) } + match self.interactive { + false => { + for code_block in self.codes.iter() { + program::run(code_block)?; + } + }, + true => self.interactive_interface()?, + } + Ok(()) } } @@ -64,6 +77,7 @@ impl Runner { pub fn new() -> Self { Self { is_newline: true, + interactive: true, .. Default::default() } } @@ -77,5 +91,35 @@ impl Runner { self.codes.last_mut().unwrap().code.pop(); } } - + fn interactive_interface(&mut self) -> Result<()> { + use std::io::Write; + if self.codes.is_empty() { + return Ok(()); + } + loop { + print!("Execute code ?\n1-{}: index of the code block\nq: quit\n> ", self.codes.len()); + std::io::stdout().flush()?; + let mut stdin_buf = String::new(); + std::io::stdin().read_line(&mut stdin_buf)?; + let stdin_buf = stdin_buf.trim(); + if stdin_buf == "q" { + return Ok(()); + } + let index = match stdin_buf.parse::() { + Ok(i) => i, + Err(_) => { + println!("Not a number"); + continue; + } + }; + if !(1..=self.codes.len() as isize).contains(&index) { + println!("Index out of range"); + continue; + } + print!("\n"); + program::run(&self.codes[index as usize-1])?; + print!("\n"); + } + Ok(()) + } } \ No newline at end of file From 82c9e3e9052808e0ee4a8f57945aa8b119b5956d Mon Sep 17 00:00:00 2001 From: Gly Date: Mon, 18 Sep 2023 15:13:38 +0200 Subject: [PATCH 20/37] Add argument do run code --- src/arguments.rs | 75 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 52 insertions(+), 23 deletions(-) diff --git a/src/arguments.rs b/src/arguments.rs index 4a12983..9424747 100644 --- a/src/arguments.rs +++ b/src/arguments.rs @@ -2,6 +2,35 @@ use std::fmt::Display; use clap::{Parser, ValueEnum}; +/// Program to communicate with large language models and AI API +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +pub struct Args { + /// Configuration file + #[arg(long, default_value_t = String::from("~/.config/aio/config.yaml"))] + pub config_path: String, + /// Credentials file + #[arg(long, default_value_t = String::from("~/.config/aio/creds.yaml"))] + pub creds_path: String, + /// Engine name + /// + /// The name can be followed by custom prompt name from the configuration file + /// (ex: openai:command) + #[arg(long, short)] + pub engine: String, + /// Formatter + /// + /// Possible values: markdown, raw + #[arg(long, short, default_value_t = Default::default())] + pub formatter: FormatterChoice, + /// Run code block if the language is supported + #[arg(long, short, default_value_t = Default::default())] + pub run: RunChoice, + /// Force to run code + /// User text prompt + pub input: Option, +} + #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] pub enum FormatterChoice { Markdown, @@ -28,31 +57,31 @@ impl Display for FormatterChoice { } } -/// Program to communicate with large language models and AI API -#[derive(Parser, Debug)] -#[command(author, version, about, long_about = None)] -pub struct Args { - /// Configuration file - #[arg(long, default_value_t = String::from("~/.config/aio/config.yaml"))] - pub config_path: String, - /// Credentials file - #[arg(long, default_value_t = String::from("~/.config/aio/creds.yaml"))] - pub creds_path: String, - /// Engine name - /// - /// The name can be followed by custom prompt name from the configuration file - /// (ex: openai:command) - #[arg(long, short)] - pub engine: String, - /// Formatter - /// - /// Possible values: markdown, raw - #[arg(long, short, default_value_t = Default::default())] - pub formatter: FormatterChoice, - /// User text prompt - pub input: Option, +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] +pub enum RunChoice { + /// Doesn't run anything + No, + /// Ask to run code + Ask, + /// Run code without asking + Force +} + +impl Default for RunChoice { + fn default() -> Self { + RunChoice::No + } } +impl Display for RunChoice { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + RunChoice::No => write!(f, "no"), + RunChoice::Ask => write!(f, "ask"), + RunChoice::Force => write!(f, "force"), + } + } +} pub struct ProcessedArgs { pub config_path: String, pub creds_path: String, From 88bc037eaa6c370b1cb02368df936f8ed114df4f Mon Sep 17 00:00:00 2001 From: Gly Date: Mon, 18 Sep 2023 15:36:55 +0200 Subject: [PATCH 21/37] Implement run choice --- src/arguments.rs | 2 ++ src/main.rs | 2 +- src/runner/mod.rs | 14 ++++++++------ 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/arguments.rs b/src/arguments.rs index 9424747..539212c 100644 --- a/src/arguments.rs +++ b/src/arguments.rs @@ -87,6 +87,7 @@ pub struct ProcessedArgs { pub creds_path: String, pub engine: String, pub formatter: FormatterChoice, + pub run: RunChoice, pub input: String, } @@ -97,6 +98,7 @@ impl From for ProcessedArgs { creds_path: args.creds_path, engine: args.engine, formatter: args.formatter, + run: args.run, input: args.input.unwrap_or_default(), } } diff --git a/src/main.rs b/src/main.rs index f62fb39..af31cfb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -68,7 +68,7 @@ async fn main() -> Result<(), String> { args::FormatterChoice::Markdown => Box::new(formatters::new_markdown_formatter()), args::FormatterChoice::Raw => Box::new(formatters::new_raw_formatter()), }; - let mut runner = runner::Runner::new(); + let mut runner = runner::Runner::new(args.run); let (engine, prompt) = args.engine .find(':') diff --git a/src/runner/mod.rs b/src/runner/mod.rs index e7855f9..b959ad1 100644 --- a/src/runner/mod.rs +++ b/src/runner/mod.rs @@ -1,4 +1,5 @@ mod program; +use crate::args; use anyhow::Result; use crossterm::ExecutableCommand; use super::Formatter; @@ -16,7 +17,7 @@ impl CodeBlock { } #[derive(Default, Debug)] pub struct Runner{ - interactive: bool, + interactive_mode: args::RunChoice, is_code: bool, is_newline: bool, current_token: String, @@ -60,13 +61,14 @@ impl Formatter for Runner { // No code execution allowed if not in a terminal return Ok(()) } - match self.interactive { - false => { + match self.interactive_mode { + args::RunChoice::No => return Ok(()), + args::RunChoice::Ask => self.interactive_interface()?, + args::RunChoice::Force => { for code_block in self.codes.iter() { program::run(code_block)?; } }, - true => self.interactive_interface()?, } Ok(()) @@ -74,10 +76,10 @@ impl Formatter for Runner { } impl Runner { - pub fn new() -> Self { + pub fn new(run_choice: args::RunChoice) -> Self { Self { is_newline: true, - interactive: true, + interactive_mode: run_choice, .. Default::default() } } From 652d8c8bddf7c4e6afdc9dcfc63d2d0f458010e2 Mon Sep 17 00:00:00 2001 From: Gly Date: Mon, 18 Sep 2023 15:43:14 +0200 Subject: [PATCH 22/37] smol improv --- src/main.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index af31cfb..4c388f4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -70,14 +70,14 @@ async fn main() -> Result<(), String> { }; let mut runner = runner::Runner::new(args.run); - let (engine, prompt) = args.engine + let (engine, _prompt) = args.engine .find(':') .map(|i| (&args.engine[..i], Some(&args.engine[i+1..]))) .unwrap_or((args.engine.as_str(), None)); let mut stream = match engine { "openai" => generators::openai::run(creds.openai, config, args).await, - "debug" => generators::debug::run(config, args).await, + "from-file" => generators::debug::run(config, args).await, _ => panic!("Unknown engine: {}", engine), }.map_err(|e| format!("Failed to request OpenAI API: {}", e))?; @@ -85,7 +85,7 @@ async fn main() -> Result<(), String> { match stream.next().await { Some(Ok(token)) => { raise_str!(formatter.push(&token), "Failed to parse markdown: {}"); - raise_str!(runner.push(&token), "Failed push text for execution: {}"); + raise_str!(runner.push(&token), "Failed push text in the runner system: {}"); }, Some(Err(e)) => Err(e.to_string())?, None => break, From 9bd550c219ed4423545a115d91146c506538e0f7 Mon Sep 17 00:00:00 2001 From: Gly Date: Mon, 18 Sep 2023 16:02:52 +0200 Subject: [PATCH 23/37] Add python runner --- src/runner/program/mod.rs | 3 +++ src/runner/program/python.rs | 27 +++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 src/runner/program/python.rs diff --git a/src/runner/program/mod.rs b/src/runner/program/mod.rs index bd624e1..bdcb7f0 100644 --- a/src/runner/program/mod.rs +++ b/src/runner/program/mod.rs @@ -2,6 +2,8 @@ mod shell; use shell::*; mod rust; use rust::*; +mod python; +use python::*; use std::borrow::Cow; use thiserror::Error; @@ -83,6 +85,7 @@ fn get_program(language: &str) -> SearchStatus { "nu" => ShellProgram::search(&["nu"]), "pwsh" | "powershell" => ShellProgram::search(&["pwsh", "powershell"]), "rust" | "rs" => RustProgram::search(), + "py" | "python" => PythonProgram::search(), _ => SearchStatus::Error(SearchError::NoCorrespondingProgram(language.to_string())), } } diff --git a/src/runner/program/python.rs b/src/runner/program/python.rs new file mode 100644 index 0000000..4f3088d --- /dev/null +++ b/src/runner/program/python.rs @@ -0,0 +1,27 @@ +use super::*; +pub struct PythonProgram(String); + +impl Program for PythonProgram { + fn run(&self, code_block: &CodeBlock) -> Result { + use std::io::Write; + let mut process = std::process::Command::new(&self.0); + process + .arg("-") + .stdin(std::process::Stdio::piped()); + let mut child = process.spawn()?; + child.stdin.take().expect("Failed to get stdin of python").write_all(code_block.code.as_bytes())?; + Ok(child.wait_with_output()?) + } +} +impl PythonProgram { + pub(super) fn search() -> SearchStatus { + for shell in ["python3", "python"] { + match search_program(shell) { + Ok(Some(found)) => return SearchStatus::Found(Box::new(Self(found))), + // Err(e) => println!("Warning during search for {}: {}", shell, e), + _ => continue + } + } + SearchStatus::NotFound + } +} \ No newline at end of file From 0e502b741807b3466760eea87d1ae8e5010acdc4 Mon Sep 17 00:00:00 2001 From: Gly Date: Mon, 18 Sep 2023 16:16:33 +0200 Subject: [PATCH 24/37] add home_dir fn --- src/main.rs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/main.rs b/src/main.rs index 4c388f4..aaf6205 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,13 +22,21 @@ macro_rules! raise_str { }; } +fn home_dir() -> &'static str { + #[cfg(unix)] + lazy_static::lazy_static! { + static ref HOME: String = std::env::var("HOME").expect("Failed to resolve home path"); + } + #[cfg(windows)] + lazy_static::lazy_static! { + static ref HOME: String = std::env::var("USERPROFILE").expect("Failed to resolve user profile path"); + } + &*HOME +} + fn resolve_path(path: &str) -> Cow { if path.starts_with("~/") { - #[cfg(unix)] - let home = std::env::var("HOME").expect("Failed to resolve home path"); - #[cfg(windows)] - let home = std::env::var("USERPROFILE").expect("Failed to resolve user profile path"); - Cow::Owned(format!("{}{}{}", home, std::path::MAIN_SEPARATOR, &path[2..])) + Cow::Owned(format!("{}{}{}", home_dir(), std::path::MAIN_SEPARATOR, &path[2..])) } else { Cow::Borrowed(path) } From 4798d632e48428a3d815f3160f902dd2f2696ec6 Mon Sep 17 00:00:00 2001 From: Gly Date: Mon, 18 Sep 2023 16:37:36 +0200 Subject: [PATCH 25/37] Add cache for search programs --- src/runner/program/cache.rs | 47 +++++++++++++++++++++++++++++++++++++ src/runner/program/mod.rs | 12 ++++++++-- 2 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 src/runner/program/cache.rs diff --git a/src/runner/program/cache.rs b/src/runner/program/cache.rs new file mode 100644 index 0000000..4f5df2b --- /dev/null +++ b/src/runner/program/cache.rs @@ -0,0 +1,47 @@ +use thiserror::Error; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug, Default)] +pub struct Cache { + programs: std::collections::HashMap, +} + +#[derive(Error, Debug)] +pub enum CacheError { + #[error("error while accessing to the cache file: {0}")] + Io(#[from] std::io::Error), + #[error("error while parsing or encoding the cache file: {0}")] + Parse(#[from] serde_yaml::Error), + #[error("Unable to access to cache directory")] + NoParent, +} + +impl Cache { + fn get() -> Result { + let file_path = Self::cache_path(); + if !file_path.exists() { + return Ok(Default::default()); + } + let cache_file = std::fs::File::open(file_path)?; + Ok(serde_yaml::from_reader(cache_file)?) + } + fn cache_path() -> std::path::PathBuf { + std::path::Path::new(crate::home_dir()).join(".cache").join("aio.yaml") + } + pub fn get_program(program: &str) -> Result, CacheError> { + let cache = Self::get()?; + Ok(cache.programs.get(program).cloned()) + } + pub fn set_program(program: String, path: String) -> Result<(), CacheError> { + let file_path = Self::cache_path(); + let Some(parent) = file_path.parent() else { return Err(CacheError::NoParent); }; + if !parent.exists() { + std::fs::create_dir_all(parent)?; + } + let mut cache = Self::get()?; + cache.programs.insert(program.to_string(), path.to_string()); + let mut cache_file = std::fs::File::create(file_path)?; + serde_yaml::to_writer(&mut cache_file, &cache)?; + Ok(()) + } +} \ No newline at end of file diff --git a/src/runner/program/mod.rs b/src/runner/program/mod.rs index bdcb7f0..3c3ad59 100644 --- a/src/runner/program/mod.rs +++ b/src/runner/program/mod.rs @@ -1,3 +1,5 @@ +mod cache; + mod shell; use shell::*; mod rust; @@ -30,6 +32,8 @@ pub enum SearchError { BadUTF8, #[error("no corresponding program found for `{0}`")] NoCorrespondingProgram(String), + #[error("cache error: {0}")] + Cache(#[from] cache::CacheError) } enum SearchStatus { @@ -39,6 +43,9 @@ enum SearchStatus { } fn search_program(program: &str) -> Result, SearchError> { + if let Some(found) = cache::Cache::get_program(program)? { + return Ok(Some(found)); + } #[cfg(target_family = "unix")] const SEPARATOR: char = ':'; #[cfg(target_family = "windows")] @@ -73,8 +80,9 @@ fn search_program(program: &str) -> Result, SearchError> { match found { None => Ok(None), Some(found) => { - let found = found.to_str().ok_or(SearchError::BadUTF8)?; - Ok(Some(found.to_string())) + let found = found.to_str().ok_or(SearchError::BadUTF8)?.to_string(); + cache::Cache::set_program(program.into(), found.clone())?; + Ok(Some(found)) }, } } From cbf1a15330d5940c330451bbef24e16fcd6a5124 Mon Sep 17 00:00:00 2001 From: Gly Date: Mon, 18 Sep 2023 16:48:37 +0200 Subject: [PATCH 26/37] check for program cache existance --- src/runner/program/cache.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/runner/program/cache.rs b/src/runner/program/cache.rs index 4f5df2b..2159ee3 100644 --- a/src/runner/program/cache.rs +++ b/src/runner/program/cache.rs @@ -30,7 +30,11 @@ impl Cache { } pub fn get_program(program: &str) -> Result, CacheError> { let cache = Self::get()?; - Ok(cache.programs.get(program).cloned()) + let Some(path) = cache.programs.get(program) else { return Ok(None); }; + if !std::path::Path::new(path).exists() { + return Ok(None); + } + Ok(Some(path.clone())) } pub fn set_program(program: String, path: String) -> Result<(), CacheError> { let file_path = Self::cache_path(); From faca98144745d0590a5dcc95915076d26f80f0ad Mon Sep 17 00:00:00 2001 From: Gly Date: Mon, 18 Sep 2023 18:12:52 +0200 Subject: [PATCH 27/37] better interactive display --- src/runner/mod.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/runner/mod.rs b/src/runner/mod.rs index b959ad1..8d346bb 100644 --- a/src/runner/mod.rs +++ b/src/runner/mod.rs @@ -1,7 +1,6 @@ mod program; use crate::args; use anyhow::Result; -use crossterm::ExecutableCommand; use super::Formatter; #[derive(Default, Debug)] @@ -99,7 +98,14 @@ impl Runner { return Ok(()); } loop { - print!("Execute code ?\n1-{}: index of the code block\nq: quit\n> ", self.codes.len()); + println!("Execute code ?"); + if self.codes.len() == 1 { + println!("1: index of the code block"); + } else { + println!("1-{}: index of the code block", self.codes.len()); + } + println!("q: quit"); + print!("> "); std::io::stdout().flush()?; let mut stdin_buf = String::new(); std::io::stdin().read_line(&mut stdin_buf)?; From 37f4a2e9946f22ce14c968ae6c024d1ffb3ffb9e Mon Sep 17 00:00:00 2001 From: Gly Date: Tue, 19 Sep 2023 01:09:05 +0200 Subject: [PATCH 28/37] code opti based on review --- src/runner/program/mod.rs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/runner/program/mod.rs b/src/runner/program/mod.rs index 3c3ad59..eef571a 100644 --- a/src/runner/program/mod.rs +++ b/src/runner/program/mod.rs @@ -53,7 +53,7 @@ fn search_program(program: &str) -> Result, SearchError> { let path = std::env::var("PATH").map_err(|e| SearchError::EnvVarNotFound("PATH".into(), e))?; let found = path.split(SEPARATOR).find_map(|p| { - let Ok(mut directory) = std::fs::read_dir(p) else { return None; }; + let mut directory = std::fs::read_dir(p).ok()?; let found_program = directory.find(|res_item| { let Ok(item) = res_item else { return false }; let Ok(file_type) = item.file_type() else { return false }; @@ -77,14 +77,10 @@ fn search_program(program: &str) -> Result, SearchError> { .and_then(Result::ok) .map(|v| v.path()) }); - match found { - None => Ok(None), - Some(found) => { - let found = found.to_str().ok_or(SearchError::BadUTF8)?.to_string(); - cache::Cache::set_program(program.into(), found.clone())?; - Ok(Some(found)) - }, - } + let Some(found) = found else { return Ok(None); }; + let found = found.to_str().ok_or(SearchError::BadUTF8)?.to_string(); + cache::Cache::set_program(program.into(), found.clone())?; + Ok(Some(found)) } fn get_program(language: &str) -> SearchStatus { From 08d2a4c74f79f436b33df340be692747279d75d4 Mon Sep 17 00:00:00 2001 From: Gly Date: Tue, 19 Sep 2023 01:09:30 +0200 Subject: [PATCH 29/37] add shell variant based on code lang --- src/runner/program/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/runner/program/mod.rs b/src/runner/program/mod.rs index eef571a..ec7414f 100644 --- a/src/runner/program/mod.rs +++ b/src/runner/program/mod.rs @@ -85,7 +85,9 @@ fn search_program(program: &str) -> Result, SearchError> { fn get_program(language: &str) -> SearchStatus { match language { - "bash" | "sh" | "shell" | "zsh" => ShellProgram::search(&["zsh", "bash", "sh"]), + "sh" | "shell" => ShellProgram::search(&["zsh", "bash", "sh"]), + "bash" => ShellProgram::search(&["zsh", "bash"]), + "zsh" => ShellProgram::search(&["zsh"]), "nu" => ShellProgram::search(&["nu"]), "pwsh" | "powershell" => ShellProgram::search(&["pwsh", "powershell"]), "rust" | "rs" => RustProgram::search(), From e3cbda9f2b153fcf586889f17544a76a1a8bba21 Mon Sep 17 00:00:00 2001 From: Gly Date: Tue, 19 Sep 2023 01:15:56 +0200 Subject: [PATCH 30/37] RunChoice derive Default --- src/arguments.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/arguments.rs b/src/arguments.rs index 539212c..4e80cda 100644 --- a/src/arguments.rs +++ b/src/arguments.rs @@ -57,9 +57,10 @@ impl Display for FormatterChoice { } } -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] +#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] pub enum RunChoice { /// Doesn't run anything + #[default] No, /// Ask to run code Ask, @@ -67,12 +68,6 @@ pub enum RunChoice { Force } -impl Default for RunChoice { - fn default() -> Self { - RunChoice::No - } -} - impl Display for RunChoice { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { From 5239d73d804a11db96b0a36f496ad8a06ffe6420 Mon Sep 17 00:00:00 2001 From: Gly Date: Tue, 19 Sep 2023 13:47:45 +0200 Subject: [PATCH 31/37] check python for version 3 or more --- src/runner/program/python.rs | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/runner/program/python.rs b/src/runner/program/python.rs index 4f3088d..188b048 100644 --- a/src/runner/program/python.rs +++ b/src/runner/program/python.rs @@ -15,13 +15,27 @@ impl Program for PythonProgram { } impl PythonProgram { pub(super) fn search() -> SearchStatus { - for shell in ["python3", "python"] { - match search_program(shell) { - Ok(Some(found)) => return SearchStatus::Found(Box::new(Self(found))), - // Err(e) => println!("Warning during search for {}: {}", shell, e), - _ => continue + if let Ok(Some(found)) = search_program("python3") { + return SearchStatus::Found(Box::new(Self(found))); + } + if let Ok(Some(found)) = search_program("python") { + if let Ok(true) = Self::check_python_version(&found) { + return SearchStatus::Found(Box::new(Self(found))); } } SearchStatus::NotFound } + fn check_python_version(path: &str) -> Result { + let mut command = std::process::Command::new(path); + command.arg("-V"); + let output = command.output()?; + if !output.status.success() { + return Ok(false); + } + let str_output = String::from_utf8_lossy(&output.stdout); + + let Some(capture_version) = regex::Regex::new(r"Python (\d+)\.\d+\.\d+").unwrap().captures(&str_output) else { return Ok(false); }; + let Ok(major) = capture_version[1].parse::() else { return Ok(false); }; + Ok(major >= 3) + } } \ No newline at end of file From 04767f67bd4ef66b86c9c6c357ebcbcd06374f9f Mon Sep 17 00:00:00 2001 From: Gly Date: Tue, 19 Sep 2023 13:48:50 +0200 Subject: [PATCH 32/37] fix warnings --- src/generators/debug.rs | 4 ++-- src/runner/mod.rs | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/generators/debug.rs b/src/generators/debug.rs index 59e4c80..005ca48 100644 --- a/src/generators/debug.rs +++ b/src/generators/debug.rs @@ -1,12 +1,12 @@ use crate::args; use super::{ResultRun, ResultStream, Error}; -pub async fn run(config: crate::config::Config, args: args::ProcessedArgs) -> ResultRun { +pub async fn run(_: crate::config::Config, args: args::ProcessedArgs) -> ResultRun { use tokio_stream::StreamExt; let input = args.input; let file = tokio::fs::File::open(&input).await.map_err(|e| Error::Custom(std::borrow::Cow::Owned(e.to_string())))?; - let stream = tokio_util::io::ReaderStream::new(file).map(|mut r| -> ResultStream { + let stream = tokio_util::io::ReaderStream::new(file).map(|r| -> ResultStream { let bytes = r.map_err(|e| Error::Custom(std::borrow::Cow::Owned(e.to_string())))?; Ok(String::from_utf8(bytes.as_ref().to_vec()).map_err(|e| Error::Custom(std::borrow::Cow::Owned(e.to_string())))?) }); diff --git a/src/runner/mod.rs b/src/runner/mod.rs index 8d346bb..fc95e8a 100644 --- a/src/runner/mod.rs +++ b/src/runner/mod.rs @@ -128,6 +128,5 @@ impl Runner { program::run(&self.codes[index as usize-1])?; print!("\n"); } - Ok(()) } } \ No newline at end of file From 766ab6a8d7134bade7960f7276522ff155b51d23 Mon Sep 17 00:00:00 2001 From: Gly Date: Tue, 19 Sep 2023 14:04:16 +0200 Subject: [PATCH 33/37] better clap arguments code --- src/arguments.rs | 42 ++++++++---------------------------------- 1 file changed, 8 insertions(+), 34 deletions(-) diff --git a/src/arguments.rs b/src/arguments.rs index 4e80cda..0fb1b17 100644 --- a/src/arguments.rs +++ b/src/arguments.rs @@ -1,5 +1,3 @@ -use std::fmt::Display; - use clap::{Parser, ValueEnum}; /// Program to communicate with large language models and AI API @@ -21,43 +19,28 @@ pub struct Args { /// Formatter /// /// Possible values: markdown, raw - #[arg(long, short, default_value_t = Default::default())] + #[arg(long, short, value_enum, default_value_t = Default::default())] pub formatter: FormatterChoice, /// Run code block if the language is supported - #[arg(long, short, default_value_t = Default::default())] + #[arg(long, short, value_enum, default_value_t = Default::default())] pub run: RunChoice, /// Force to run code /// User text prompt pub input: Option, } -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] +#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] +#[value(rename_all = "lowercase")] pub enum FormatterChoice { + /// Markdown display + #[default] Markdown, + /// Raw display Raw, } -impl Default for FormatterChoice { - fn default() -> Self { - use std::io::IsTerminal; - if std::io::stdout().is_terminal() { - FormatterChoice::Markdown - } else { - FormatterChoice::Raw - } - } -} - -impl Display for FormatterChoice { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - FormatterChoice::Markdown => write!(f, "markdown"), - FormatterChoice::Raw => write!(f, "raw"), - } - } -} - #[derive(Default, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] +#[value(rename_all = "lowercase")] pub enum RunChoice { /// Doesn't run anything #[default] @@ -68,15 +51,6 @@ pub enum RunChoice { Force } -impl Display for RunChoice { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - RunChoice::No => write!(f, "no"), - RunChoice::Ask => write!(f, "ask"), - RunChoice::Force => write!(f, "force"), - } - } -} pub struct ProcessedArgs { pub config_path: String, pub creds_path: String, From c158390480db6fc06463db00a6bdc1c47b9d83c9 Mon Sep 17 00:00:00 2001 From: Gly Date: Tue, 19 Sep 2023 14:25:59 +0200 Subject: [PATCH 34/37] replace lazy_static with once_cell --- Cargo.lock | 6 +++--- Cargo.toml | 2 +- src/config.rs | 5 ++--- .../markdown/renderer/terminal/utils.rs | 9 ++++----- src/main.rs | 19 ++++++++++++------- 5 files changed, 22 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e7061a8..9167761 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -38,8 +38,8 @@ dependencies = [ "bytes", "clap", "crossterm", - "lazy_static", "num-traits", + "once_cell", "openssl", "pin-project", "regex", @@ -772,9 +772,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.17.1" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "openssl" diff --git a/Cargo.toml b/Cargo.toml index 781dc87..83041b5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,8 +23,8 @@ async-trait = "0.1" bytes = "1.1.0" clap = { version = "4.2.2", features = ["derive"] } crossterm = "0.27" -lazy_static = "1.4.0" num-traits = "0.2" +once_cell = "1.18" pin-project = "1.1" regex = "1.7.3" reqwest = { version = "0.11", features = ["gzip", "brotli", "deflate", "json", "stream", "default-tls"] } diff --git a/src/config.rs b/src/config.rs index 88b8508..5a9d827 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,4 +1,5 @@ use std::borrow::Cow; +use once_cell::sync::Lazy; use regex::Regex; use serde::{Deserialize, Serialize}; @@ -24,9 +25,7 @@ impl Default for Config { } pub fn format_content<'a>(content: &'a str, args: &args::ProcessedArgs) -> Cow<'a, str> { - lazy_static::lazy_static!{ - static ref RE: Regex = Regex::new(r"(?P\$\$?)(?P\w+)").expect("Failed to compile regex"); - } + static RE: Lazy = Lazy::new(|| Regex::new(r"(?P\$\$?)(?P\w+)").expect("Failed to compile regex")); RE.replace_all(content, |caps: ®ex::Captures| { let prefix = &caps["prefix"]; if prefix == "$$" { diff --git a/src/formatters/markdown/renderer/terminal/utils.rs b/src/formatters/markdown/renderer/terminal/utils.rs index cf5e5bf..e11e9dc 100644 --- a/src/formatters/markdown/renderer/terminal/utils.rs +++ b/src/formatters/markdown/renderer/terminal/utils.rs @@ -1,5 +1,4 @@ -use lazy_static::lazy_static; - +use once_cell::sync::Lazy; use super::{token, queue}; use std::io::Error; @@ -77,9 +76,9 @@ pub fn repeat_char(c: char, n: usize) -> String { #[inline] pub fn draw_line() -> Result<(), Error> { - lazy_static! { - static ref LINE_STRING: String = repeat_char(CODE_BLOCK_LINE_CHAR[0], CODE_BLOCK_MARGIN.max(crossterm::terminal::size().unwrap_or_default().0 as usize)); - } + static LINE_STRING: Lazy = Lazy::new(|| { + repeat_char(CODE_BLOCK_LINE_CHAR[0], CODE_BLOCK_MARGIN.max(crossterm::terminal::size().unwrap_or_default().0 as usize)) + }); queue!(std::io::stdout(), crossterm::style::Print(&*LINE_STRING) ) diff --git a/src/main.rs b/src/main.rs index aaf6205..0bdb547 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,15 +23,20 @@ macro_rules! raise_str { } fn home_dir() -> &'static str { + static HOME: once_cell::sync::OnceCell = once_cell::sync::OnceCell::new(); + #[cfg(unix)] - lazy_static::lazy_static! { - static ref HOME: String = std::env::var("HOME").expect("Failed to resolve home path"); - } + HOME.set( + std::env::var("HOME") + .expect("Failed to resolve home path") + ).expect("Failed to set home path"); + #[cfg(windows)] - lazy_static::lazy_static! { - static ref HOME: String = std::env::var("USERPROFILE").expect("Failed to resolve user profile path"); - } - &*HOME + HOME.set( + std::env::var("USERPROFILE") + .expect("Failed to resolve user profile path") + ).expect("Failed to set user profile path"); + &HOME.get().unwrap() } fn resolve_path(path: &str) -> Cow { From 71dade629b25d0725de7b3cc935e152e56865dad Mon Sep 17 00:00:00 2001 From: Gly Date: Tue, 19 Sep 2023 15:12:27 +0200 Subject: [PATCH 35/37] Fix home_dir --- src/main.rs | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/main.rs b/src/main.rs index 0bdb547..de4c893 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,20 +23,18 @@ macro_rules! raise_str { } fn home_dir() -> &'static str { - static HOME: once_cell::sync::OnceCell = once_cell::sync::OnceCell::new(); + static HOME: once_cell::sync::Lazy = once_cell::sync::Lazy::new(|| { + #[cfg(unix)] + let path = std::env::var("HOME") + .expect("Failed to resolve home path"); + + #[cfg(windows)] + let path = std::env::var("USERPROFILE") + .expect("Failed to resolve user profile path"); + path + }); - #[cfg(unix)] - HOME.set( - std::env::var("HOME") - .expect("Failed to resolve home path") - ).expect("Failed to set home path"); - - #[cfg(windows)] - HOME.set( - std::env::var("USERPROFILE") - .expect("Failed to resolve user profile path") - ).expect("Failed to set user profile path"); - &HOME.get().unwrap() + &*HOME } fn resolve_path(path: &str) -> Cow { From 2d2b0d65f92232666a2a7916072b4d2fffe809a8 Mon Sep 17 00:00:00 2001 From: Gly Date: Tue, 19 Sep 2023 15:12:42 +0200 Subject: [PATCH 36/37] rewrite cache --- src/runner/program/cache.rs | 65 +++++++++++++++++++++++++++---------- src/runner/program/mod.rs | 4 +-- 2 files changed, 49 insertions(+), 20 deletions(-) diff --git a/src/runner/program/cache.rs b/src/runner/program/cache.rs index 2159ee3..367fad9 100644 --- a/src/runner/program/cache.rs +++ b/src/runner/program/cache.rs @@ -1,3 +1,6 @@ +use std::ops::{Deref, DerefMut}; + +use std::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; use thiserror::Error; use serde::{Deserialize, Serialize}; @@ -16,36 +19,62 @@ pub enum CacheError { NoParent, } +static CACHE: once_cell::sync::Lazy, CacheError>> = once_cell::sync::Lazy::new(|| { + Cache::load().map(|cache| RwLock::new(cache)) +}); + impl Cache { - fn get() -> Result { - let file_path = Self::cache_path(); + fn load() -> Result { + let file_path = Cache::cache_path(); if !file_path.exists() { return Ok(Default::default()); } - let cache_file = std::fs::File::open(file_path)?; - Ok(serde_yaml::from_reader(cache_file)?) - } - fn cache_path() -> std::path::PathBuf { - std::path::Path::new(crate::home_dir()).join(".cache").join("aio.yaml") - } - pub fn get_program(program: &str) -> Result, CacheError> { - let cache = Self::get()?; - let Some(path) = cache.programs.get(program) else { return Ok(None); }; - if !std::path::Path::new(path).exists() { - return Ok(None); + let cache_file = match std::fs::File::open(file_path) { + Ok(file) => file, + Err(e) => return Err(e.into()), + }; + match serde_yaml::from_reader(cache_file) { + Ok(cache) => return Ok(cache), + Err(e) => return Err(e.into()), } - Ok(Some(path.clone())) } - pub fn set_program(program: String, path: String) -> Result<(), CacheError> { + fn save(&self) -> Result<(), CacheError> { let file_path = Self::cache_path(); let Some(parent) = file_path.parent() else { return Err(CacheError::NoParent); }; if !parent.exists() { std::fs::create_dir_all(parent)?; } - let mut cache = Self::get()?; - cache.programs.insert(program.to_string(), path.to_string()); let mut cache_file = std::fs::File::create(file_path)?; - serde_yaml::to_writer(&mut cache_file, &cache)?; + serde_yaml::to_writer(&mut cache_file, self)?; Ok(()) } + fn get() -> RwLockReadGuard<'static, Self> { + match *CACHE { + Ok(ref cache) => cache.read().expect("Error while accessing to the cache memory"), + Err(_) => panic!("Unable to access to the cache file"), + } + } + fn get_mut() -> RwLockWriteGuard<'static, Self> { + match *CACHE { + Ok(ref cache) => cache.write().expect("Error while accessing to the cache memory"), + Err(_) => panic!("Unable to access to the cache file"), + } + } + fn cache_path() -> std::path::PathBuf { + std::path::Path::new(crate::home_dir()).join(".cache").join("aio.yaml") + } +} +pub fn get_program(program: &str) -> Option { + let cache = Cache::get(); + let path = cache.programs.get(program)?; + if !std::path::Path::new(path).exists() { + return None; + } + Some(path.clone()) +} +pub fn set_program(program: String, path: String) -> Result<(), CacheError> { + let mut cache = Cache::get_mut(); + cache.programs.insert(program.to_string(), path.to_string()); + cache.save()?; + Ok(()) } \ No newline at end of file diff --git a/src/runner/program/mod.rs b/src/runner/program/mod.rs index ec7414f..1fa3d11 100644 --- a/src/runner/program/mod.rs +++ b/src/runner/program/mod.rs @@ -43,7 +43,7 @@ enum SearchStatus { } fn search_program(program: &str) -> Result, SearchError> { - if let Some(found) = cache::Cache::get_program(program)? { + if let Some(found) = cache::get_program(program) { return Ok(Some(found)); } #[cfg(target_family = "unix")] @@ -79,7 +79,7 @@ fn search_program(program: &str) -> Result, SearchError> { }); let Some(found) = found else { return Ok(None); }; let found = found.to_str().ok_or(SearchError::BadUTF8)?.to_string(); - cache::Cache::set_program(program.into(), found.clone())?; + cache::set_program(program.into(), found.clone())?; Ok(Some(found)) } From 927daf613cff49e04df3b5b7a690ab43a81a78ad Mon Sep 17 00:00:00 2001 From: Gly Date: Thu, 21 Sep 2023 00:30:51 +0200 Subject: [PATCH 37/37] remove unused file --- src/runner/run.rs | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/runner/run.rs diff --git a/src/runner/run.rs b/src/runner/run.rs deleted file mode 100644 index e69de29..0000000