philiprehberger-slug 0.2.0 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8d55508ea15a44506254fb9899b5d87869b1749b775615fe11f129dced907f2c
4
- data.tar.gz: ca27b4b622a9c6b08798e285d68b39b37813594d1d0701d665a4c9621c038f12
3
+ metadata.gz: c6417c2057ab93e96dd94c6427ea1176a9e90b0827a03e5b54d69cc88d2265b0
4
+ data.tar.gz: 34cd902c8265b577a5c991c5d49ef4e142b8cbf5a823d4645e055429202d715d
5
5
  SHA512:
6
- metadata.gz: 7f4cb22828657d23aab0b6332762ad4c431349bdeef78b5ade464354e955057d58efa6eb60db228218969df69c97064c1211758b23e653b8ea8725a01ea7d9ad
7
- data.tar.gz: 39c4c94b8cb023de7617dc082927f3c5c3bb5b94ec1ae5492c618eba379858a5422a5843d09b90535b69c9c88db3d7f55d4b255472314aeb01aa26414d97dfea
6
+ metadata.gz: 1a76c62679ff50297c41caec2943def332006ed1b04936807e4c437c4bc1b029ffdd0827380ad204721232b55144db4f999f7c828135c31373b596f65c07ad4d
7
+ data.tar.gz: 799fc05c9c45cf4e179bf9b116074fa378acb5523366b6539e64847d8f9d864a363fe3448d4fc9c58ec3e9e92f6736ccb1cd820478c30e097f3fbe06d19c370f
data/CHANGELOG.md CHANGED
@@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.3.0] - 2026-04-13
11
+
12
+ ### Added
13
+ - `Slug.valid_slug?(string, separator:)` method for validating well-formed slugs
14
+ - `Slug.humanize(slug, separator:, capitalize:)` method for converting slugs back to human-readable titles
15
+
16
+ ### Changed
17
+ - Optimize transliteration with pre-compiled regex instead of char-by-char iteration
18
+ - Remove unused `mapping_regex` private method from Transliterator
19
+ - Tighten RuboCop metrics to match guide defaults
20
+
10
21
  ## [0.2.0] - 2026-04-01
11
22
 
12
23
  ### Added
data/README.md CHANGED
@@ -78,6 +78,26 @@ Philiprehberger::Slug.generate("Hello & World", custom_mapping: { "&" => "and" }
78
78
  # => "hello-and-world"
79
79
  ```
80
80
 
81
+ ### Validation
82
+
83
+ Check whether a string is already a well-formed slug:
84
+
85
+ ```ruby
86
+ Philiprehberger::Slug.valid_slug?("hello-world") # => true
87
+ Philiprehberger::Slug.valid_slug?("Hello World") # => false
88
+ Philiprehberger::Slug.valid_slug?("hello--world") # => false
89
+ ```
90
+
91
+ ### Humanize
92
+
93
+ Convert a slug back to a human-readable title:
94
+
95
+ ```ruby
96
+ Philiprehberger::Slug.humanize("hello-world") # => "Hello World"
97
+ Philiprehberger::Slug.humanize("hello-world", capitalize: :first) # => "Hello world"
98
+ Philiprehberger::Slug.humanize("hello_world", separator: "_") # => "Hello World"
99
+ ```
100
+
81
101
  ### Transliteration
82
102
 
83
103
  ```ruby
@@ -88,12 +108,11 @@ Philiprehberger::Slug.transliterate("café résumé") # => "cafe resume"
88
108
 
89
109
  | Method | Description |
90
110
  |--------|-------------|
91
- | `Slug.generate(string, separator: "-", max: nil, unique: nil)` | Generate a URL-safe slug from any string |
111
+ | `Slug.generate(string, separator:, max:, unique:, custom_mapping:)` | Generate a URL-safe slug from any string |
92
112
  | `Slug.generate_batch(strings, separator:, max:, custom_mapping:)` | Generate unique slugs for an array of strings with deduplication |
93
- | `Slug.transliterate(string)` | Transliterate Unicode characters to ASCII equivalents |
94
- | `Slug::Generator.call(string, separator: "-", max: nil, unique: nil)` | Core slug generation logic (called by `Slug.generate`) |
95
- | `Slug::Transliterator.call(string)` | Core transliteration logic (called by `Slug.transliterate`) |
96
- | `Slug::Transliterator::MAPPING` | Frozen hash of Unicode to ASCII character mappings |
113
+ | `Slug.valid_slug?(string, separator:)` | Check whether a string is a well-formed slug |
114
+ | `Slug.humanize(slug, separator:, capitalize:)` | Convert a slug back to a human-readable title |
115
+ | `Slug.transliterate(string, custom_mapping:)` | Transliterate Unicode characters to ASCII equivalents |
97
116
  | `Slug::Error` | Error raised for invalid input (e.g. non-String argument) |
98
117
  | `Slug::VERSION` | Current gem version string |
99
118
 
@@ -55,19 +55,22 @@ module Philiprehberger
55
55
  'ϊ' => 'i', 'ϋ' => 'y', 'ΐ' => 'i', 'ΰ' => 'y'
56
56
  }.freeze
57
57
 
58
+ MAPPING_REGEX = Regexp.union(MAPPING.keys).freeze
59
+
58
60
  # Transliterate Unicode characters to ASCII equivalents
59
61
  #
60
62
  # @param string [String] input string
63
+ # @param custom_mapping [Hash, nil] custom character replacements
61
64
  # @return [String] transliterated string
62
65
  def self.call(string, custom_mapping: nil)
63
- mapping = custom_mapping ? MAPPING.merge(custom_mapping) : MAPPING
64
- string.each_char.map { |char| mapping[char] || char }.join
65
- end
66
-
67
- def self.mapping_regex
68
- @mapping_regex ||= Regexp.union(MAPPING.keys)
66
+ if custom_mapping
67
+ merged = MAPPING.merge(custom_mapping)
68
+ regex = Regexp.union(merged.keys)
69
+ string.gsub(regex) { |match| merged[match] }
70
+ else
71
+ string.gsub(MAPPING_REGEX) { |match| MAPPING[match] }
72
+ end
69
73
  end
70
- private_class_method :mapping_regex
71
74
  end
72
75
  end
73
76
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Philiprehberger
4
4
  module Slug
5
- VERSION = '0.2.0'
5
+ VERSION = '0.3.0'
6
6
  end
7
7
  end
@@ -52,5 +52,38 @@ module Philiprehberger
52
52
  def self.transliterate(string, custom_mapping: nil)
53
53
  Transliterator.call(string, custom_mapping: custom_mapping)
54
54
  end
55
+
56
+ # Check whether a string is a well-formed slug
57
+ #
58
+ # @param string [String] the string to validate
59
+ # @param separator [String] the allowed separator character (default: "-")
60
+ # @return [Boolean] true if the string is a valid slug
61
+ def self.valid_slug?(string, separator: '-')
62
+ return false unless string.is_a?(String)
63
+ return false if string.empty?
64
+
65
+ sep = Regexp.escape(separator)
66
+ string.match?(/\A[a-z0-9]+(?:#{sep}[a-z0-9]+)*\z/)
67
+ end
68
+
69
+ # Convert a slug back to a human-readable title
70
+ #
71
+ # @param slug [String] the slug to humanize
72
+ # @param separator [String] the separator used in the slug (default: "-")
73
+ # @param capitalize [Symbol] capitalization strategy: :words, :first, or :none
74
+ # @return [String] the human-readable string
75
+ # @raise [Error] if input is not a String
76
+ def self.humanize(slug, separator: '-', capitalize: :words)
77
+ raise Error, "Input must be a String, got #{slug.class}" unless slug.is_a?(String)
78
+ return '' if slug.empty?
79
+
80
+ result = slug.gsub(separator, ' ')
81
+ case capitalize
82
+ when :words then result.gsub(/\b[a-z]/, &:upcase)
83
+ when :first then result.sub(/\A[a-z]/, &:upcase)
84
+ when :none then result
85
+ else raise Error, "Unknown capitalize option: #{capitalize}"
86
+ end
87
+ end
55
88
  end
56
89
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: philiprehberger-slug
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Philip Rehberger
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-04-01 00:00:00.000000000 Z
11
+ date: 2026-04-13 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Generate URL-safe slugs from any string with built-in Unicode transliteration,
14
14
  configurable separators, word-boundary truncation, and collision-aware uniqueness.