chore: initial commit
This commit is contained in:
commit
402c67998d
10 changed files with 1320 additions and 0 deletions
34
.github/workflows/ci.yml
vendored
Normal file
34
.github/workflows/ci.yml
vendored
Normal 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
21
.github/workflows/ct-commitlint.yml
vendored
Normal 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
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/target
|
||||||
1108
Cargo.lock
generated
Normal file
1108
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
16
Cargo.toml
Normal file
16
Cargo.toml
Normal 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
21
README.md
Normal 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
42
src/lib.rs
Normal 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
30
src/main.rs
Normal 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
28
tests/features.rs
Normal 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"));
|
||||||
|
}
|
||||||
19
tests/features/basic.feature
Normal file
19
tests/features/basic.feature
Normal 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'
|
||||||
Loading…
Reference in a new issue