Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* @timothebot
2 changes: 1 addition & 1 deletion .github/workflows/rust-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,4 @@ jobs:
- name: doesn't detect powershell
if: ${{ matrix.os != 'macos-14' }}
shell: bash
run: cargo test --release -- --ignored is_powershell_false
run: cargo test --release -- --ignored is_powershell_false
9 changes: 5 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ executes or lines getting replaced in a target file.
- [ ] Path support
- [ ] Improve readme
- [ ] Variables support
- [ ] Input variables (ask for variable or command or something)
4 changes: 2 additions & 2 deletions book/src/configuration/brick_config.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ Allows you to run a command or a script file.
[[actions]]
action = "run_command"

command = "echo 'hi'" # command or a script file (prefix with file:)
command = "echo 'hi'"
```

This is by far the most simple yet powerful action.
If you need more complex behaviour, you can add a custom script that does what you need.
If you need more complex behavior, you can add a custom script that does what you need.
4 changes: 2 additions & 2 deletions book/src/intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Instead of having to look up and copy your desired license from the web, you can

You can create multiple bricks and combine them behind an alias.

This way, you can easily bootstrap new projects!
This way, you can easily bootstrap new projects!

```shell
$ cargo new my_project && cd my_project
Expand All @@ -26,4 +26,4 @@ $ crane add rust
• rustfmt
• rustauthor
# ...
```
```
2 changes: 1 addition & 1 deletion crane/src/cmd/commands.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::path::PathBuf;

use clap::{Parser, Subcommand, ValueHint, command};
use clap::{Parser, Subcommand, ValueHint};
use clap_verbosity::{InfoLevel, Verbosity};

#[derive(Debug, Parser)]
Expand Down
1 change: 1 addition & 0 deletions crane_bricks/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ toml = "0.9.6"
anyhow = "1.0.99"
log = "0.4.28"
shellexpand = "3.1.1"
regex = "1.12.2"

[dev-dependencies]
tempfile = "3"
Expand Down
90 changes: 67 additions & 23 deletions crane_bricks/src/actions/modify_file.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use anyhow::{Ok, anyhow};
use regex::Regex;
use serde::Deserialize;

use crate::{
actions::{ExecuteAction, common::Common},
brick::BrickFile,
file_utils::{file_read_content, file_replace_content},
};

Expand Down Expand Up @@ -61,16 +63,65 @@ enum ModifyType {
Replace,
}

fn modify_content_using_regex(
source_text: String,
selector: &str,
content: String,
modify_type: &ModifyType,
) -> anyhow::Result<String> {
let re = Regex::new(selector)?;
match modify_type {
ModifyType::Append => Ok(re
.replace_all(&source_text, format!("$0{}", content))
.to_string()),
ModifyType::Prepend => Ok(re
.replace_all(&source_text, format!("{}$0", content))
.to_string()),
ModifyType::Replace => Ok(re.replace_all(&source_text, content).to_string()),
}
}

impl ModifyFileAction {
pub fn content(&self) -> String {
// TODO: Get content from somewhere else if not set
self.content.clone().unwrap_or_default()
pub fn content(&self, brick: &crate::brick::Brick) -> anyhow::Result<String> {
let content = self.content.clone().unwrap_or_default();
if let Some(file) = content.strip_prefix("file:") {
let content_file: Vec<BrickFile> = brick
.files()
.into_iter()
.filter(|f| f.name() == file)
.collect();
if content_file.len() != 1 {
return Err(anyhow!(
"The file '{}' that was defined for taking the content from does not exis, or multiple results found. (Found {})",
file,
content_file.len()
));
}
return Ok(content_file.first().unwrap().content().to_string());
};
Ok(content)
}

pub fn modify_content(&self, source_text: String) -> anyhow::Result<String> {
pub fn modify_content(
&self,
brick: &crate::brick::Brick,
source_text: String,
) -> anyhow::Result<String> {
// TODO: Handle regex
// TODO: insert for all or just one?

let content = &self.content(brick)?;

if self.selector.starts_with("regex:") {
info!("Modifying content with regex selector.");
return modify_content_using_regex(
source_text,
self.selector.strip_prefix("regex:").unwrap_or_default(),
content.to_string(),
&self.r#type,
);
}

let locations: Vec<(usize, &str)> =
source_text.match_indices(&self.selector).collect();

Expand All @@ -92,29 +143,23 @@ impl ModifyFileAction {
let modified_index = index + output.len().abs_diff(start_length);
match &self.r#type {
ModifyType::Append => {
output.insert_str(
(modified_index + selected.len()).max(0),
&self.content(),
);
output.insert_str((modified_index + selected.len()).max(0), content);
}
ModifyType::Prepend => {
output.insert_str(modified_index, &self.content());
output.insert_str(modified_index, content);
}
ModifyType::Replace => {
// TODO: Something isnt right here but im so tired rn pls
// future tiimo fix this
debug!(
"replacing from {} to {} (total chars {})",
"Replacing from {} to {}",
modified_index,
(modified_index + selected.len()).max(0),
output.len()
(modified_index + selected.len()).max(0)
);
if modified_index > output.len() {
output.insert_str(output.len(), &self.content());
output.insert_str(output.len(), content);
} else {
output.replace_range(
modified_index..(modified_index + selected.len()),
&self.content(),
content,
);
}
}
Expand Down Expand Up @@ -142,20 +187,19 @@ impl ExecuteAction for ModifyFileAction {
brick: &crate::brick::Brick,
cwd: &std::path::Path,
) -> anyhow::Result<()> {
let mut files: Vec<String> = brick
.files()
.iter()
.map(|brick_file| brick_file.name().to_string())
.collect();
files.extend(self.common.sources.clone());
let files: Vec<String> = self.common.sources.clone();
for file in files {
let target_path = cwd.join(file);
if !target_path.exists() {
return Err(anyhow!("Target file does not exist!"));
}
info!("Modifying file '{}'", target_path.display());
let content = file_read_content(context, &target_path)?;
file_replace_content(context, &target_path, &self.modify_content(content)?)?;
file_replace_content(
context,
&target_path,
&self.modify_content(brick, content)?,
)?;
}
Ok(())
}
Expand Down
10 changes: 6 additions & 4 deletions crane_bricks/src/brick.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use serde::Deserialize;
use crate::{
actions::{Action, ExecuteAction, insert_file::InsertFileAction},
context::ActionContext,
file_utils::{sub_dirs, sub_paths},
file_utils::{all_file_paths, sub_dirs},
};

const BRICK_CONFIG_FILE: &str = "brick.toml";
Expand Down Expand Up @@ -100,15 +100,17 @@ impl Brick {
Ok(())
}

/// Returns a list of all files that
/// Returns a list of all files that are in the brick dir
pub fn files(&self) -> Vec<BrickFile> {
let Ok(paths) = sub_paths(self.path()) else {
let Ok(paths) = all_file_paths(self.path()) else {
return vec![];
};

let brick_path = format!("{}/", self.path().display());
paths
.iter()
.filter_map(|path| {
let name = path.file_name()?.display().to_string();
let name = path.display().to_string().replace(&brick_path, "");
if !path.is_file() || name == BRICK_CONFIG_FILE {
return None;
}
Expand Down
44 changes: 38 additions & 6 deletions crane_bricks/src/file_utils.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use std::{
fs::{self, File},
fs::{self, File, create_dir_all},
io::{self, Write},
path::{Path, PathBuf},
};
Expand All @@ -15,7 +15,27 @@ pub fn sub_dirs(dir: &Path) -> anyhow::Result<Vec<PathBuf>> {
.collect::<Vec<PathBuf>>())
}

/// Get a vec of all files and folders in the given dir if valid
/// Returns the path to every file located in the given dir, including
/// paths in subdirectories
pub fn all_file_paths(dir: &Path) -> anyhow::Result<Vec<PathBuf>> {
let dir = PathBuf::from(shellexpand::tilde(&dir.display().to_string()).to_string());
if !dir.exists() || !dir.is_dir() {
return Err(anyhow!("Target does not exist or not a directory"));
}
let mut paths: Vec<PathBuf> = Vec::new();
if let Ok(children) = sub_paths(dir.as_path()) {
for path in children {
if path.is_dir() {
paths.append(&mut all_file_paths(path.as_path()).unwrap());
} else if path.is_file() {
paths.push(path);
}
}
}
Ok(paths)
}

/// Returns a vec of all files and folders in the given dir (without subdirs) if valid
pub fn sub_paths(dir: &Path) -> anyhow::Result<Vec<PathBuf>> {
let dir = PathBuf::from(shellexpand::tilde(&dir.display().to_string()).to_string());
if !dir.exists() || !dir.is_dir() {
Expand All @@ -32,11 +52,23 @@ pub fn file_create_new(
path: &Path,
content: Option<String>,
) -> anyhow::Result<()> {
if !ctx.dry_run {
debug!("Creating new file '{:?}'", path);
let mut file = File::create_new(path)?;
file.write_all(content.unwrap_or_default().as_bytes())?;
if ctx.dry_run {
return Ok(());
}
debug!("Creating new file '{:?}'", path);
if let Some(parent_dir) = path.parent() {
if !parent_dir.exists() {
debug!("Adding all missing parent dir(s).");
match create_dir_all(parent_dir) {
Ok(_) => {}
Err(_) => return Err(anyhow!("Couldn't create parent dir(s)!")),
}
}
} else {
debug!("Parent dirs exists");
}
let mut file = File::create_new(path)?;
file.write_all(content.unwrap_or_default().as_bytes())?;
Ok(())
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
C1
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This file should not exist at target location
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
name = "test"

[[actions]]
action = "insert_file"
if_file_exists = "replace"
sources = [
"TEST_C1",
"sub/TEST_C2"
]
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
C2
2 changes: 1 addition & 1 deletion crane_bricks/tests/bricks/insert_no_config/TEST_B
Original file line number Diff line number Diff line change
@@ -1 +1 @@
Hello World
Hello World
2 changes: 1 addition & 1 deletion crane_bricks/tests/bricks/insert_with_config/TEST_A
Original file line number Diff line number Diff line change
@@ -1 +1 @@
TEST
TEST
8 changes: 8 additions & 0 deletions crane_bricks/tests/bricks/modify_append_regex/brick.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
name = "test"

[[actions]]
sources = ["Test.toml"]
action = "modify_file"
type = "append"
content = "\nserde = \"1\""
selector = "regex:\\[[a-z]+\\]"
8 changes: 8 additions & 0 deletions crane_bricks/tests/bricks/modify_prepend_regex/brick.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
name = "test"

[[actions]]
sources = ["Test.toml"]
action = "modify_file"
type = "prepend"
content = "# This is group $group\n"
selector = "regex:\\[(?<group>[a-z]+)\\]"
8 changes: 8 additions & 0 deletions crane_bricks/tests/bricks/modify_replace_regex/brick.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
name = "test"

[[actions]]
sources = ["Test.toml"]
action = "modify_file"
type = "replace"
content = "[dev-$group]"
selector = "regex:\\[(?<group>d[a-z]+)\\]"
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
name = "test"

[[actions]]
sources = ["Test.toml"]
action = "modify_file"
type = "replace"
content = "file:sub/content"
selector = "[dependencies]"
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
NEW_CONTENT
Loading