strings-case 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 5985a29d7cd211560f31949ea90cacd559ec100cc993c12cd2d5c2a6c0fdb683
4
+ data.tar.gz: 9e542cd6473627f1f5bad0b05ec47a0e8b667bcbf00377cac5a918da13524fad
5
+ SHA512:
6
+ metadata.gz: 9e73e4189c6af4a9458022af07b20a8863e9fb344dadd223a1e86ba16c44a2a798c7d8fd5f663b556e89f7c8577d060791eb05c59bc967c9d9e07faa39d07ac6
7
+ data.tar.gz: 7052e5e1fffd616a6c00fb92cb2869586a21232d062f1625c09503c79b396a21e645e432d63a648f479465ac99b25f25dfc83b1284009bff79a230d662ee7711
data/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # Change log
2
+
3
+ ## [v0.1.0] - 2019-11-11
4
+
5
+ * Initial implementation and release
6
+
7
+ [v0.1.0]: https://github.com/piotrmurach/strings-case/compare/v0.1.0
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2019 Piotr Murach
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,317 @@
1
+ <div align="center">
2
+ <img width="225" src="https://github.com/piotrmurach/strings/blob/master/assets/strings_logo.png" alt="strings logo" />
3
+ </div>
4
+
5
+ # Strings::Case
6
+
7
+ [![Gem Version](https://badge.fury.io/rb/strings-case.svg)][gem]
8
+ [![Build Status](https://secure.travis-ci.org/piotrmurach/strings-case.svg?branch=master)][travis]
9
+ [![Build status](https://ci.appveyor.com/api/projects/status/yr87c96wxp1cw2ep?svg=true)][appveyor]
10
+ [![Maintainability](https://api.codeclimate.com/v1/badges/7938258c4af196a19843/maintainability)][codeclimate]
11
+ [![Coverage Status](https://coveralls.io/repos/github/piotrmurach/strings-case/badge.svg?branch=master)][coverage]
12
+ [![Inline docs](http://inch-ci.org/github/piotrmurach/strings-case.svg?branch=master)][inchpages]
13
+
14
+ [gem]: http://badge.fury.io/rb/strings-case
15
+ [travis]: http://travis-ci.org/piotrmurach/strings-case
16
+ [appveyor]: https://ci.appveyor.com/project/piotrmurach/strings-case
17
+ [codeclimate]: https://codeclimate.com/github/piotrmurach/strings-case/maintainability
18
+ [coverage]: https://coveralls.io/github/piotrmurach/strings-case?branch=master
19
+ [inchpages]: http://inch-ci.org/github/piotrmurach/strings-case
20
+
21
+ > Convert strings to different cases.
22
+
23
+ **Strings::Case** provides string case conversions for [Strings](https://github.com/piotrmurach/strings) utilities.
24
+
25
+ ## Motivation
26
+
27
+ Popular solutions that deal with transforming string cases work well in simple cases.(Sorry ;-) With more complex strings you may get unexpected results:
28
+
29
+ ```ruby
30
+ ActiveSupport::Inflector.underscore("supports IPv6 on iOS?")
31
+ # => "supports i_pv6 on i_os?"
32
+ ```
33
+
34
+ In contrast, `Strings::Case` aims to be able to transform any string to expected case:
35
+
36
+ ```ruby
37
+ Strings::Case.underscore("supports IPv6 on iOS?")
38
+ # => "supports_ipv6_on_ios"
39
+ ```
40
+
41
+ ## Installation
42
+
43
+ Add this line to your application's Gemfile:
44
+
45
+ ```ruby
46
+ gem 'strings-case'
47
+ ```
48
+
49
+ And then execute:
50
+
51
+ $ bundle
52
+
53
+ Or install it yourself as:
54
+
55
+ $ gem install strings-case
56
+
57
+
58
+ ## Features
59
+
60
+ * No monkey-patching String class
61
+ * Converts any string to specified case
62
+ * Supports Unicode characters
63
+ * Provides many common case transformations
64
+ * Allows to preserve casing of acronyms
65
+
66
+ ## Contents
67
+
68
+ * [1. Usage](#1-usage)
69
+ * [2. API](#2-api)
70
+ * [2.1 camelcase](#21-camelcase)
71
+ * [2.2 constcase](#22-constcase)
72
+ * [2.3 headercase](#23-headercase)
73
+ * [2.4 kebabcase | dashcase](#24-kebabcase--dashcase)
74
+ * [2.5 pascalcase](#25-pascalcase)
75
+ * [2.6 pathcase](#26-pathcase)
76
+ * [2.7 sentencecase](#27-sentencecase)
77
+ * [2.8 snakecase | underscore](#28-snakecase--underscore)
78
+ * [2.9 titlecase](#29-titlecase)
79
+ * [3. Extending String class](#3-extending-string-class)
80
+
81
+ ## 1. Usage
82
+
83
+ The `Strings::Case` is a module with functions for transforming between string cases:
84
+
85
+ ```ruby
86
+ Strings::Case.snakecase("foo bar baz")
87
+ # => "foo_bar_baz"
88
+ ````
89
+
90
+ It will transform any string into expected case:
91
+
92
+ ```ruby
93
+ Strings::Case.snake_case("supports IPv6 on iOS?")
94
+ # => "supports_ipv6_on_ios"
95
+ ```
96
+
97
+ You can apply case transformations to Unicode characters:
98
+
99
+ ```ruby
100
+ Strings::Case.snake_case("ЗдравствуйтеПривет")
101
+ # => "здравствуйте_привет"
102
+ ```
103
+
104
+ Here is a quick summary of available transformations:
105
+
106
+ | Case Type | Result |
107
+ | --------- | ------- |
108
+ | ```Strings::Case.camelcase("foo bar baz")``` | `"fooBarBaz"` |
109
+ | ```Strings::Case.constcase("foo bar baz")``` | `"FOO_BAR_BAZ"` |
110
+ | ```Strings::Case.headercase("foo bar baz")``` | `"Foo-Bar-Baz"` |
111
+ | ```Strings::Case.kebabcase("foo bar baz")``` | `"foo-bar-baz"` |
112
+ | ```Strings::Case.pascalcase("foo bar baz")``` | `"FooBarBaz"` |
113
+ | ```Strings::Case.pathcase("foo bar baz")``` | `"foo/bar/baz"` |
114
+ | ```Strings::Case.sentencecase("foo bar baz")``` | `"Foo bar baz"` |
115
+ | ```Strings::Case.snakecase("foo bar baz")``` | `"foo_bar_baz"` |
116
+ | ```Strings::Case.titlecase("foo bar baz")``` | `"Foo Bar Baz"` |
117
+
118
+ ## 2. API
119
+
120
+ ### 2.1 camelcase
121
+
122
+ To convert a string into a camel case, that is, a case with all the words capitilized apart from the first one and compouned together without any space use `camelase` method. For example:
123
+
124
+ ```ruby
125
+ Strings::Case.camelcase("HTTP Response Code")
126
+ # => "httpResponseCode"
127
+ ```
128
+
129
+ To preserve the acronyms use the `:acronyms` option:
130
+
131
+ ```ruby
132
+ Strings::Case.camelcase("HTTP Response Code", acronyms: ["HTTP"])
133
+ # => "HTTPResponseCode"
134
+ ```
135
+
136
+ ### 2.2 constcase
137
+
138
+ To convert a string into a constant case, that is, a case with all the words uppercased and separated by underscore character use `constcase`. For example:
139
+
140
+ ```ruby
141
+ Strings::Case.constcase("HTTP Response Code")
142
+ # => "HTTP_RESPONSE_CODE"
143
+ ```
144
+
145
+ ### 2.3 headercase
146
+
147
+ To covert a string into a header case, that is, a case with all the words capitalized and separated by a hypen use `headercase`. For example:
148
+
149
+ ```ruby
150
+ Strings::Case.headercase("HTTP Response Code")
151
+ # => "Http-Response-Code"
152
+ ```
153
+
154
+ To preserve the acronyms use the `:acronyms` option:
155
+
156
+ ```ruby
157
+ Strings::Case.headercase("HTTP Response Code", acronyms: ["HTTP"])
158
+ # => "HTTP-Response-Code"
159
+ ```
160
+ ### 2.4 kebabcase | dashcase
161
+
162
+ To convert a string into a kebab case, that is, a case with all the words lowercased and separted by a dash, like a words kebabab on a skewer, use `kebabcase` or `dashcase` methods. For example:
163
+
164
+ ```ruby
165
+ Strings::Case.kebabcase("HTTP Response Code")
166
+ # => "http-response-code"
167
+ ```
168
+
169
+ To preserve the acronyms use the `:acronyms` option:
170
+
171
+ ```ruby
172
+ Strings::Case.dashcase("HTTP Response Code", acronyms: ["HTTP"])
173
+
174
+ expect(dashed).to eq("HTTP-response-code")
175
+ ```
176
+
177
+ ### 2.5 pascalcase
178
+
179
+ To convert a string into a pascal case, that is, a case with all the words capitilized and compounded together without a space, use `pascalcase` method. For example:
180
+
181
+ ```ruby
182
+ Strings::Case.pascalcase("HTTP Response Code")
183
+ # => "HttpResponseCode"
184
+ ```
185
+
186
+ To preserve the acronyms use the `:acronyms` option:
187
+
188
+ ```ruby
189
+ Strings::Case.pascalcase("HTTP Response Code")
190
+ # => "HTTPResponseCode"
191
+ ```
192
+
193
+ ### 2.6 pathcase
194
+
195
+ To convert a string into a file path use `pathcase`:
196
+
197
+ ```ruby
198
+ Strings::Case.pathcase("HTTP Response Code")
199
+ # => "http/response/code"
200
+ ````
201
+
202
+ To preserve the acronyms use the `:acronyms` option:
203
+
204
+ ```ruby
205
+ Strings::Case.pathcase("HTTP Response Code", acronyms: ["HTTP"])
206
+ # => "HTTP/response/code"
207
+ ```
208
+
209
+ By default the `/` is used as a path separator. To change this use a `:sep` option. For example, on Windows the file path separator is `\`:
210
+
211
+ ```ruby
212
+ Strings::Case.pathcase("HTTP Response Code", separator: "\\")
213
+ # => "http\response\code"
214
+ ```
215
+
216
+ ### 2.7 `sentencecase`
217
+
218
+ To turn a string into a sentence use `sentencecase`:
219
+
220
+ ```ruby
221
+ Strings::Case.sentencecase("HTTP Response Code")
222
+ # => "Http response code"
223
+ ```
224
+
225
+ To preserve the `HTTP` acronym use the `:acronyms` option:
226
+
227
+ ```ruby
228
+ Strings::Case.sentencecase("HTTP Response Code", acronyms: ["HTTP"])
229
+ # => "HTTP response code"
230
+ ```
231
+
232
+ ### 2.8 `snakecase` | `underscore`
233
+
234
+ To convert a string into a snake case by lowercasing all the characters and separating them with an `_` use `snakecase` or `underscore` methods. For example:
235
+
236
+ ```ruby
237
+ Strings::Case.snakecase("HTTP Response Code")
238
+ # => "http_response_code"
239
+ ```
240
+
241
+ To preserve acronyms in your string use the `:acronyms` option. For example:
242
+
243
+ ```ruby
244
+ Strings::Case.snakecase("HTTP Response Code", acronyms: ["HTTP"])
245
+ # => "HTTP_response_code"
246
+ ```
247
+
248
+ ### 2.9 `titlecase`
249
+
250
+ To convert a string into a space delimited words that have their first letter capitalized use `titlecase`. For example:
251
+
252
+ ```ruby
253
+ Strings::Case.titlecase("HTTPResponseCode")
254
+ # => "Http Response Code"
255
+ ```
256
+
257
+ To preserve the `HTTP` acronym use the `:acronyms` option:
258
+
259
+ ```ruby
260
+ Strings::Case.titlecase("HTTP response code", acronyms: ["HTTP"])
261
+ # => "HTTP Response Code"
262
+ ```
263
+
264
+ ## 3. Extending String class
265
+
266
+ Though it is highly discouraged to pollute core Ruby classes, you can add the required methods to `String` class by using refinements.
267
+
268
+ For example, if you wish to only extend strings with `wrap` method do:
269
+
270
+ ```ruby
271
+ module MyStringExt
272
+ refine String do
273
+ def snakecase(*args)
274
+ Strings::Case.snakecase(self, *args)
275
+ end
276
+ end
277
+ end
278
+ ```
279
+
280
+ Then `snakecase` method will be available for any strings where refinement is applied:
281
+
282
+ ```ruby
283
+ using MyStringExt
284
+
285
+ "foo bar baz".snakecase
286
+ # => "foo_bar_baz"
287
+ ```
288
+
289
+ However, if you want to include all the **Strings::Case** methods, you can use provided extensions file:
290
+
291
+ ```ruby
292
+ require "strings/case/extensions"
293
+
294
+ using Strings::Case::Extensions
295
+ ```
296
+
297
+ ## Development
298
+
299
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
300
+
301
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
302
+
303
+ ## Contributing
304
+
305
+ Bug reports and pull requests are welcome on GitHub at https://github.com/piotrmurach/strings-case. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
306
+
307
+ ## License
308
+
309
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
310
+
311
+ ## Code of Conduct
312
+
313
+ Everyone interacting in the Strings::Case project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/piotrmurach/strings-case/blob/master/CODE_OF_CONDUCT.md).
314
+
315
+ ## Copyright
316
+
317
+ Copyright (c) 2019 Piotr Murach. See LICENSE for further details.
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ FileList["tasks/**/*.rake"].each(&method(:import))
4
+
5
+ desc "Run all specs"
6
+ task ci: %w[ spec ]
7
+
8
+ task default: :spec
@@ -0,0 +1 @@
1
+ require_relative "strings/case"
@@ -0,0 +1,304 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "case/version"
4
+
5
+ module Strings
6
+ module Case
7
+ DIGITS = ("0".."9").freeze
8
+ UP_LETTERS = ("A".."Z").freeze
9
+ DOWN_LETTERS = ("a".."z").freeze
10
+ DELIMITERS = [" ", "\n", "\t", "_", ".", "-", "#", "?", "!"].freeze
11
+ NONALPHANUMERIC = (32..127).map(&:chr) -
12
+ (DIGITS.to_a + DOWN_LETTERS.to_a + UP_LETTERS.to_a + DELIMITERS)
13
+ UPCASE = /(?<!\p{Lu})\p{Lu}$/.freeze
14
+ LOWERCASE = /\p{Lu}(?=\p{Ll})/.freeze
15
+
16
+ class Error < StandardError; end
17
+
18
+ # Prevent changing case
19
+ module NullCase
20
+ def downcase
21
+ self
22
+ end
23
+ alias upcase downcase
24
+ alias capitalize downcase
25
+ end
26
+
27
+ # Convert string to camel case:
28
+ # * start with a lowercase character
29
+ # * every subsequent word has its first character uppercased
30
+ # * all words are compounded together
31
+ #
32
+ # @example
33
+ # camelcase("foo bar baz") # => "fooBarBaz"
34
+ #
35
+ # @param [String] string
36
+ # the string to camelcase
37
+ # @param [Array[String]] acronyms
38
+ # the acronyms to use to prevent modifications
39
+ # @param [String] separator
40
+ # the separator for linking words, by default none
41
+ #
42
+ # @api public
43
+ def camelcase(string, acronyms: [], separator: "")
44
+ res = parsecase(string, acronyms: acronyms, sep: separator, casing: :capitalize)
45
+
46
+ return res if res.to_s.empty?
47
+
48
+ acronyms_regex = /^(#{acronyms.join("|")})/
49
+ if !acronyms.empty? && (res =~ acronyms_regex)
50
+ res
51
+ else
52
+ res[0].downcase + res[1..-1]
53
+ end
54
+ end
55
+ module_function :camelcase
56
+
57
+ alias lower_camelcase camelcase
58
+ module_function :lower_camelcase
59
+
60
+ # Converts string to a constant
61
+ #
62
+ # @example
63
+ # constantcase("foo bar baz") # => "FOO_BAR_BAZ"
64
+ #
65
+ # @param [String] string
66
+ # the string to turn into constant
67
+ # @param [Array[String]] acronyms
68
+ # the acronyms to use to prevent modifications
69
+ # @param [String] separator
70
+ # the words separator, by default "_"
71
+ #
72
+ # @api public
73
+ def constcase(string, separator: "_")
74
+ parsecase(string, sep: separator, casing: :upcase)
75
+ end
76
+ module_function :constcase
77
+
78
+ alias constantcase constcase
79
+ module_function :constantcase
80
+
81
+ # Convert string to a HTTP Header
82
+ #
83
+ # @example
84
+ # headercase("foo bar baz") # = "Foo-Bar-Baz"
85
+ #
86
+ # @param [String] string
87
+ # the string to turn into header
88
+ # @param [Array[String]] acronyms
89
+ # the acronyms to use to prevent modifications
90
+ # @param [String] separator
91
+ # the words separator, by default "-"
92
+ #
93
+ # @api public
94
+ def headercase(string, acronyms: [], separator: "-")
95
+ parsecase(string, acronyms: acronyms, sep: separator, casing: :capitalize)
96
+ end
97
+ module_function :headercase
98
+
99
+ # Converts string to lower case words linked by hyphenes
100
+ #
101
+ # @example
102
+ # kebabcase("fooBarBaz") # => "foo-bar-baz"
103
+ #
104
+ # kebabcase("__FOO_BAR__") # => "foo-bar"
105
+ #
106
+ # @param [String] string
107
+ # the string to convert to dashed string
108
+ # @param [Array[String]] acronyms
109
+ # the acronyms to use to prevent modifications
110
+ # @param [String] separator
111
+ # the separator for linking words, by default hyphen
112
+ #
113
+ # @return [String]
114
+ #
115
+ # @api public
116
+ def kebabcase(string, acronyms: [], separator: "-")
117
+ parsecase(string, acronyms: acronyms, sep: separator)
118
+ end
119
+ module_function :kebabcase
120
+
121
+ alias dashcase kebabcase
122
+ module_function :dashcase
123
+
124
+ # Convert string to pascal case:
125
+ # * every word has its first character uppercased
126
+ # * all words are compounded together
127
+ #
128
+ # @example
129
+ # pascalcase("foo bar baz") # => "FooBarBaz"
130
+ #
131
+ # @param [String] string
132
+ # the string to convert to camel case with capital letter
133
+ # @param [Array[String]] acronyms
134
+ # the acronyms to use to prevent modifications
135
+ # @param [String] separator
136
+ # the separator for linking words, by default none
137
+ #
138
+ # @api public
139
+ def pascalcase(string, acronyms: [], separator: "")
140
+ parsecase(string, acronyms: acronyms, sep: separator, casing: :capitalize)
141
+ end
142
+ module_function :pascalcase
143
+
144
+ alias upper_camelcase pascalcase
145
+ module_function :upper_camelcase
146
+
147
+ # Convert string into a file path.
148
+ #
149
+ # By default uses `/` as a path separator.
150
+ #
151
+ # @example
152
+ # pathcase("foo bar baz") # => "foo/bar/baz"
153
+ #
154
+ # pathcase("FooBarBaz") # => "foo/bar/baz"
155
+ #
156
+ # @param [String] string
157
+ # the string to convert to file path
158
+ # @param [Array[String]] acronyms
159
+ # the acronyms to use to prevent modifications
160
+ # @param [String] separator
161
+ # the separator for linking words, by default `/`
162
+ #
163
+ # @api public
164
+ def pathcase(string, acronyms: [], separator: "/")
165
+ parsecase(string, acronyms: acronyms, sep: separator)
166
+ end
167
+ module_function :pathcase
168
+
169
+ # Convert string int a sentence
170
+ #
171
+ # @example
172
+ # sentencecase("foo bar baz") # => "Foo bar baz"
173
+ #
174
+ # @param [String] string
175
+ # the string to convert to sentence
176
+ # @param [Array[String]] acronyms
177
+ # the acronyms to use to prevent modifications
178
+ # @param [String] separator
179
+ # the separator for linking words, by default a space
180
+ #
181
+ # @api public
182
+ def sentencecase(string, acronyms: [], separator: " ")
183
+ res = parsecase(string, acronyms: acronyms, sep: separator, casing: :downcase)
184
+
185
+ return res if res.to_s.empty?
186
+
187
+ res[0].upcase + res[1..-1]
188
+ end
189
+ module_function :sentencecase
190
+
191
+ # Convert string into a snake_case
192
+ #
193
+ # @example
194
+ # snakecase("foo bar baz") # => "foo_bar_baz"
195
+ #
196
+ # snakecase("ЗдравствуйтеПривет") # => "здравствуйте_привет"
197
+ #
198
+ # snakecase("HTTPResponse") # => "http_response"
199
+ #
200
+ # @param [String] string
201
+ # the string to convert to snake case
202
+ # @param [Array[String]] acronyms
203
+ # the acronyms to use to prevent modifications
204
+ # @param [String] separator
205
+ # the separator for linking words, by default `_`
206
+ #
207
+ # @api public
208
+ def snakecase(string, acronyms: [], separator: "_")
209
+ parsecase(string, acronyms: acronyms, sep: separator)
210
+ end
211
+ module_function :snakecase
212
+
213
+ alias underscore snakecase
214
+ module_function :underscore
215
+
216
+ # Convert string into a title case
217
+ #
218
+ # @example
219
+ # titlecase("foo bar baz") # => "Foo Bar Baz"
220
+ #
221
+ # @param [String] string
222
+ # the string to convert to title case
223
+ # @param [Array[String]] acronyms
224
+ # the acronyms to use to prevent modifications
225
+ # @param [String] separator
226
+ # the separator for linking words, by default a space
227
+ #
228
+ # @api public
229
+ def titlecase(string, acronyms: [], separator: " ")
230
+ parsecase(string, acronyms: acronyms, sep: separator, casing: :capitalize)
231
+ end
232
+ module_function :titlecase
233
+
234
+ # Parse string and transform to desired case
235
+ #
236
+ # @api private
237
+ def parsecase(string, acronyms: [], sep: "_", casing: :downcase)
238
+ return if string.nil?
239
+
240
+ words = split_into_words(string, sep: sep)
241
+
242
+ no_case = ->(w) { acronyms.include?(w) ? w.extend(NullCase) : w }
243
+
244
+ words
245
+ .map(&no_case)
246
+ .map(&casing)
247
+ .join(sep)
248
+ end
249
+ module_function :parsecase
250
+ private_class_method :parsecase
251
+
252
+ # Split string into words
253
+ #
254
+ # @return [Array[String]]
255
+ # the split words
256
+ #
257
+ # @api private
258
+ def split_into_words(string, sep: nil)
259
+ words = []
260
+ word = []
261
+ last = string.length - 1
262
+
263
+ string.each_char.with_index do |char, i|
264
+ combine = word[-1].to_s + char
265
+
266
+ if combine =~ UPCASE
267
+ if word.size <= 1 # don't allow single letter words
268
+ word << char
269
+ else
270
+ words << word.join
271
+ word = [char]
272
+ end
273
+ elsif combine =~ LOWERCASE
274
+ letter = word.pop
275
+ if word.size <= 1 # don't allow single letter words
276
+ word << letter << char
277
+ else
278
+ words << word.join
279
+ word = [letter, char]
280
+ end
281
+ elsif DELIMITERS.include?(char)
282
+ words << word.join unless word.empty?
283
+ if i.zero? && char == sep
284
+ words << ""
285
+ else
286
+ word = []
287
+ end
288
+ elsif NONALPHANUMERIC.include?(char)
289
+ # noop
290
+ else
291
+ word << char
292
+ end
293
+
294
+ if last == i
295
+ word = [""] if char == sep
296
+ words << word.join unless word.empty?
297
+ end
298
+ end
299
+
300
+ words
301
+ end
302
+ module_function :split_into_words
303
+ end # Case
304
+ end # Strings