Skip to content
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,10 @@ use decasify::{Locale, StyleGuide, StyleOptions};

fn demo() {
let input = "ILIK SU VE İTEN RÜZGARLAR";
let output = titlecase(input, Locale::TR, StyleGuide::LanguageDefault, StyleOptions::default());
let output = titlecase(input, Locale::TR, StyleGuide::LanguageDefault, StyleOptions::default()).unwrap();
eprintln! {"{output}"};
let input = "title with a twist: a colon";
let output = titlecase(input, Locale::EN, StyleGuide::DaringFireball, StyleOptions::default());
let output = titlecase(input, Locale::EN, StyleGuide::DaringFireball, StyleOptions::default()).unwrap();
eprintln! {"{output}"};
}
```
Expand Down
48 changes: 48 additions & 0 deletions spec/decasify_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,15 @@ describe("decasify", function ()
end)
end)

it("should balk at unparsable values", function ()
assert.error(function ()
case("foo", nil, "foo")
end)
assert.error(function ()
case("foo", nil, 1)
end)
end)

it("should not balk at passing all options through", function ()
local text = "foo: a baz"
assert.equal("Foo: A Baz", case(text, "title", "en", "gruber"))
Expand Down Expand Up @@ -89,11 +98,23 @@ describe("decasify", function ()
assert.no.error(function ()
titlecase("foo", "tr")
end)
assert.no.error(function ()
titlecase("foo", "tr")
end)
assert.no.error(function ()
titlecase("foo")
end)
end)

it("should balk at unparsable values", function ()
assert.error(function ()
titlecase("foo", "foo")
end)
assert.error(function ()
titlecase("foo", 1)
end)
end)

it("should cooperate with English style guides", function ()
local text = "foo: a baz"
local cmos = "Foo: a Baz"
Expand Down Expand Up @@ -132,6 +153,15 @@ describe("decasify", function ()
end)
end)

it("should balk at unparsable values", function ()
assert.error(function ()
lowercase("foo", "foo")
end)
assert.error(function ()
lowercase("foo", 1)
end)
end)

it("should default to handling string as English", function ()
local result = lowercase("IBUPROFIN")
assert.equal("ibuprofin", result)
Expand All @@ -153,6 +183,15 @@ describe("decasify", function ()
end)
end)

it("should balk at unparsable values", function ()
assert.error(function ()
uppercase("foo", "foo")
end)
assert.error(function ()
uppercase("foo", 1)
end)
end)

it("should default to handling string as English", function ()
local result = uppercase("ibuprofin")
assert.equal("IBUPROFIN", result)
Expand All @@ -174,6 +213,15 @@ describe("decasify", function ()
end)
end)

it("should balk at unparsable values", function ()
assert.error(function ()
sentencecase("foo", "foo")
end)
assert.error(function ()
sentencecase("foo", 1)
end)
end)

it("should default to handling string as English", function ()
local result = sentencecase("insert BIKE here")
assert.equal("Insert bike here", result)
Expand Down
74 changes: 24 additions & 50 deletions src/bin/decasify.rs
Original file line number Diff line number Diff line change
@@ -1,53 +1,30 @@
// SPDX-FileCopyrightText: © 2023 Caleb Maclennan <caleb@alerque.com>
// SPDX-License-Identifier: LGPL-3.0-only

use decasify::cli::Cli;
use decasify::{lowercase, sentencecase, titlecase, uppercase};
use decasify::{Case, Locale, StyleGuide, StyleOptions, StyleOptionsBuilder};

use snafu::prelude::*;

use clap::CommandFactory;
use std::io;
use std::io::BufRead;

#[derive(Snafu)]
enum Error {
#[snafu(display("Failed to identify input"))]
Input {},

#[snafu(display("Failed to resolve a known locale"))]
Locale {},

#[snafu(display("Failed to resolve a known case"))]
Case {},

#[snafu(display("Failed to resolve a known style guide"))]
StyleGuide {},
}

// Clap CLI errors are reported using the Debug trait, but Snafu sets up the Display trait.
// So we delegate. c.f. https://github.com/shepmaster/snafu/issues/110
impl std::fmt::Debug for Error {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
std::fmt::Display::fmt(self, fmt)
}
}

type Result<T, E = Error> = std::result::Result<T, E>;
use decasify::cli::Cli;
use decasify::types::Result;
use decasify::{lowercase, sentencecase, titlecase, uppercase};
use decasify::{Case, Locale, StyleGuide, StyleOptions, StyleOptionsBuilder};

fn main() -> Result<()> {
let version = option_env!("VERGEN_GIT_DESCRIBE").unwrap_or_else(|| env!("CARGO_PKG_VERSION"));
let app = Cli::command().version(version);
let matches = app.get_matches();
let locale = matches.get_one::<Locale>("locale").context(LocaleSnafu)?;
let locale = matches
.get_one::<Locale>("locale")
.unwrap_or(&Locale::default())
.to_owned();
let case = matches
.get_one::<Case>("case")
.context(CaseSnafu)?
.unwrap_or(&Case::default())
.to_owned();
let style = matches
.get_one::<StyleGuide>("style")
.context(StyleGuideSnafu)?
.unwrap_or(&StyleGuide::default())
.to_owned();
let opts = if let Some(overrides) = matches.get_many::<String>("overrides") {
StyleOptionsBuilder::new()
Expand All @@ -60,27 +37,23 @@ fn main() -> Result<()> {
true => {
let input: Vec<String> = matches
.get_many::<String>("input")
.context(InputSnafu)?
.unwrap()
.cloned()
.collect();
let input: Vec<String> = vec![input.join(" ")];
process(
input.iter().map(|ln| ln.to_string()),
*locale,
locale,
case,
style,
opts,
);
)
}
false => {
let stdin = io::stdin().lock().lines().map(|ln| ln.unwrap());
process(stdin, locale, case, style, opts)
}
false => process(
io::stdin().lock().lines().map(|ln| ln.unwrap()),
*locale,
case,
style,
opts,
),
}
Ok(())
}

fn process<I: IntoIterator<Item = String>>(
Expand All @@ -89,15 +62,16 @@ fn process<I: IntoIterator<Item = String>>(
case: Case,
style: StyleGuide,
opts: StyleOptions,
) {
) -> Result<()> {
for string in strings {
let output = match case {
Case::Title => titlecase(string, locale, style.clone(), opts.clone()),
Case::Lower => lowercase(string, locale),
Case::Upper => uppercase(string, locale),
Case::Sentence => sentencecase(string, locale),
Case::Title => titlecase(string, locale, style.clone(), opts.clone())?,
Case::Lower => lowercase(string, locale)?,
Case::Upper => uppercase(string, locale)?,
Case::Sentence => sentencecase(string, locale)?,
_ => unreachable!(),
};
println!("{output}")
println!("{output}");
}
Ok(())
}
20 changes: 1 addition & 19 deletions src/content.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
// SPDX-FileCopyrightText: © 2023 Caleb Maclennan <caleb@alerque.com>
// SPDX-License-Identifier: LGPL-3.0-only

pub use crate::types::Word;

use regex::Regex;
use std::{borrow::Cow, fmt, fmt::Display, str::FromStr};
use unicode_titlecase::StrTitleCase;

use snafu::prelude::*;
use crate::types::{Error, Result, Word};

#[derive(Clone, Debug)]
#[non_exhaustive]
Expand All @@ -22,22 +20,6 @@ pub enum Segment {
Word(Word),
}

#[derive(Snafu)]
pub enum Error {
#[snafu(display("Unable to cast str to Chunk"))]
StrToChunk {},
}

// Clap CLI errors are reported using the Debug trait, but Snafu sets up the Display trait.
// So we delegate. c.f. https://github.com/shepmaster/snafu/issues/110
impl std::fmt::Debug for Error {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
std::fmt::Display::fmt(self, fmt)
}
}

pub type Result<T, E = Error> = std::result::Result<T, E>;

fn split_chunk(s: &str) -> Chunk {
let mut segments: Vec<Segment> = Vec::new();
let captures = Regex::new(r"(?<separator>\p{Whitespace}+)|(?<word>\P{Whitespace}+)").unwrap();
Expand Down
Loading
Loading