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