Skip to content
Open
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 001/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
https://codingchallenges.fyi/challenges/challenge-wc
32 changes: 32 additions & 0 deletions 001/rust/ccwc/.cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
[build]
rustc-wrapper = '/Users/pedro.silva/.cargo/bin/sccache'
[target.x86_64-unknown-linux-gnu]
rustflags = [
'-Clink-arg=-fuse-ld=lld',
'-Zshare-generics=y',
]
linker = '/usr/bin/clang'

[target.x86_64-pc-windows-msvc]
rustflags = ['-Zshare-generics=y']
linker = 'rust-lld.exe'

[target.x86_64-apple-darwin]
rustflags = [
'-C',
'link-arg=-fuse-ld=/usr/local/bin/zld',
'-Zshare-generics=y',
'-Csplit-debuginfo=unpacked',
]
[profile.dev]
opt-level = 0
debug = 2
incremental = true
codegen-units = 512

[profile.release]
opt-level = 3
debug = 0
incremental = false
codegen-units = 256
split-debuginfo = '...'
3 changes: 3 additions & 0 deletions 001/rust/ccwc/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
target
*.profraw
test.file
25 changes: 25 additions & 0 deletions 001/rust/ccwc/Cargo.lock

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

9 changes: 9 additions & 0 deletions 001/rust/ccwc/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "ccwc"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
getopts = "0.2"
18 changes: 18 additions & 0 deletions 001/rust/ccwc/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
.PHONY: run setup clean_cover cover check

run:
cargo run -- "$@"

setup:
cargo install grcov

clean_cover:
rm -rf *.profraw ./target/coverage

cover: clean_cover
CARGO_INCREMENTAL=0 RUSTFLAGS='-Cinstrument-coverage' LLVM_PROFILE_FILE='ccwc-%p-%m.profraw' cargo test && \
grcov . --binary-path ./target/debug/ -s . -t html --branch --ignore-not-existing --ignore '../*' --ignore "/*" -o target/coverage/html && \
open ./target/coverage/html/index.html

check:
cargo clippy
16 changes: 16 additions & 0 deletions 001/rust/ccwc/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
### Code coverage notes

- https://blog.rng0.io/how-to-do-code-coverage-in-rust

# install llvm component

`rustup component add llvm-tools-preview`

# TODO

- [ ] Add test coverage
- [ ] Add benchmark tests
- [ ] Improve returned error messages
- [ ] Make it a full replacement for `wc`
- [ ] Make it better than `wc`
- [ ] Add stress tests
5 changes: 5 additions & 0 deletions 001/rust/ccwc/fleet.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
rd_enabled = true
fleet_id = "3ba346f7-a4b3-4dbe-a690-1d429748f050"

[build]
sccache = "/Users/pedro.silva/.cargo/bin/sccache"
248 changes: 248 additions & 0 deletions 001/rust/ccwc/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
extern crate getopts;

use getopts::Options;
use std::env;
use std::error::Error;
use std::fs::File;
use std::io::BufReader;
use std::io::Read;

#[derive(Default)]
struct WCCmd {
/// flag to enable the count of bytes
bytes: bool,

/// flag to enable the count of lines
lines: bool,

/// flag to enable the count of words
words: bool,

/// flag to enable the count of characters
chars: bool,

/// input files to be processed (empty if stdin)
inputs: Vec<WCInput>,

/// output processed
outputs: Vec<WCOutput>,
}

impl WCCmd {
fn show(&self) {
for o in &self.outputs {
println!("{}", o.as_string(self))
}
}

fn use_default_flags(&self) -> bool {
!self.chars && !self.lines && !self.words && !self.bytes
}

fn from_args(args: Vec<String>) -> Result<Self, getopts::Fail> {
let mut opts = Options::new();

// define the options
opts.optflag("c", "bytes", "count the number of bytes");
opts.optflag("l", "lines", "count the number of lines");
opts.optflag("w", "words", "count the number of words");
opts.optflag("m", "chars", "count the number of chars");

// parse the options
let opts_matches = opts.parse(&args[1..])?;
let arg_inputs = opts_matches.free.to_owned();
let mut parsed_inputs = Vec::new();

if !arg_inputs.is_empty() {
for f in arg_inputs {
parsed_inputs.push(WCInput::File(f.to_string()));
}
} else {
parsed_inputs.push(WCInput::StdIn())
}

Ok(WCCmd {
bytes: opts_matches.opt_present("c"),
lines: opts_matches.opt_present("l"),
words: opts_matches.opt_present("w"),
chars: opts_matches.opt_present("m"),
inputs: parsed_inputs,
outputs: Vec::new(),
})
}

fn process(&mut self) -> std::io::Result<()> {
let mut buffer: Vec<u8> = Vec::new();
let default = self.use_default_flags();

for input in &mut self.inputs {
let mut output = WCOutput::default();

input.as_buffer(&mut buffer)?;
output.filename = input.path();

let mut parsing_word = true;
for b in buffer.iter() {
if self.bytes || default {
output.byte_ct += 1;
}
if (self.lines || default) && *b == b'\n' {
output.line_ct += 1;
}

if self.words || default {
if parsing_word {
if b.is_ascii_whitespace() {
output.word_ct += 1;
parsing_word = false;
}
} else if !b.is_ascii_whitespace() {
parsing_word = true;
}
}
}

if self.chars {
match String::from_utf8(buffer.to_owned()) {
Ok(s) => {
output.char_ct = s.chars().count() as u64;
}
_ => {
// if there is an error we fallback for bytes
output.char_ct = output.byte_ct;
}
}
}
self.outputs.push(output);
buffer.clear();
}
Ok(())
}
}

enum WCInput {
File(String),
StdIn(),
}

impl WCInput {
fn path(&self) -> Option<String> {
match self {
WCInput::File(s) => Some(s.clone()),
WCInput::StdIn() => None,
}
}

fn as_buffer(&mut self, buffer: &mut Vec<u8>) -> std::io::Result<()> {
match self {
WCInput::File(f) => {
let file = File::open(f)?;
let mut reader = BufReader::new(file);
reader.read_to_end(buffer)?;
}
WCInput::StdIn() => {
std::io::stdin().read_to_end(buffer)?;
}
}
Ok(())
}
}

#[derive(Default)]
struct WCOutput {
byte_ct: u64,
line_ct: u64,
word_ct: u64,
char_ct: u64,
filename: Option<String>,
}

impl WCOutput {
fn as_string(&self, wc: &WCCmd) -> String {
let mut out = String::new();
let default = wc.use_default_flags();

if wc.lines || default {
out.push_str(format!("\t{}", self.line_ct).as_str());
}
if wc.words || default {
out.push_str(format!("\t{}", self.word_ct).as_str());
}
if wc.chars {
out.push_str(format!("\t{}", self.char_ct).as_str());
} else if wc.bytes || default {
out.push_str(format!("\t{}", self.byte_ct).as_str());
}
if let Some(f) = &self.filename {
out.push_str(format!(" {}", f).as_str())
}
out.to_string()
}
}

fn main() -> Result<(), Box<dyn Error>> {
let args = env::args().collect();
let mut wc = WCCmd::from_args(args)?;
wc.process()?;
wc.show();
Ok(())
}

#[cfg(test)]
mod tests {

use crate::{WCCmd, WCInput, WCOutput};

#[test]
fn wccmd_parse_env_args_test() {
let args = vec![String::from("ccwc"), String::from("test.file")];
let wc = WCCmd::from_args(args).unwrap();

_ = wc;
}

#[test]
fn input_path_test() {
let filepath = String::from("test.file");
let file_input = WCInput::File(filepath.clone());

let stdin_input = WCInput::StdIn();
assert_eq!(filepath, file_input.path().unwrap());
assert_eq!(None, stdin_input.path());
}

#[test]
fn wcoutput_print_using_default_test() {
let wc = WCCmd::default();

let out = WCOutput::default();
let result = out.as_string(&wc);
let expected = "\t0\t0\t0";
assert_eq!(result, expected);
}

#[test]
fn wcoutput_print_using_chars_test() {
let mut wc = WCCmd::default();
wc.chars = true;

let mut out = WCOutput::default();
out.char_ct = 123;

let result = out.as_string(&wc);
let expected = "\t123";
assert_eq!(result, expected);
}

#[test]
fn wcoutput_print_with_input_file_test() {
let input = Some(String::from("./test_file.txt"));
let wc = WCCmd::default();
let mut out = WCOutput::default();
out.filename = input.clone();

let result = out.as_string(&wc);
let expected = format!("\t0\t0\t0 {}", input.unwrap());
assert_eq!(result, expected);
}
}
Loading