commit db3fc2fcff319f1016e777a50d466240cb012aec Author: Greg Date: Sun Dec 23 00:01:35 2018 -0500 start diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..53eaa21 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +**/*.rs.bk diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..eda166d --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,212 @@ +[[package]] +name = "aho-corasick" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "memchr 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "atty" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)", + "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "bitflags" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cfg-if" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "clap" +version = "2.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "lazy_static" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libc" +version = "0.2.45" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "memchr" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)", + "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "re" +version = "0.1.0" +dependencies = [ + "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "redox_syscall" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "redox_termios" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "redox_syscall 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "regex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "aho-corasick 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", + "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "regex-syntax" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "strsim" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "termion" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "textwrap" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "thread_local" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ucd-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unicode-width" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "utf8-ranges" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "vec_map" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "version_check" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[metadata] +"checksum aho-corasick 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)" = "1e9a933f4e58658d7b12defcf96dc5c720f20832deebe3e0a19efd3b6aaeeb9e" +"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +"checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" +"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" +"checksum cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "082bb9b28e00d3c9d39cc03e64ce4cea0f1bb9b3fde493f0cbc008472d22bdf4" +"checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e" +"checksum lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a374c89b9db55895453a74c1e38861d9deec0b01b405a82516e9d5de4820dea1" +"checksum libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)" = "2d2857ec59fadc0773853c664d2d18e7198e83883e7060b63c924cb077bd5c74" +"checksum memchr 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "db4c41318937f6e76648f42826b1d9ade5c09cafb5aef7e351240a70f39206e9" +"checksum redox_syscall 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)" = "a84bcd297b87a545980a2d25a0beb72a1f490c31f0a9fde52fca35bfbb1ceb70" +"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" +"checksum regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "37e7cbbd370869ce2e8dff25c7018702d10b21a20ef7135316f8daecd6c25b7f" +"checksum regex-syntax 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4e47a2ed29da7a9e1960e1639e7a982e6edc6d49be308a3b02daf511504a16d1" +"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" +"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" +"checksum textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "307686869c93e71f94da64286f9a9524c0f308a9e1c87a583de8e9c9039ad3f6" +"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" +"checksum ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535c204ee4d8434478593480b8f86ab45ec9aae0e83c568ca81abf0fd0e88f86" +"checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" +"checksum utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "796f7e48bef87609f7ade7e06495a87d5cd06c7866e6a5cbfceffc558a243737" +"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" +"checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" +"checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0" +"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..9a8c9d4 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "re" +version = "0.1.0" +edition = "2018" +authors = ["Gregory "] +description = "A find & replace CLI focused on user-friendliness" +keywords = ["sed", "find", "replace", "regex"] +license = "MIT" +homepage = "https://github.com/chmln/re" +repository = "https://github.com/chmln/re.git" +categories = ["command-line-utilities", "text-processing", "development-tools"] + +[dependencies] +clap = "2.32.0" +regex = "1.1.0" + +[profile.release] +opt-level = 3 +lto = true +debug = false diff --git a/src/app.rs b/src/app.rs new file mode 100644 index 0000000..80d6231 --- /dev/null +++ b/src/app.rs @@ -0,0 +1,56 @@ +use {crate::{Error,Stream,Source}}; + +const VERSION: &'static str = env!("CARGO_PKG_VERSION"); + +pub(crate) struct App; + +impl App { + pub(crate) fn run() -> Result<(), Error> { + use clap; + let app = clap::App::new("re") + .version(VERSION) + .setting(clap::AppSettings::ColoredHelp) + .setting(clap::AppSettings::NextLineHelp) + .arg( + clap::Arg::with_name("enable_regex") + .short("r") + .long("regex") + .required(false) + .takes_value(false) + .help("Enable regex"), + ) + .arg( + clap::Arg::with_name("input") + .short("i") + .required(false) + .takes_value(true) + .help("The path to file") + ) + .arg( + clap::Arg::with_name("find") + .help("The string or regexp (if --regex) to search for.") + .required(true) + .index(1), + ) + .arg( + clap::Arg::with_name("replace_with") + .help("What to replace each match with. If regex is enabled, you may use captured values like $1, $2, etc.") + .required(true) + .index(2), + ); + + let matches = app.get_matches(); + + let file_path = matches.value_of("input").map(|p| p.to_string()); + let find = matches.value_of("find").unwrap(); + let replace_with = matches.value_of("replace_with").unwrap(); + let is_regex = matches.occurrences_of("enable_regex") == 1; + + let source = Source::from(file_path); + let mut stream: Stream = (&source).into_stream()?; + stream.replace(is_regex, find, replace_with)?; + + // replace file in-place, or pipe to stdout + stream.output(&source) + } +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..8d919c9 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,32 @@ +pub(crate) struct Error { + // low-level cause, used only for debugging + cause: String, + // user-facing error output + description: String, +} + +impl From for Error +where + T: std::error::Error, +{ + fn from(err: T) -> Error { + Error { + cause: err + .source() + .map_or_else(|| "N/A".to_string(), |e| e.to_string()), + description: format!("{}", err), + } + } +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", self.description) + } +} + +impl std::fmt::Debug for Error { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "Error: {}\nDetails: {}", self.description, self.cause) + } +} diff --git a/src/input.rs b/src/input.rs new file mode 100644 index 0000000..7c283a5 --- /dev/null +++ b/src/input.rs @@ -0,0 +1,78 @@ +use {crate::Error, std::{fs::File, io::prelude::*}}; + +pub(crate) enum Source { + Stdin, + File(String), +} + +impl Source { + pub(crate) fn from(maybe_path: Option) -> Self { + maybe_path + .map(Source::File) + .unwrap_or_else(|| Source::Stdin) + } + + pub(crate) fn into_stream(&self) -> Result { + match self { + Source::Stdin => { + let mut buffer = String::new(); + let stdin = std::io::stdin(); + let mut handle = stdin.lock(); + + handle.read_to_string(&mut buffer)?; + Ok(Stream::new(buffer)) + } + Source::File(path) => { + let file = File::open(path)?; + let mut buf_reader = std::io::BufReader::new(file); + let mut buffer = String::new(); + buf_reader.read_to_string(&mut buffer)?; + Ok(Stream::new(buffer)) + } + } + } + +} + +pub(crate) struct Stream { + data: String, +} + +impl Stream { + pub(crate) fn new(data: String) -> Self { + Self { data } + } + + pub(crate) fn replace( + &mut self, + is_regex: bool, + look_for: &str, + replace_with: &str, + ) -> Result<(), crate::Error> { + if is_regex { + self.data = regex::Regex::new(look_for)? + .replace_all(&self.data, replace_with) + .to_string() + } else { + self.data = self.data.replace(look_for, replace_with) + } + Ok(()) + } + + // Output based on input. + // When dealing with a file, transform it in-place. + // Otherwise, pipe to stdout. + pub(crate) fn output(self, source: &Source) -> Result<(), crate::Error> { + match source { + Source::File(path) => { + Ok(std::fs::write(path, self.data)?) + }, + Source::Stdin => { + let stdout = std::io::stdout(); + let mut handle = stdout.lock(); + handle.write(self.data.as_bytes())?; + Ok(()) + } + } + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..e861bbc --- /dev/null +++ b/src/main.rs @@ -0,0 +1,12 @@ +mod app; +mod error; +mod input; + +pub(crate) use { + crate::error::Error, + crate::input::{Source, Stream}, +}; + +fn main() -> Result<(), ()> { + app::App::run().map_err(|e| eprintln!("{}", e)) +} diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1 @@ +