philiprehberger-string_kit 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: 5860e5af85b84c9b5febcb56424bed3960dc4d26d193b2edf748f3138a59f2db
4
+ data.tar.gz: a84f3f30e15ea382ebd28f89ea9bb61120dce7e8c403d970a0b1f2963b505ab9
5
+ SHA512:
6
+ metadata.gz: 8622621d3904e9425ea98f91598aecaa4749c81dd9be692ced0503202e21690310138928af2e1cb742160a4569156fab435e8abf31111f4302fc26a1f59b054b
7
+ data.tar.gz: 7cfa9aa53ecc255ae7bca4e10755f5da08eec04587fd4185ff74a51a6619c4b4c1734aa4bb7d4cea37cead99ffa1e06e0fd917ef884875913a781a732923d7a9
data/CHANGELOG.md ADDED
@@ -0,0 +1,19 @@
1
+ # Changelog
2
+
3
+ All notable changes to this gem will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this gem adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.1.0] - 2026-03-22
11
+
12
+ ### Added
13
+
14
+ - Initial release
15
+ - Case conversion: titlecase, kebab_case, camel_case, pascal_case, snake_case, constant_case
16
+ - HTML stripping and whitespace normalization
17
+ - Word counting and reading time estimation
18
+ - Excerpt extraction around a phrase
19
+ - String squeeze, indent, and dedent utilities
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 philiprehberger
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 all
13
+ 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 THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,87 @@
1
+ # philiprehberger-string_kit
2
+
3
+ [![Tests](https://github.com/philiprehberger/rb-string-kit/actions/workflows/ci.yml/badge.svg)](https://github.com/philiprehberger/rb-string-kit/actions/workflows/ci.yml)
4
+ [![Gem Version](https://badge.fury.io/rb/philiprehberger-string_kit.svg)](https://rubygems.org/gems/philiprehberger-string_kit)
5
+ [![License](https://img.shields.io/github/license/philiprehberger/rb-string-kit)](LICENSE)
6
+
7
+ Comprehensive string utilities without ActiveSupport dependency
8
+
9
+ ## Requirements
10
+
11
+ - Ruby >= 3.1
12
+
13
+ ## Installation
14
+
15
+ Add to your Gemfile:
16
+
17
+ ```ruby
18
+ gem 'philiprehberger-string_kit'
19
+ ```
20
+
21
+ Or install directly:
22
+
23
+ ```bash
24
+ gem install philiprehberger-string_kit
25
+ ```
26
+
27
+ ## Usage
28
+
29
+ ```ruby
30
+ require 'philiprehberger/string_kit'
31
+
32
+ Philiprehberger::StringKit.titlecase('hello world') # => "Hello World"
33
+ Philiprehberger::StringKit.kebab_case('helloWorld') # => "hello-world"
34
+ Philiprehberger::StringKit.camel_case('hello world') # => "helloWorld"
35
+ Philiprehberger::StringKit.pascal_case('hello world') # => "HelloWorld"
36
+ Philiprehberger::StringKit.snake_case('Hello World') # => "hello_world"
37
+ Philiprehberger::StringKit.constant_case('hello world') # => "HELLO_WORLD"
38
+ ```
39
+
40
+ ### Text Processing
41
+
42
+ ```ruby
43
+ Philiprehberger::StringKit.strip_html('<p>hello</p>') # => "hello"
44
+ Philiprehberger::StringKit.normalize_whitespace('a b') # => "a b"
45
+ Philiprehberger::StringKit.word_count('hello world foo') # => 3
46
+ Philiprehberger::StringKit.reading_time(long_text, wpm: 200) # => 2
47
+ ```
48
+
49
+ ### Excerpt and Indentation
50
+
51
+ ```ruby
52
+ Philiprehberger::StringKit.excerpt(text, 'fox', radius: 10) # => "...brown fox jumps..."
53
+ Philiprehberger::StringKit.squeeze('aaabbb') # => "ab"
54
+ Philiprehberger::StringKit.indent("hello\nworld", 2) # => " hello\n world"
55
+ Philiprehberger::StringKit.dedent(" hello\n world") # => "hello\nworld"
56
+ ```
57
+
58
+ ## API
59
+
60
+ | Method | Description |
61
+ |--------|-------------|
62
+ | `StringKit.titlecase(str)` | Convert string to Title Case |
63
+ | `StringKit.kebab_case(str)` | Convert string to kebab-case |
64
+ | `StringKit.camel_case(str)` | Convert string to camelCase |
65
+ | `StringKit.pascal_case(str)` | Convert string to PascalCase |
66
+ | `StringKit.snake_case(str)` | Convert string to snake_case |
67
+ | `StringKit.constant_case(str)` | Convert string to CONSTANT_CASE |
68
+ | `StringKit.strip_html(str)` | Remove HTML tags from string |
69
+ | `StringKit.normalize_whitespace(str)` | Collapse whitespace to single spaces |
70
+ | `StringKit.word_count(str)` | Count words in string |
71
+ | `StringKit.reading_time(str, wpm:)` | Estimate reading time in minutes |
72
+ | `StringKit.excerpt(str, phrase, radius:)` | Extract text around a phrase |
73
+ | `StringKit.squeeze(str)` | Remove consecutive duplicate characters |
74
+ | `StringKit.indent(str, n)` | Indent each line by n spaces |
75
+ | `StringKit.dedent(str)` | Remove common leading whitespace |
76
+
77
+ ## Development
78
+
79
+ ```bash
80
+ bundle install
81
+ bundle exec rspec # Run tests
82
+ bundle exec rubocop # Check code style
83
+ ```
84
+
85
+ ## License
86
+
87
+ MIT
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Philiprehberger
4
+ module StringKit
5
+ VERSION = '0.1.0'
6
+ end
7
+ end
@@ -0,0 +1,177 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'string_kit/version'
4
+
5
+ module Philiprehberger
6
+ module StringKit
7
+ class Error < StandardError; end
8
+
9
+ WORD_BOUNDARY = /[^a-zA-Z0-9\u00C0-\u024F]+/
10
+
11
+ # Convert string to Title Case
12
+ #
13
+ # @param str [String]
14
+ # @return [String]
15
+ def self.titlecase(str)
16
+ validate!(str)
17
+ str.gsub(/\b(\w)/) { ::Regexp.last_match(1).upcase }
18
+ end
19
+
20
+ # Convert string to kebab-case
21
+ #
22
+ # @param str [String]
23
+ # @return [String]
24
+ def self.kebab_case(str)
25
+ validate!(str)
26
+ separate_words(str).join('-')
27
+ end
28
+
29
+ # Convert string to camelCase
30
+ #
31
+ # @param str [String]
32
+ # @return [String]
33
+ def self.camel_case(str)
34
+ validate!(str)
35
+ words = separate_words(str)
36
+ return '' if words.empty?
37
+
38
+ words.first + words[1..].map(&:capitalize).join
39
+ end
40
+
41
+ # Convert string to PascalCase
42
+ #
43
+ # @param str [String]
44
+ # @return [String]
45
+ def self.pascal_case(str)
46
+ validate!(str)
47
+ separate_words(str).map(&:capitalize).join
48
+ end
49
+
50
+ # Convert string to snake_case
51
+ #
52
+ # @param str [String]
53
+ # @return [String]
54
+ def self.snake_case(str)
55
+ validate!(str)
56
+ separate_words(str).join('_')
57
+ end
58
+
59
+ # Convert string to CONSTANT_CASE
60
+ #
61
+ # @param str [String]
62
+ # @return [String]
63
+ def self.constant_case(str)
64
+ validate!(str)
65
+ separate_words(str).join('_').upcase
66
+ end
67
+
68
+ # Strip HTML tags from string
69
+ #
70
+ # @param str [String]
71
+ # @return [String]
72
+ def self.strip_html(str)
73
+ validate!(str)
74
+ str.gsub(/<[^>]*>/, '')
75
+ end
76
+
77
+ # Normalize whitespace to single spaces
78
+ #
79
+ # @param str [String]
80
+ # @return [String]
81
+ def self.normalize_whitespace(str)
82
+ validate!(str)
83
+ str.gsub(/\s+/, ' ').strip
84
+ end
85
+
86
+ # Count words in string
87
+ #
88
+ # @param str [String]
89
+ # @return [Integer]
90
+ def self.word_count(str)
91
+ validate!(str)
92
+ str.split(/\s+/).reject(&:empty?).length
93
+ end
94
+
95
+ # Estimate reading time in minutes
96
+ #
97
+ # @param str [String]
98
+ # @param wpm [Integer] words per minute
99
+ # @return [Float]
100
+ def self.reading_time(str, wpm: 200)
101
+ validate!(str)
102
+ count = word_count(str)
103
+ (count.to_f / wpm).ceil
104
+ end
105
+
106
+ # Extract excerpt around a phrase
107
+ #
108
+ # @param str [String]
109
+ # @param phrase [String]
110
+ # @param radius [Integer] number of characters around the phrase
111
+ # @return [String]
112
+ def self.excerpt(str, phrase, radius: 32)
113
+ validate!(str)
114
+ index = str.downcase.index(phrase.downcase)
115
+ return '' if index.nil?
116
+
117
+ start_pos = [index - radius, 0].max
118
+ end_pos = [index + phrase.length + radius, str.length].min
119
+
120
+ result = str[start_pos...end_pos]
121
+ result = "...#{result}" if start_pos.positive?
122
+ result = "#{result}..." if end_pos < str.length
123
+ result
124
+ end
125
+
126
+ # Remove consecutive duplicate characters
127
+ #
128
+ # @param str [String]
129
+ # @return [String]
130
+ def self.squeeze(str)
131
+ validate!(str)
132
+ str.squeeze
133
+ end
134
+
135
+ # Indent each line by n spaces
136
+ #
137
+ # @param str [String]
138
+ # @param n [Integer]
139
+ # @return [String]
140
+ def self.indent(str, n)
141
+ validate!(str)
142
+ prefix = ' ' * n
143
+ str.gsub(/^/, prefix)
144
+ end
145
+
146
+ # Remove common leading whitespace from all lines
147
+ #
148
+ # @param str [String]
149
+ # @return [String]
150
+ def self.dedent(str)
151
+ validate!(str)
152
+ lines = str.lines
153
+ non_empty = lines.reject { |line| line.strip.empty? }
154
+ return str if non_empty.empty?
155
+
156
+ min_indent = non_empty.map { |line| line[/\A\s*/].length }.min
157
+ lines.map { |line| line.length > min_indent ? line[min_indent..] : line.lstrip }.join
158
+ end
159
+
160
+ class << self
161
+ private
162
+
163
+ def validate!(str)
164
+ raise Error, 'input must be a String' unless str.is_a?(String)
165
+ end
166
+
167
+ def separate_words(str)
168
+ str
169
+ .gsub(/([a-z])([A-Z])/, '\1 \2')
170
+ .gsub(/[^a-zA-Z0-9]+/, ' ')
171
+ .strip
172
+ .downcase
173
+ .split
174
+ end
175
+ end
176
+ end
177
+ end
metadata ADDED
@@ -0,0 +1,54 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: philiprehberger-string_kit
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Philip Rehberger
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-03-22 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: String case conversion, HTML stripping, whitespace normalization, word
14
+ counting, reading time estimation, excerpt extraction, indentation, and more.
15
+ email:
16
+ - me@philiprehberger.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - CHANGELOG.md
22
+ - LICENSE
23
+ - README.md
24
+ - lib/philiprehberger/string_kit.rb
25
+ - lib/philiprehberger/string_kit/version.rb
26
+ homepage: https://github.com/philiprehberger/rb-string-kit
27
+ licenses:
28
+ - MIT
29
+ metadata:
30
+ homepage_uri: https://github.com/philiprehberger/rb-string-kit
31
+ source_code_uri: https://github.com/philiprehberger/rb-string-kit
32
+ changelog_uri: https://github.com/philiprehberger/rb-string-kit/blob/main/CHANGELOG.md
33
+ bug_tracker_uri: https://github.com/philiprehberger/rb-string-kit/issues
34
+ rubygems_mfa_required: 'true'
35
+ post_install_message:
36
+ rdoc_options: []
37
+ require_paths:
38
+ - lib
39
+ required_ruby_version: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: 3.1.0
44
+ required_rubygems_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ requirements: []
50
+ rubygems_version: 3.5.22
51
+ signing_key:
52
+ specification_version: 4
53
+ summary: Comprehensive string utilities without ActiveSupport dependency
54
+ test_files: []