diff --git a/src/functions/public/ConvertTo-CasingStyle.ps1 b/src/functions/public/ConvertTo-CasingStyle.ps1 new file mode 100644 index 0000000..2db5dcc --- /dev/null +++ b/src/functions/public/ConvertTo-CasingStyle.ps1 @@ -0,0 +1,74 @@ +filter ConvertTo-CasingStyle { + <# + .SYNOPSIS + Convert a string to a different casing style + + .DESCRIPTION + This function converts a string to a different casing style. + + .EXAMPLE + 'thisIsCamelCase' | ConvertTo-CasingStyle -To 'snake_case' + + Convert the string 'thisIsCamelCase' to 'this_is_camel_case' + + .EXAMPLE + 'thisIsCamelCase' | ConvertTo-CasingStyle -To 'UPPER_SNAKE_CASE' + + Convert the string 'thisIsCamelCase' to 'THIS_IS_CAMEL_CASE' + + .EXAMPLE + 'thisIsCamelCase' | ConvertTo-CasingStyle -To 'kebab-case' + + .OUTPUTS + [string] - The converted string + + .LINK + https://psmodule.io/Casing/Functions/ConvertTo-CasingStyle/ + #> + [OutputType([string])] + [CmdletBinding()] + param ( + # The string to convert + [Parameter( + Mandatory, + ValueFromPipeline + )] + [string] $Text, + + # The casing style to convert the string to + [Parameter(Mandatory)] + [ValidateSet( + 'lowercase', + 'UPPERCASE', + 'Title Case', + 'Sentencecase', + 'PascalCase', + 'camelCase', + 'kebab-case', + 'UPPER-KEBAB-CASE', + 'snake_case', + 'UPPER_SNAKE_CASE' + )] + [string] $To + ) + + $currentStyle = Get-CasingStyle -Text $Text + + $words = Split-CasingStyle -Text $Text -By $currentStyle + + # Convert the words into the target style + switch ($To) { + 'lowercase' { ($words -join '').toLower() } + 'UPPERCASE' { ($words -join '').toUpper() } + 'Title Case' { ($words | ForEach-Object { $_.Substring(0, 1).ToUpper() + $_.Substring(1).ToLower() }) -join ' ' } + 'Sentencecase' { $words -join '' | ForEach-Object { $_.Substring(0, 1).ToUpper() + $_.Substring(1).ToLower() } } + 'kebab-case' { ($words -join '-').ToLower() } + 'snake_case' { ($words -join '_').ToLower() } + 'PascalCase' { ($words | ForEach-Object { $_.Substring(0, 1).ToUpper() + $_.Substring(1).ToLower() }) -join '' } + 'camelCase' { + $words[0].toLower() + (($words | Select-Object -Skip 1 | ForEach-Object { $_.Substring(0, 1).ToUpper() + $_.Substring(1) }) -join '') + } + 'UPPER_SNAKE_CASE' { ($words -join '_').toUpper() } + 'UPPER-KEBAB-CASE' { ($words -join '-').toUpper() } + } +} diff --git a/src/functions/public/Get-CasingStyle.ps1 b/src/functions/public/Get-CasingStyle.ps1 new file mode 100644 index 0000000..56b574a --- /dev/null +++ b/src/functions/public/Get-CasingStyle.ps1 @@ -0,0 +1,102 @@ +filter Get-CasingStyle { + <# + .SYNOPSIS + Detects the casing style of a string + + .DESCRIPTION + This function detects the casing style of a string. + + .EXAMPLE + 'testtesttest' | Get-CasingStyle + + lowercase + + .EXAMPLE + 'TESTTESTTEST' | Get-CasingStyle + + UPPERCASE + + .EXAMPLE + 'Testtesttest' | Get-CasingStyle + + Sentencecase + + .EXAMPLE + 'TestTestTest' | Get-CasingStyle + + PascalCase + + .EXAMPLE + 'testTestTest' | Get-CasingStyle + + camelCase + + .EXAMPLE + 'test-test-test' | Get-CasingStyle + + kebab-case + + .EXAMPLE + 'TEST-TEST-TEST' | Get-CasingStyle + + UPPER-KEBAB-CASE + + .EXAMPLE + 'test_test_test' | Get-CasingStyle + + snake_case + + .EXAMPLE + 'TEST_TEST_TEST' | Get-CasingStyle + + UPPER_SNAKE_CASE + + .EXAMPLE + 'Test_teSt-Test' | Get-CasingStyle + + Unknown + + .OUTPUTS + [string] - The detected casing style of the input string + + .LINK + https://psmodule.io/Casing/Functions/Get-CasingStyle/ + #> + [OutputType([string])] + [CmdletBinding()] + param ( + # The string to check the casing style of + [Parameter( + Mandatory, + ValueFromPipeline + )] + [string] $Text + ) + + $style = if ([regex]::Match($Text, $script:LowerCase).Success) { + 'lowercase' + } elseif ([regex]::Match($Text, $script:UpperCase).Success) { + 'UPPERCASE' + } elseif ([regex]::Match($Text, $script:SentenceCase).Success) { + 'Sentencecase' + } elseif ([regex]::Match($Text, $script:TitleCase).Success) { + 'Title Case' + } elseif ([regex]::Match($Text, $script:PascalCase).Success) { + 'PascalCase' + } elseif ([regex]::Match($Text, $script:CamelCase).Success) { + 'camelCase' + } elseif ([regex]::Match($Text, $script:KebabCase).Success) { + 'kebab-case' + } elseif ([regex]::Match($Text, $script:UpperKebabCase).Success) { + 'UPPER-KEBAB-CASE' + } elseif ([regex]::Match($Text, $script:SnakeCase).Success) { + 'snake_case' + } elseif ([regex]::Match($Text, $script:UpperSnakeCase).Success) { + 'UPPER_SNAKE_CASE' + } else { + 'Unknown' + } + + Write-Verbose "Detected casing style: [$style]" + $style +} diff --git a/src/functions/public/Set-PSModuleTest.ps1 b/src/functions/public/Set-PSModuleTest.ps1 deleted file mode 100644 index a87ac11..0000000 --- a/src/functions/public/Set-PSModuleTest.ps1 +++ /dev/null @@ -1,22 +0,0 @@ -function Set-PSModuleTest { - <# - .SYNOPSIS - Performs tests on a module. - - .EXAMPLE - Test-PSModule -Name 'World' - - "Hello, World!" - #> - [Diagnostics.CodeAnalysis.SuppressMessageAttribute( - 'PSUseShouldProcessForStateChangingFunctions', '', Scope = 'Function', - Justification = 'Reason for suppressing' - )] - [CmdletBinding()] - param ( - # Name of the person to greet. - [Parameter(Mandatory)] - [string] $Name - ) - Write-Output "Hello, $Name!" -} diff --git a/src/functions/public/Split-CasingStyle.ps1 b/src/functions/public/Split-CasingStyle.ps1 new file mode 100644 index 0000000..15aa5ec --- /dev/null +++ b/src/functions/public/Split-CasingStyle.ps1 @@ -0,0 +1,153 @@ +filter Split-CasingStyle { + <# + .SYNOPSIS + Splits a string based on one or more casing styles. + + .DESCRIPTION + This function takes a string and an array of casing styles (via the -By parameter) + and splits the string into its component words. It does this iteratively, + applying each split to every token produced by the previous one. + + .EXAMPLE + Split-CasingStyle -Text 'this-is-a-kebab-case-string' -By kebab-case + + this + is + a + kebab + case + string + + .EXAMPLE + Split-CasingStyle -Text 'this_is_a_kebab_case_string' -By 'snake_case' + + this + is + a + kebab + case + string + + .EXAMPLE + Split-CasingStyle -Text 'ThisIsAPascalCaseString' -By 'PascalCase' + + This + Is + A + Pascal + Case + String + + .EXAMPLE + Split-CasingStyle -Text 'thisIsACamelCaseString' -By 'camelCase' + + this + Is + A + Camel + Case + String + + .EXAMPLE + Split-CasingStyle -Text 'this_is_a-CamelCaseString' -By kebab-case | Split-CasingStyle -By snake_case + + this_is_a + camelcasestring + + .EXAMPLE + 'this_is_a-PascalString' | Split-CasingStyle -By 'snake_case','kebab-case','PascalCase' + + .OUTPUTS + [string[]] - An array of strings, each representing a word in the original string + + .LINK + https://psmodule.io/Casing/Functions/Split-CasingStyle/ + #> + [CmdletBinding()] + param( + # The string to split + [Parameter( + Mandatory, + ValueFromPipeline + )] + [string]$Text, + + # The casing style(s) to split the string by. + [Parameter()] + [ValidateSet( + 'lowercase', + 'UPPERCASE', + 'Sentencecase', + 'Title Case', + 'PascalCase', + 'camelCase', + 'kebab-case', + 'UPPER-KEBAB-CASE', + 'snake_case', + 'UPPER_SNAKE_CASE' + )] + [string[]]$By + ) + + process { + Write-Verbose "Starting with string: [$Text]" + # Start with the original text as the only token. + $tokens = @($Text) + + # For each casing style in the -By list, split every token accordingly. + foreach ($style in $By) { + Write-Verbose "Splitting by casing style: $style" + $newTokens = @() + foreach ($token in $tokens) { + switch ($style) { + 'PascalCase' { + # Use regex to match sequences like 'Pascal' and 'String' in 'PascalString' + $matchedTokens = [regex]::Matches($token, '([A-Z][a-z]*)') + if ($matchedTokens.Count -gt 0) { + $newTokens += $matchedTokens | ForEach-Object { $_.Value } + } else { + $newTokens += $token + } + break + } + 'camelCase' { + # Match leading lowercase or uppercase letter groups + $matchedTokens = [regex]::Matches($token, '(^[a-z]+|[A-Z][a-z]*)') + if ($matchedTokens.Count -gt 0) { + $newTokens += $matchedTokens | ForEach-Object { $_.Value } + } else { + $newTokens += $token + } + break + } + 'kebab-case' { + $newTokens += $token -split '-' + break + } + 'UPPER-KEBAB-CASE' { + $newTokens += $token -split '-' + break + } + 'snake_case' { + $newTokens += $token -split '_' + break + } + 'UPPER_SNAKE_CASE' { + $newTokens += $token -split '_' + break + } + default { + # For any other case styles, you might split on whitespace + $newTokens += $token -split ' ' + break + } + } + } + # Update tokens with the newly split parts + $tokens = $newTokens + Write-Verbose "Tokens after splitting by $style`: [$($tokens -join ', ')]" + } + Write-Verbose "Final result: [$($tokens -join ', ')]" + $tokens + } +} diff --git a/src/variables/private/Cases.ps1 b/src/variables/private/Cases.ps1 new file mode 100644 index 0000000..690866e --- /dev/null +++ b/src/variables/private/Cases.ps1 @@ -0,0 +1,10 @@ +$script:LowerCase = '^[a-z][a-z0-9]*$' +$script:UpperCase = '^[A-Z][A-Z0-9]*$' +$script:SentenceCase = '^[A-Z][a-z0-9]*$' +$script:TitleCase = '^([A-Z][a-z]*)(\s+[A-Z][a-z]*)+$' +$script:PascalCase = '^[A-Z][a-z0-9]*([A-Z][a-z0-9]*)+$' +$script:CamelCase = '^[a-z][a-z0-9]*([A-Z][a-z0-9]*)+$' +$script:KebabCase = '^[a-z][a-z0-9]*(-[a-z0-9]+)+$' +$script:UpperKebabCase = '^[A-Z][A-Z0-9]*(-[A-Z0-9]+)+$' +$script:SnakeCase = '^[a-z][a-z0-9]*(_[a-z0-9]+)+$' +$script:UpperSnakeCase = '^[A-Z][A-Z0-9]*(_[A-Z0-9]+)+$' diff --git a/tests/Casing.Tests.ps1 b/tests/Casing.Tests.ps1 new file mode 100644 index 0000000..3ca7b8d --- /dev/null +++ b/tests/Casing.Tests.ps1 @@ -0,0 +1,124 @@ +Describe 'Casing' { + Context 'Function: Get-CasingStyle' { + It "Get-CasingStyle: Detects 'testtesttest' as lowercase" { + 'testtesttest' | Get-CasingStyle | Should -Be 'lowercase' + } + + It "Get-CasingStyle: Detects 'TESTTESTTEST' as UPPERCASE" { + 'TESTTESTTEST' | Get-CasingStyle | Should -Be 'UPPERCASE' + } + + It "Get-CasingStyle: Detects 'Testtesttest' as Sentencecase" { + 'Testtesttest' | Get-CasingStyle | Should -Be 'Sentencecase' + } + + It "Get-CasingStyle: Detects 'TestTestTest' as PascalCase" { + 'TestTestTest' | Get-CasingStyle | Should -Be 'PascalCase' + } + + It "Get-CasingStyle: Detects 'testTestTest' as camelCase" { + 'testTestTest' | Get-CasingStyle | Should -Be 'camelCase' + } + + It "Get-CasingStyle: Detects 'test-test-test' as kebab-case" { + 'test-test-test' | Get-CasingStyle | Should -Be 'kebab-case' + } + + It "Get-CasingStyle: Detects 'TEST-TEST-TEST' as UPPER-KEBAB-CASE" { + 'TEST-TEST-TEST' | Get-CasingStyle | Should -Be 'UPPER-KEBAB-CASE' + } + + It "Get-CasingStyle: Detects 'test_test_test' as snake_case" { + 'test_test_test' | Get-CasingStyle | Should -Be 'snake_case' + } + + It "Get-CasingStyle: Detects 'TEST_TEST_TEST' as UPPER_SNAKE_CASE" { + 'TEST_TEST_TEST' | Get-CasingStyle | Should -Be 'UPPER_SNAKE_CASE' + } + + It "Get-CasingStyle: Detects 'Test Test Test' as Title Case" { + 'Test Test Test' | Get-CasingStyle | Should -Be 'Title Case' + } + + It "Get-CasingStyle: Detects 'Test_teSt-Test' as Unknown" { + 'Test_teSt-Test' | Get-CasingStyle | Should -Be 'Unknown' + } + + It "Get-CasingStyle: Detects 'Test-Test_test' as Unknown" { + 'Test-Test_test' | Get-CasingStyle | Should -Be 'Unknown' + } + + It "Get-CasingStyle: Detects 'ThisIsAMultiWordPascalString' as PascalCase" { + 'ThisIsAMultiWordPascalString' | Get-CasingStyle | Should -Be 'PascalCase' + } + + It "Get-CasingStyle: Detects 'T' as UPPERCASE" { + 'T' | Get-CasingStyle | Should -Be 'UPPERCASE' + } + + It "Get-CasingStyle: Detects 'TTTT' UPPERCASE" { + 'TTTT' | Get-CasingStyle | Should -Be 'UPPERCASE' + } + + It "Get-CasingStyle: Detects 't' as lowercase" { + 't' | Get-CasingStyle | Should -Be 'lowercase' + } + + It "Get-CasingStyle: Detects 'tttt' as lowercase" { + 'tttt' | Get-CasingStyle | Should -Be 'lowercase' + } + + It "Get-CasingStyle: Detects 'tT' as camelCase" { + 'tT' | Get-CasingStyle | Should -Be 'camelCase' + } + + It "Get-CasingStyle: Detects 't-t' as kebab-case" { + 't-t' | Get-CasingStyle | Should -Be 'kebab-case' + } + + It "Get-CasingStyle: Detects 'T-T' as UPPER-KEBAB-CASE" { + 'T-T' | Get-CasingStyle | Should -Be 'UPPER-KEBAB-CASE' + } + + It "Get-CasingStyle: Detects 't_t' as snake_case" { + 't_t' | Get-CasingStyle | Should -Be 'snake_case' + } + } + Context 'Function: ConvertTo-CasingStyle' { + It "ConvertTo-CasingStyle: Converts 'thisIsCamelCase' to 'snake_case'" { + 'thisIsCamelCase' | ConvertTo-CasingStyle -To 'snake_case' | Should -Be 'this_is_camel_case' + } + + It "ConvertTo-CasingStyle: Converts 'thisIsCamelCase' to 'THIS_IS_CAMEL_CASE'" { + 'thisIsCamelCase' | ConvertTo-CasingStyle -To 'UPPER_SNAKE_CASE' | Should -Be 'THIS_IS_CAMEL_CASE' + } + + It "ConvertTo-CasingStyle: Converts 'thisIsCamelCase' to 'this-is-camel-case'" { + 'thisIsCamelCase' | ConvertTo-CasingStyle -To 'kebab-case' | Should -Be 'this-is-camel-case' + } + } + Context 'Function: Split-CasingStyle' { + It "Split-CasingStyle: Splits 'this_is_a_kebab_case_string' by 'snake_case'" { + 'this_is_a_kebab_case_string' | Split-CasingStyle -By 'snake_case' | Should -Be @('this', 'is', 'a', 'kebab', 'case', 'string') + } + + It "Split-CasingStyle: Splits 'ThisIsAPascalCaseString' by 'PascalCase'" { + 'ThisIsAPascalCaseString' | Split-CasingStyle -By 'PascalCase' | Should -Be @('This', 'Is', 'A', 'Pascal', 'Case', 'String') + } + + It "Split-CasingStyle: Splits 'thisIsACamelCaseString' by 'camelCase'" { + 'thisIsACamelCaseString' | Split-CasingStyle -By 'camelCase' | Should -Be @('this', 'Is', 'A', 'Camel', 'Case', 'String') + } + + It "Split-CasingStyle: Splits 'this_is_a-CamelCaseString' by kebab-case | Split-CasingStyle -By snake_case" { + 'this_is_a-CamelCaseString' | + Split-CasingStyle -By kebab-case | + Split-CasingStyle -By snake_case, PascalCase | + Should -Be @('this', 'is', 'a', 'Camel', 'Case', 'String') + } + + It "Split-CasingStyle: Splits 'this-is-a-kebab-case-string' by kebab-case" { + 'this-is-a-kebab-case-string' | Split-CasingStyle -By kebab-case | Should -Be @('this', 'is', 'a', 'kebab', 'case', 'string') + } + } +} diff --git a/tests/PSModuleTest.Tests.ps1 b/tests/PSModuleTest.Tests.ps1 deleted file mode 100644 index 4892023..0000000 --- a/tests/PSModuleTest.Tests.ps1 +++ /dev/null @@ -1,5 +0,0 @@ -Describe 'Module' { - It 'Function: Set-PSModuleTest' { - Set-PSModuleTest -Name 'World' | Should -Be 'Hello, World!' - } -} diff --git a/tests/UseCases.Tests.ps1 b/tests/UseCases.Tests.ps1 new file mode 100644 index 0000000..3567f28 --- /dev/null +++ b/tests/UseCases.Tests.ps1 @@ -0,0 +1,80 @@ +#-------------------------------------------------------------------- +# Test Get-CasingStyle: verify that known inputs are detected correctly, +# and that ambiguous strings return 'Unknown' +#-------------------------------------------------------------------- +Describe 'Get-CasingStyle Tests' { + + Context 'When given valid casing strings' { + $testCases = @( + @{ Text = 'testtest' ; Expected = 'lowercase' }, + @{ Text = 'TESTTEST' ; Expected = 'UPPERCASE' }, + @{ Text = 'Testtest' ; Expected = 'Sentencecase' }, + @{ Text = 'Test Test' ; Expected = 'Title Case' }, + @{ Text = 'TestTest' ; Expected = 'PascalCase' }, + @{ Text = 'testTest' ; Expected = 'camelCase' }, + @{ Text = 'test-test' ; Expected = 'kebab-case' }, + @{ Text = 'TEST-TEST' ; Expected = 'UPPER-KEBAB-CASE' }, + @{ Text = 'test_test' ; Expected = 'snake_case' }, + @{ Text = 'TEST_TEST' ; Expected = 'UPPER_SNAKE_CASE' } + @{ Text = 'Test_teSt-Test'; Expected = 'Unknown' } + ) + + It "detects '' as ')'" -ForEach $testCases { + $Text | Get-CasingStyle | Should -Be $Expected + } + } +} + +#-------------------------------------------------------------------- +# Test ConvertTo-CasingStyle: verify that converting a sample string +# (here, in camelCase) to each target style produces the expected output. +# Also verify that conversion fails when the input style is unknown. +#-------------------------------------------------------------------- +Describe 'ConvertTo-CasingStyle Tests' { + + Context 'Converting from camelCase' { + # Our sample input is in camelCase. + $testCases = @( + @{ Name = 'lowercase' ; Text = 'thisIsCamelCase' ; Expected = 'thisiscamelcase' } + @{ Name = 'UPPERCASE' ; Text = 'thisIsCamelCase' ; Expected = 'THISISCAMELCASE' } + @{ Name = 'Sentencecase' ; Text = 'thisIsCamelCase' ; Expected = 'Thisiscamelcase' } + @{ Name = 'Title Case' ; Text = 'thisIsCamelCase' ; Expected = 'This Is Camel Case' } + @{ Name = 'PascalCase' ; Text = 'thisIsCamelCase' ; Expected = 'ThisIsCamelCase' } + @{ Name = 'camelCase' ; Text = 'thisIsCamelCase' ; Expected = 'thisIsCamelCase' } + @{ Name = 'kebab-case' ; Text = 'thisIsCamelCase' ; Expected = 'this-is-camel-case' } + @{ Name = 'UPPER-KEBAB-CASE' ; Text = 'thisIsCamelCase' ; Expected = 'THIS-IS-CAMEL-CASE' } + @{ Name = 'snake_case' ; Text = 'thisIsCamelCase' ; Expected = 'this_is_camel_case' } + @{ Name = 'UPPER_SNAKE_CASE' ; Text = 'thisIsCamelCase' ; Expected = 'THIS_IS_CAMEL_CASE' } + ) + + It "converts '' to '' using ''" -ForEach $testCases { + $result = $Text | ConvertTo-CasingStyle -To $Name + $result | Should -Be $Expected + } + } + + Context 'When the input casing cannot be determined' { + It "throws an error because the ValidateSet in Split-CasingStyle will not accept 'Unknown'" { + { 'Test_teSt-Test' | ConvertTo-CasingStyle -To 'snake_case' } | Should -Throw + } + } +} + +#-------------------------------------------------------------------- +# Test Split-CasingStyle: verify that the function correctly splits +# strings according to the provided casing style(s) +#-------------------------------------------------------------------- +Describe 'Split-CasingStyle Tests' { + + $testCases = @( + @{ Text = 'this-is-a-kebab-case-string'; SplitBy = 'kebab-case' ; Expected = 'this', 'is', 'a', 'kebab', 'case', 'string' } + @{ Text = 'this_is_a_snake_case_string' ; SplitBy = 'snake_case' ; Expected = 'this', 'is', 'a', 'snake', 'case', 'string' } + @{ Text = 'ThisIsAPascalCaseString' ; SplitBy = 'PascalCase' ; Expected = 'This', 'Is', 'A', 'Pascal', 'Case', 'String' } + @{ Text = 'thisIsACamelCaseString' ; SplitBy = 'camelCase' ; Expected = 'this', 'Is', 'A', 'Camel', 'Case', 'String' } + @{ Text = 'this_is_a-PascalString' ; SplitBy = 'snake_case', 'kebab-case', 'PascalCase' ; Expected = 'this', 'is', 'a', 'Pascal', 'String' } + ) + + It 'splits using into individual words' -ForEach $testCases { + $Text | Split-CasingStyle -By $SplitBy | Should -Be $expected + } +}