Skip to content

Commit

Permalink
Added some code.
Browse files Browse the repository at this point in the history
  • Loading branch information
DariuszDepta committed Nov 22, 2024
1 parent d6cee8e commit 6c8c067
Show file tree
Hide file tree
Showing 12 changed files with 440 additions and 3 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

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

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
[package]
name = "kivi"
version = "0.0.0"
version = "0.0.1"
authors = ["Dariusz Depta <[email protected]>"]
description = "A key-value pair with the key and value on different lines."
description = "A key-value pair with the key and value on different lines"
documentation = "https://docs.rs/kivi"
repository = "https://github.com/EngosSoftware/kivi.git"
keywords = ["key", "value", "pair", "double", "line"]
Expand Down
40 changes: 40 additions & 0 deletions Taskfile.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
version: '3'

silent: true

tasks:

build:
desc: Builds in debug mode
cmds:
- cmd: cargo +stable build

clean:
desc: Cleans all targets
cmds:
- cmd: cargo clean

clippy:
desc: Runs clippy for all targets
cmds:
- cmd: cargo +stable clippy --all-targets

doc:
desc: Generates documentation
cmds:
- cmd: cargo +stable doc --document-private-items

doc-open:
desc: Generates documentation and opens in browser
cmds:
- cmd: cargo +stable doc --open --document-private-items

fmt:
desc: Runs code formatter
cmds:
- cmd: cargo +nightly fmt

test:
desc: Runs tests in debug mode
cmds:
- cmd: cargo +stable test
7 changes: 7 additions & 0 deletions coverage.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env bash

# generate coverage report
cargo tarpaulin --force-clean --out Html --engine llvm --output-dir "$(pwd)/target/coverage-report"

# display link to coverage report
echo "Report: file://$(pwd)/target/coverage-report/tarpaulin-report.html"
3 changes: 3 additions & 0 deletions rustfmt.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
imports_granularity = "Module"
tab_spaces = 2
max_width = 180
5 changes: 5 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
mod loader;
mod model;

pub use loader::{load_from_file, load_from_string};
pub use model::KeyValuePairs;
116 changes: 116 additions & 0 deletions src/loader.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
//! # Loader
use crate::model::KeyValuePairs;
use std::path::Path;
use std::{fs, io};

enum State {
Key,
KeyExt,
Value,
ValueExt,
}

pub fn load_from_string(input: &str) -> KeyValuePairs {
let mut output = KeyValuePairs::new();
let mut state = State::Key;
let mut buffer = String::new();
let mut key = String::new();
let mut chars = input.chars().peekable();

while let Some(ch) = chars.next() {
match state {
State::Key => match ch {
'"' => {
buffer.clear();
state = State::KeyExt;
}
'\n' => {
buffer = buffer.trim().to_string();
if !buffer.is_empty() {
key.clear();
key.push_str(&buffer);
buffer.clear();
state = State::Value;
}
}
other => buffer.push(other),
},
State::KeyExt => match ch {
'"' => {
key.clear();
key.push_str(&buffer);
buffer.clear();
state = State::Value;
}
'\\' => {
if let Some(nch) = chars.peek() {
if *nch == '"' {
buffer.push('"');
chars.next();
continue; // WATCH OUT!
}
}
buffer.push('\\');
}
other => buffer.push(other),
},
State::Value => match ch {
'"' => {
buffer.clear();
state = State::ValueExt;
}
'\n' => {
buffer = buffer.trim().to_string();
if !buffer.is_empty() {
// Consume the key and value.
output.kv.insert(key.clone(), buffer.clone());
key.clear();
buffer.clear();
state = State::Key;
}
buffer.clear();
}
other => {
buffer.push(other);
if chars.peek().is_none() {
// If there are no more characters on input,
// then consume the key and value.
buffer = buffer.trim().to_string();
if !buffer.is_empty() {
output.kv.insert(key.clone(), buffer.clone());
key.clear();
buffer.clear();
state = State::Key;
}
buffer.clear();
}
}
},
State::ValueExt => match ch {
'"' => {
output.kv.insert(key.clone(), buffer.clone());
key.clear();
buffer.clear();
state = State::Key;
}
'\\' => {
if let Some(nch) = chars.peek() {
if *nch == '"' {
buffer.push('"');
chars.next();
continue; // WATCH OUT!
}
}
buffer.push('\\');
}
other => buffer.push(other),
},
}
}
output
}

pub fn load_from_file<P: AsRef<Path>>(path: P) -> io::Result<KeyValuePairs> {
Ok(load_from_string(&fs::read_to_string(path)?))
}
44 changes: 44 additions & 0 deletions src/model.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//! # Key-value pairs
use std::collections::hash_map::{IntoIter, Keys, Values};
use std::collections::HashMap;

#[derive(Debug, Clone, PartialEq)]
pub struct KeyValuePairs {
pub(crate) kv: HashMap<String, String>,
}

impl KeyValuePairs {
pub(crate) fn new() -> Self {
Self { kv: HashMap::new() }
}

pub fn get(&self, key: &str) -> Option<&String> {
self.kv.get(key)
}

pub fn is_empty(&self) -> bool {
self.kv.is_empty()
}

pub fn len(&self) -> usize {
self.kv.len()
}

pub fn keys(&self) -> Keys<'_, String, String> {
self.kv.keys()
}

pub fn values(&self) -> Values<'_, String, String> {
self.kv.values()
}
}

impl IntoIterator for KeyValuePairs {
type Item = (String, String);
type IntoIter = IntoIter<String, String>;

fn into_iter(self) -> Self::IntoIter {
self.kv.into_iter()
}
}
28 changes: 28 additions & 0 deletions tests/loading/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
mod test_load_from_string;

use kivi::load_from_string;
use std::collections::BTreeMap;

fn eq(input: &str, expected: &str) {
let mut items = vec![];
for (k, v) in &load_from_string(input).into_iter().collect::<BTreeMap<String, String>>() {
let mut buffer = String::new();
buffer.push('"');
buffer.push_str(k);
buffer.push('"');
buffer.push(':');
buffer.push(' ');
buffer.push('"');
buffer.push_str(v);
buffer.push('"');
items.push(buffer);
}
let mut actual = "{".to_string();
actual.push_str(&items.join(", "));
actual.push('}');
assert_eq!(actual, expected);
}

fn eqn(input: &str, expected: &str) {
assert_eq!(format!("{:?}", load_from_string(input).into_iter().collect::<BTreeMap<String, String>>()), expected);
}
Loading

0 comments on commit 6c8c067

Please sign in to comment.