From 83afe5ee184e2d278054347fa2e866ce4d420c5a Mon Sep 17 00:00:00 2001 From: Vladislav Mamon Date: Tue, 5 Mar 2024 03:28:50 +0300 Subject: [PATCH] feat(actions): implement `replace` action --- src/actions/actions.rs | 59 ++++++++++++++++++++++++++++++++++++++--- src/actions/executor.rs | 6 +++-- src/repository.rs | 2 +- 3 files changed, 61 insertions(+), 6 deletions(-) diff --git a/src/actions/actions.rs b/src/actions/actions.rs index 83879c7..435b93e 100644 --- a/src/actions/actions.rs +++ b/src/actions/actions.rs @@ -1,11 +1,16 @@ use std::path::{Path, PathBuf}; use std::process; +use std::thread; +use std::time::{Duration, Instant}; use crossterm::style::Stylize; use run_script::ScriptOptions; +use tokio::fs::{File, OpenOptions}; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; use unindent::Unindent; use crate::actions::{State, Value}; +use crate::fs::Traverser; use crate::manifest::actions::*; use crate::spinner::Spinner; @@ -60,7 +65,6 @@ impl Run { if let Some(injects) = &self.injects { for inject in injects { if let Some(Value::String(value)) = state.values.get(inject) { - // In format strings we escape `{` and `}` by doubling them. command = command.replace(&format!("{{{inject}}}"), value); } } @@ -121,8 +125,57 @@ impl Prompt { } impl Replace { - pub async fn execute(&self, _state: &State) -> anyhow::Result<()> { - Ok(println!("replace action")) + pub async fn execute

(&self, root: P, state: &State) -> anyhow::Result<()> + where + P: Into + AsRef, + { + let spinner = Spinner::new(); + let start = Instant::now(); + + // If no glob pattern specified, traverse all files. + let pattern = self.glob.clone().unwrap_or("**/*".to_string()); + + let traverser = Traverser::new(root.into()) + .ignore_dirs(true) + .contents_first(true) + .pattern(&pattern); + + if !self.replacements.is_empty() { + spinner.set_message("Performing replacements"); + + for matched in traverser.iter().flatten() { + let mut buffer = String::new(); + let mut file = File::open(&matched.path).await?; + + file.read_to_string(&mut buffer).await?; + + for replacement in &self.replacements { + if let Some(Value::String(value)) = state.values.get(replacement) { + buffer = buffer.replace(&format!("{{{replacement}}}"), value); + } + } + + let mut result = OpenOptions::new() + .write(true) + .truncate(true) + .open(&matched.path) + .await?; + + result.write_all(buffer.as_bytes()).await?; + } + + // Add artificial delay if replacements were performed too fast. + let elapsed = start.elapsed(); + + // This way we spent at least 1 second before stopping the spinner. + if elapsed < Duration::from_millis(750) { + thread::sleep(Duration::from_millis(1_000) - elapsed); + } + + spinner.stop_with_message("Successfully performed replacements\n"); + } + + Ok(()) } } diff --git a/src/actions/executor.rs b/src/actions/executor.rs index 78e1ae6..84601bc 100644 --- a/src/actions/executor.rs +++ b/src/actions/executor.rs @@ -102,14 +102,16 @@ impl Executor { /// Execute a single action. async fn single(&self, action: &ActionSingle, state: &mut State) -> anyhow::Result<()> { + let root = &self.manifest.root; + match action { | ActionSingle::Copy(action) => action.execute().await, | ActionSingle::Move(action) => action.execute().await, | ActionSingle::Delete(action) => action.execute().await, | ActionSingle::Echo(action) => action.execute(state).await, - | ActionSingle::Run(action) => action.execute(&self.manifest.root, state).await, + | ActionSingle::Run(action) => action.execute(root, state).await, | ActionSingle::Prompt(action) => action.execute(state).await, - | ActionSingle::Replace(action) => action.execute(state).await, + | ActionSingle::Replace(action) => action.execute(root, state).await, | ActionSingle::Unknown(action) => action.execute().await, } } diff --git a/src/repository.rs b/src/repository.rs index aca6f2f..31e90a9 100644 --- a/src/repository.rs +++ b/src/repository.rs @@ -169,7 +169,7 @@ impl FromStr for RemoteRepository { is_valid_user(ch) || ch == '.' } - // TODO: Handle an edge case with multuple slashes in the repository name. + // TODO: Handle an edge case with multiple slashes in the repository name. let input = input.trim();