added various case conversion commands for str. Added the inflection … (#2363)

* added various case conversion commands for str. Added the inflection crate as a dependency

* lighten the restriction on the inflector dependency

* publishing the case commands

* fix typo

* fix kebab case test

* formatting
This commit is contained in:
Rick Richardson 2020-08-17 13:18:23 -07:00 committed by GitHub
parent a224cd38ab
commit f6ff6ab6e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 484 additions and 2 deletions

View File

@ -97,6 +97,7 @@ quick-xml = "0.18.1"
rayon = "1.3.1"
trash = {version = "1.0.1", optional = true}
url = {version = "2.1.1"}
Inflector = "0.11"
[target.'cfg(unix)'.dependencies]
users = "0.10.0"

View File

@ -320,6 +320,11 @@ pub fn create_default_context(
whole_stream_command(StrCollect),
whole_stream_command(StrLength),
whole_stream_command(StrReverse),
whole_stream_command(StrCamelCase),
whole_stream_command(StrPascalCase),
whole_stream_command(StrKebabCase),
whole_stream_command(StrSnakeCase),
whole_stream_command(StrScreamingSnakeCase),
whole_stream_command(BuildString),
whole_stream_command(Ansi),
whole_stream_command(Char),

View File

@ -231,8 +231,9 @@ pub(crate) use sort_by::SortBy;
pub(crate) use split::{Split, SplitChars, SplitColumn, SplitRow};
pub(crate) use split_by::SplitBy;
pub(crate) use str_::{
Str, StrCapitalize, StrCollect, StrContains, StrDowncase, StrEndsWith, StrFindReplace, StrFrom,
StrIndexOf, StrLength, StrReverse, StrSet, StrStartsWith, StrSubstring, StrToDatetime,
Str, StrCamelCase, StrCapitalize, StrCollect, StrContains, StrDowncase, StrEndsWith,
StrFindReplace, StrFrom, StrIndexOf, StrKebabCase, StrLength, StrPascalCase, StrReverse,
StrScreamingSnakeCase, StrSet, StrSnakeCase, StrStartsWith, StrSubstring, StrToDatetime,
StrToDecimal, StrToInteger, StrTrim, StrTrimLeft, StrTrimRight, StrUpcase,
};
pub(crate) use table::Table;

View File

@ -0,0 +1,74 @@
use super::operate;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use inflector::cases::camelcase::to_camel_case;
use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape, Value};
pub struct SubCommand;
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"str camel-case"
}
fn signature(&self) -> Signature {
Signature::build("str camel-case").rest(
SyntaxShape::ColumnPath,
"optionally convert text to camelCase by column paths",
)
}
fn usage(&self) -> &str {
"converts a string to camelCase"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
operate(args, registry, &to_camel_case).await
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "convert a string to camelCase",
example: "echo 'NuShell' | str camel-case",
result: Some(vec![Value::from("nuShell")]),
}]
}
}
#[cfg(test)]
mod tests {
use super::{to_camel_case, SubCommand};
use crate::commands::str_::case::action;
use nu_plugin::test_helpers::value::string;
use nu_source::Tag;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(SubCommand {})
}
#[test]
fn camel_case_from_kebab() {
let word = string("this-is-the-first-case");
let expected = string("thisIsTheFirstCase");
let actual = action(&word, Tag::unknown(), &to_camel_case).unwrap();
assert_eq!(actual, expected);
}
#[test]
fn camel_case_from_snake() {
let word = string("this_is_the_second_case");
let expected = string("thisIsTheSecondCase");
let actual = action(&word, Tag::unknown(), &to_camel_case).unwrap();
assert_eq!(actual, expected);
}
}

View File

@ -0,0 +1,74 @@
use super::operate;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use inflector::cases::kebabcase::to_kebab_case;
use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape, Value};
pub struct SubCommand;
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"str kebab-case"
}
fn signature(&self) -> Signature {
Signature::build("str kebab-case").rest(
SyntaxShape::ColumnPath,
"optionally convert text to kebab-case by column paths",
)
}
fn usage(&self) -> &str {
"converts a string to kebab-case"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
operate(args, registry, &to_kebab_case).await
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "convert a string to kebab-case",
example: "echo 'NuShell' | str kebab-case",
result: Some(vec![Value::from("nu-shell")]),
}]
}
}
#[cfg(test)]
mod tests {
use super::{to_kebab_case, SubCommand};
use crate::commands::str_::case::action;
use nu_plugin::test_helpers::value::string;
use nu_source::Tag;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(SubCommand {})
}
#[test]
fn kebab_case_from_camel() {
let word = string("thisIsTheFirstCase");
let expected = string("this-is-the-first-case");
let actual = action(&word, Tag::unknown(), &to_kebab_case).unwrap();
assert_eq!(actual, expected);
}
#[test]
fn kebab_case_from_screaming_snake() {
let word = string("THIS_IS_THE_SECOND_CASE");
let expected = string("this-is-the-second-case");
let actual = action(&word, Tag::unknown(), &to_kebab_case).unwrap();
assert_eq!(actual, expected);
}
}

View File

@ -0,0 +1,79 @@
pub mod camel_case;
pub mod kebab_case;
pub mod pascal_case;
pub mod screaming_snake_case;
pub mod snake_case;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::ShellTypeName;
use nu_protocol::{ColumnPath, Primitive, ReturnSuccess, UntaggedValue, Value};
use nu_source::Tag;
use nu_value_ext::ValueExt;
pub use camel_case::SubCommand as CamelCase;
pub use pascal_case::SubCommand as PascalCase;
pub use screaming_snake_case::SubCommand as ScreamingSnakeCase;
pub use snake_case::SubCommand as SnakeCase;
#[derive(Deserialize)]
struct Arguments {
rest: Vec<ColumnPath>,
}
pub async fn operate<F>(
args: CommandArgs,
registry: &CommandRegistry,
case_operation: &'static F,
) -> Result<OutputStream, ShellError>
where
F: Fn(&str) -> String + Send + Sync + 'static,
{
let registry = registry.clone();
let (Arguments { rest }, input) = args.process(&registry).await?;
let column_paths: Vec<_> = rest;
Ok(input
.map(move |v| {
if column_paths.is_empty() {
ReturnSuccess::value(action(&v, v.tag(), &case_operation)?)
} else {
let mut ret = v;
for path in &column_paths {
ret = ret.swap_data_by_column_path(
path,
Box::new(move |old| action(old, old.tag(), &case_operation)),
)?;
}
ReturnSuccess::value(ret)
}
})
.to_output_stream())
}
pub fn action<F>(
input: &Value,
tag: impl Into<Tag>,
case_operation: &F,
) -> Result<Value, ShellError>
where
F: Fn(&str) -> String + Send + Sync + 'static,
{
match &input.value {
UntaggedValue::Primitive(Primitive::Line(s))
| UntaggedValue::Primitive(Primitive::String(s)) => {
Ok(UntaggedValue::string(case_operation(s)).into_value(tag))
}
other => {
let got = format!("got {}", other.type_name());
Err(ShellError::labeled_error(
"value is not string",
got,
tag.into().span,
))
}
}
}

View File

@ -0,0 +1,74 @@
use super::operate;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use inflector::cases::pascalcase::to_pascal_case;
use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape, Value};
pub struct SubCommand;
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"str pascal-case"
}
fn signature(&self) -> Signature {
Signature::build("str pascal-case").rest(
SyntaxShape::ColumnPath,
"optionally convert text to PascalCase by column paths",
)
}
fn usage(&self) -> &str {
"converts a string to PascalCase"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
operate(args, registry, &to_pascal_case).await
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "convert a string to PascalCase",
example: "echo 'nu-shell' | str pascal-case",
result: Some(vec![Value::from("NuShell")]),
}]
}
}
#[cfg(test)]
mod tests {
use super::{to_pascal_case, SubCommand};
use crate::commands::str_::case::action;
use nu_plugin::test_helpers::value::string;
use nu_source::Tag;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(SubCommand {})
}
#[test]
fn pascal_case_from_kebab() {
let word = string("this-is-the-first-case");
let expected = string("ThisIsTheFirstCase");
let actual = action(&word, Tag::unknown(), &to_pascal_case).unwrap();
assert_eq!(actual, expected);
}
#[test]
fn pascal_case_from_snake() {
let word = string("this_is_the_second_case");
let expected = string("ThisIsTheSecondCase");
let actual = action(&word, Tag::unknown(), &to_pascal_case).unwrap();
assert_eq!(actual, expected);
}
}

View File

@ -0,0 +1,74 @@
use super::operate;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use inflector::cases::screamingsnakecase::to_screaming_snake_case;
use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape, Value};
pub struct SubCommand;
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"str screaming-snake-case"
}
fn signature(&self) -> Signature {
Signature::build("str screaming-snake-case").rest(
SyntaxShape::ColumnPath,
"optionally convert text to SCREAMING_SNAKE_CASE by column paths",
)
}
fn usage(&self) -> &str {
"converts a string to SCREAMING_SNAKE_CASE"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
operate(args, registry, &to_screaming_snake_case).await
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "convert a string to SCREAMING_SNAKE_CASE",
example: "echo 'NuShell' | str screaming-snake-case",
result: Some(vec![Value::from("NU_SHELL")]),
}]
}
}
#[cfg(test)]
mod tests {
use super::{to_screaming_snake_case, SubCommand};
use crate::commands::str_::case::action;
use nu_plugin::test_helpers::value::string;
use nu_source::Tag;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(SubCommand {})
}
#[test]
fn snake_case_from_kebab() {
let word = string("this-is-the-first-case");
let expected = string("THIS_IS_THE_FIRST_CASE");
let actual = action(&word, Tag::unknown(), &to_screaming_snake_case).unwrap();
assert_eq!(actual, expected);
}
#[test]
fn snake_case_from_snake() {
let word = string("this_is_the_second_case");
let expected = string("THIS_IS_THE_SECOND_CASE");
let actual = action(&word, Tag::unknown(), &to_screaming_snake_case).unwrap();
assert_eq!(actual, expected);
}
}

View File

@ -0,0 +1,74 @@
use super::operate;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use inflector::cases::snakecase::to_snake_case;
use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape, Value};
pub struct SubCommand;
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"str snake-case"
}
fn signature(&self) -> Signature {
Signature::build("str snake-case").rest(
SyntaxShape::ColumnPath,
"optionally convert text to snake_case by column paths",
)
}
fn usage(&self) -> &str {
"converts a string to snake_case"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
operate(args, registry, &to_snake_case).await
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "convert a string to snake_case",
example: "echo 'NuShell' | str snake-case",
result: Some(vec![Value::from("nu_shell")]),
}]
}
}
#[cfg(test)]
mod tests {
use super::{to_snake_case, SubCommand};
use crate::commands::str_::case::action;
use nu_plugin::test_helpers::value::string;
use nu_source::Tag;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(SubCommand {})
}
#[test]
fn snake_case_from_kebab() {
let word = string("this-is-the-first-case");
let expected = string("this_is_the_first_case");
let actual = action(&word, Tag::unknown(), &to_snake_case).unwrap();
assert_eq!(actual, expected);
}
#[test]
fn snake_case_from_camel() {
let word = string("thisIsTheSecondCase");
let expected = string("this_is_the_second_case");
let actual = action(&word, Tag::unknown(), &to_snake_case).unwrap();
assert_eq!(actual, expected);
}
}

View File

@ -1,4 +1,5 @@
mod capitalize;
mod case;
mod collect;
mod command;
mod contains;
@ -19,6 +20,11 @@ mod trim;
mod upcase;
pub use capitalize::SubCommand as StrCapitalize;
pub use case::camel_case::SubCommand as StrCamelCase;
pub use case::kebab_case::SubCommand as StrKebabCase;
pub use case::pascal_case::SubCommand as StrPascalCase;
pub use case::screaming_snake_case::SubCommand as StrScreamingSnakeCase;
pub use case::snake_case::SubCommand as StrSnakeCase;
pub use collect::SubCommand as StrCollect;
pub use command::Command as Str;
pub use contains::SubCommand as StrContains;

View File

@ -97,6 +97,26 @@ fn upcases() {
})
}
#[test]
fn camelcases() {
Playground::setup("str_test_3", |dirs, sandbox| {
sandbox.with_files(vec![FileWithContent(
"sample.toml",
r#"
[dependency]
name = "THIS_IS_A_TEST"
"#,
)]);
let actual = nu!(
cwd: dirs.test(),
"open sample.toml | str camel-case dependency.name | get dependency.name | echo $it"
);
assert_eq!(actual.out, "thisIsATest");
})
}
#[test]
fn converts_to_int() {
let actual = nu!(