chore: initial commit

This commit is contained in:
Ade Attwood 2024-05-10 20:31:52 +01:00
commit 402c67998d
10 changed files with 1320 additions and 0 deletions

34
.github/workflows/ci.yml vendored Normal file
View file

@ -0,0 +1,34 @@
name: CI
on:
push: { branches: ["0.x"] }
pull_request: { branches: ["0.x"] }
jobs:
cargo-format:
name: Cargo Format
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up rust
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt
- name: Lint
run: cargo fmt --check
test:
name: Cargo Test
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up rust
uses: dtolnay/rust-toolchain@stable
- name: Test
run: cargo test

21
.github/workflows/ct-commitlint.yml vendored Normal file
View file

@ -0,0 +1,21 @@
name: Conventional Tools Commitlint
on:
push: { branches: ["0.x"] }
pull_request: { branches: ["0.x"] }
jobs:
commits:
name: Commitlint
runs-on: ubuntu-latest
container: practically/conventional-tools:1.x@sha256:647d6e4b3edfcbac6054b90f74d2c61a022152751b94484d54e13695a9e27377
steps:
- name: Checkout
uses: actions/checkout@v4
with: {fetch-depth: 1000}
- name: Git safe.directory
run: git config --global --add safe.directory $PWD
- name: Lint commits
run: conventional-tools commitlint

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

1108
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

16
Cargo.toml Normal file
View file

@ -0,0 +1,16 @@
[package]
name = "dev_case"
version = "0.1.0"
edition = "2021"
[dependencies]
clap = { version = "4.5.4", features = ["derive"] }
regex = "1.10.4"
[dev-dependencies]
cucumber = "0.20"
futures = "0.3"
[[test]]
name = "features"
harness = false

21
README.md Normal file
View file

@ -0,0 +1,21 @@
<div align="center">
# Dev Case
A regex search and replace tool for preserving case
</div>
## Description
Dev Case is a command-line tool built with Rust that simplifies regex search
and replace operations while preserving the case of the matched text. It allows
developers to efficiently manipulate text files while maintaining the original
casing of the content.
## Features
- **Regex Search and Replace**: Perform search and replace operations using regular expressions.
- **Case Preservation**: Maintain the original casing of the matched text during replacement.
- **CLI Interface**: Command-line interface for easy integration into development workflows.
- **Built with Rust**: Utilizes the performance and safety benefits of the Rust programming language.

42
src/lib.rs Normal file
View file

@ -0,0 +1,42 @@
use regex::Regex;
pub fn replace(search: &String, replace: String, input: String) -> String {
let mut index = 0;
let mut output = input;
let search_pattern = Regex::new(search).unwrap();
while let Some(search_match) = search_pattern.find_at(&output, index) {
let start = search_match.start();
let end = search_match.end();
let mut replacement = String::new();
match search_pattern.captures(&output[start..end]) {
Some(captures) => {
captures.expand(&replace, &mut replacement);
}
None => {
replacement.push_str(&replace);
}
};
index = start + replacement.len();
output.replace_range(start..end, &replacement);
}
output
}
#[cfg(test)]
mod tests {
#[test]
fn runs_the_search_and_replace() {
assert_eq!(
String::from("foo foo"),
super::replace(
&String::from(r"\b\w+\b"),
String::from("foo"),
String::from("bar baz")
),
);
}
}

30
src/main.rs Normal file
View file

@ -0,0 +1,30 @@
use clap::Parser;
use std::io::Read;
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct Args {
/// The pattern to search for in your input.
#[arg(short, long)]
search: String,
/// The replacement pattern.
#[arg(short, long)]
replace: String,
}
fn main() {
let args = Args::parse();
let mut input = String::new();
match std::io::stdin().read_to_string(&mut input) {
Ok(_) => (),
Err(err) => {
eprintln!("Error reading from stdin: {}", err);
return;
}
};
let output = dev_case::replace(&args.search, args.replace, input);
print!("{}", output);
}

28
tests/features.rs Normal file
View file

@ -0,0 +1,28 @@
use cucumber::{given, then, World};
#[derive(Debug, Default, World)]
pub struct TestWorld {
search: String,
replace: String,
input: String,
}
#[given(regex = r"^(Search|Replace|Input|Output) is '([^']+)'$")]
fn set_item(world: &mut TestWorld, input: String, output: String) {
match input.as_str() {
"Search" => world.search = output,
"Replace" => world.replace = output,
"Input" => world.input = output,
_ => unreachable!(),
}
}
#[then(regex = r"^Output is '([^']+)'$")]
fn assert_output(world: &mut TestWorld, expected: String) {
let actual = dev_case::replace(&world.search, world.replace.clone(), world.input.clone());
assert_eq!(actual, expected);
}
fn main() {
futures::executor::block_on(TestWorld::run("tests/features/basic.feature"));
}

View file

@ -0,0 +1,19 @@
Feature: Basic search and replace
Scenario: You can do a basic search and replace of words
Given Search is 'word'
And Replace is 'another'
And Input is 'This is a word'
Then Output is 'This is a another'
Scenario: You can do multiple search and replaces
Given Search is 'word'
And Replace is 'another'
And Input is 'This is a word and another word'
Then Output is 'This is a another and another another'
Scenario: You can search and replace with multiple words
Given Search is 'another word'
And Replace is 'word another'
And Input is 'This is a word and another word'
Then Output is 'This is a word and word another'