keycase 1.2.0 → 1.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 +4 -4
- data/README.md +250 -33
- data/keycase.gemspec +1 -1
- data/lib/keycase/camel_case.rb +54 -26
- data/lib/keycase/kebab_case.rb +51 -26
- data/lib/keycase/snake_case.rb +45 -26
- data/lib/keycase/support/acronyms.rb +30 -0
- data/lib/keycase/support/struct_instantiation.rb +112 -0
- data/lib/keycase/support/structure_keys.rb +20 -0
- data/lib/keycase/support/tokenizer.rb +3 -7
- data/lib/keycase/support/transformer/collision.rb +30 -0
- data/lib/keycase/support/transformer/only_normalization.rb +50 -0
- data/lib/keycase/support/transformer/structure_builders.rb +38 -0
- data/lib/keycase/support/transformer/visit.rb +33 -0
- data/lib/keycase/support/transformer.rb +60 -48
- data/lib/keycase/version.rb +1 -1
- data/lib/keycase.rb +8 -3
- metadata +9 -5
- data/lib/keycase/pascal_case.rb +0 -60
- data/lib/keycase/screaming_snake_case.rb +0 -60
- data/lib/keycase/train_case.rb +0 -60
data/lib/keycase/snake_case.rb
CHANGED
|
@@ -1,14 +1,33 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
require_relative "support/transformer"
|
|
3
|
+
require_relative "support/structure_keys"
|
|
6
4
|
require_relative "support/tokenizer"
|
|
7
5
|
|
|
8
6
|
module Keycase
|
|
9
7
|
module SnakeCase
|
|
8
|
+
def self.convert_string(str, upcase: false)
|
|
9
|
+
words = Keycase::Support::Tokenizer.words(str)
|
|
10
|
+
converter = upcase ? :upcase : :downcase
|
|
11
|
+
words.map(&converter).join("_")
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.convert(value, upcase: false)
|
|
15
|
+
case value
|
|
16
|
+
when String then convert_string(value, upcase: upcase)
|
|
17
|
+
when Symbol then convert_string(value.to_s, upcase: upcase).to_sym
|
|
18
|
+
else value
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.convert_keys(structure, options = {})
|
|
23
|
+
upcase = options.fetch(:upcase, false)
|
|
24
|
+
Keycase::Support::StructureKeys.transform(structure, options) do |key|
|
|
25
|
+
convert(key, upcase: upcase)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
10
29
|
refine Object do
|
|
11
|
-
def to_snake_case
|
|
30
|
+
def to_snake_case(_options = {})
|
|
12
31
|
self
|
|
13
32
|
end
|
|
14
33
|
|
|
@@ -18,43 +37,43 @@ module Keycase
|
|
|
18
37
|
end
|
|
19
38
|
|
|
20
39
|
refine String do
|
|
21
|
-
def to_snake_case
|
|
22
|
-
Keycase::
|
|
23
|
-
word.downcase
|
|
24
|
-
end.join("_")
|
|
40
|
+
def to_snake_case(upcase: false)
|
|
41
|
+
Keycase::SnakeCase.convert_string(self, upcase: upcase)
|
|
25
42
|
end
|
|
26
43
|
end
|
|
27
44
|
|
|
28
45
|
refine Symbol do
|
|
29
|
-
def to_snake_case
|
|
30
|
-
|
|
46
|
+
def to_snake_case(upcase: false)
|
|
47
|
+
Keycase::SnakeCase.convert(self, upcase: upcase)
|
|
31
48
|
end
|
|
32
49
|
end
|
|
33
50
|
|
|
34
51
|
refine Array do
|
|
35
52
|
def with_snake_case_keys(options = {})
|
|
36
|
-
Keycase::
|
|
37
|
-
self,
|
|
38
|
-
::Set.new,
|
|
39
|
-
0,
|
|
40
|
-
options[:max_depth]
|
|
41
|
-
) do |key|
|
|
42
|
-
key.to_snake_case
|
|
43
|
-
end
|
|
53
|
+
Keycase::SnakeCase.convert_keys(self, options)
|
|
44
54
|
end
|
|
45
55
|
end
|
|
46
56
|
|
|
47
57
|
refine Hash do
|
|
48
58
|
def with_snake_case_keys(options = {})
|
|
49
|
-
Keycase::
|
|
50
|
-
self,
|
|
51
|
-
::Set.new,
|
|
52
|
-
0,
|
|
53
|
-
options[:max_depth]
|
|
54
|
-
) do |key|
|
|
55
|
-
key.to_snake_case
|
|
56
|
-
end
|
|
59
|
+
Keycase::SnakeCase.convert_keys(self, options)
|
|
57
60
|
end
|
|
58
61
|
end
|
|
62
|
+
|
|
63
|
+
refine Struct do
|
|
64
|
+
def with_snake_case_keys(options = {})
|
|
65
|
+
Keycase::SnakeCase.convert_keys(self, options)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
class << self
|
|
71
|
+
def snake_case(value, upcase: false)
|
|
72
|
+
SnakeCase.convert(value, upcase: upcase)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def with_snake_case_keys(value, options = {})
|
|
76
|
+
SnakeCase.convert_keys(value, options)
|
|
77
|
+
end
|
|
59
78
|
end
|
|
60
79
|
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Keycase
|
|
4
|
+
module Support
|
|
5
|
+
module Acronyms
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def index(list)
|
|
9
|
+
return {} if list.nil?
|
|
10
|
+
|
|
11
|
+
Array(list).each_with_object({}) do |item, memo|
|
|
12
|
+
s = item.to_s
|
|
13
|
+
next if s.empty?
|
|
14
|
+
|
|
15
|
+
memo[s.downcase] = s
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def segment(word, acronyms_by_downcase)
|
|
20
|
+
acronyms_by_downcase[word.downcase] || word.capitalize
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def leading_acronym?(first_word, acronyms_by_downcase)
|
|
24
|
+
return false unless first_word
|
|
25
|
+
|
|
26
|
+
acronyms_by_downcase.key?(first_word.downcase)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Keycase
|
|
4
|
+
module Support
|
|
5
|
+
module StructInstantiation
|
|
6
|
+
KEYWORD_INIT_IVAR = :@keycase_keyword_init
|
|
7
|
+
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def struct_keyword_init_available?
|
|
11
|
+
return @struct_keyword_init_available if defined?(@struct_keyword_init_available)
|
|
12
|
+
|
|
13
|
+
available = false
|
|
14
|
+
begin
|
|
15
|
+
Struct.new(:x, keyword_init: true)
|
|
16
|
+
available = true
|
|
17
|
+
rescue TypeError
|
|
18
|
+
available = false
|
|
19
|
+
end
|
|
20
|
+
@struct_keyword_init_available = available
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def mark_keyword_init!(klass)
|
|
24
|
+
klass.instance_variable_set(KEYWORD_INIT_IVAR, true)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def struct_keyword_init?(klass)
|
|
28
|
+
return false unless struct_keyword_init_available?
|
|
29
|
+
|
|
30
|
+
return true if klass.instance_variable_defined?(KEYWORD_INIT_IVAR)
|
|
31
|
+
|
|
32
|
+
klass.respond_to?(:keyword_init?) && klass.keyword_init?
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def build_struct_class(*members, keyword_init:)
|
|
36
|
+
if keyword_init && struct_keyword_init_available?
|
|
37
|
+
Struct.new(*members, keyword_init: true).tap do |klass|
|
|
38
|
+
mark_keyword_init!(klass)
|
|
39
|
+
end
|
|
40
|
+
else
|
|
41
|
+
Struct.new(*members)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def each_member_pair(structure)
|
|
46
|
+
# Struct member accessors can hide the methods used by conversion.
|
|
47
|
+
::Struct.instance_method(:each_pair).bind(structure).to_enum(:call)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def rebuild(original, pairs)
|
|
51
|
+
klass = Object.instance_method(:class).bind(original).call
|
|
52
|
+
original_members = each_member_pair(original).map(&:first)
|
|
53
|
+
new_members = pairs.keys
|
|
54
|
+
|
|
55
|
+
# Older Rubies cannot identify keyword_init on Struct classes created by callers.
|
|
56
|
+
return copy_struct(original, pairs, klass) if new_members == original_members
|
|
57
|
+
|
|
58
|
+
target_klass = build_struct_class(*new_members, keyword_init: struct_keyword_init?(klass))
|
|
59
|
+
instantiate(target_klass, pairs, new_members)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def instantiate(klass, pairs, member_order)
|
|
63
|
+
instance = klass.allocate
|
|
64
|
+
# A converted member named initialize can hide Struct's initializer.
|
|
65
|
+
initializer = ::Struct.instance_method(:initialize).bind(instance)
|
|
66
|
+
if struct_keyword_init?(klass)
|
|
67
|
+
initializer.call(**pairs)
|
|
68
|
+
else
|
|
69
|
+
values = member_order.map do |member|
|
|
70
|
+
pairs[member]
|
|
71
|
+
end
|
|
72
|
+
initializer.call(*values)
|
|
73
|
+
end
|
|
74
|
+
instance
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def copy_struct(original, pairs, klass)
|
|
78
|
+
copy = duplicate_struct(original, klass)
|
|
79
|
+
writer = ::Struct.instance_method(:[]=).bind(copy)
|
|
80
|
+
pairs.each do |member, value|
|
|
81
|
+
writer.call(member, value)
|
|
82
|
+
end
|
|
83
|
+
copy
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def duplicate_struct(original, klass)
|
|
87
|
+
return Object.instance_method(:dup).bind(original).call unless copy_hooks_shadowed?(klass)
|
|
88
|
+
|
|
89
|
+
# dup dispatches copy hooks, which member accessors can hide.
|
|
90
|
+
copy = Class.instance_method(:allocate).bind(klass).call
|
|
91
|
+
::Struct.instance_method(:initialize_copy).bind(copy).call(original)
|
|
92
|
+
copy_instance_variables(original, copy)
|
|
93
|
+
copy
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def copy_hooks_shadowed?(klass)
|
|
97
|
+
(%i[initialize_dup initialize_copy] & klass.members).any? do |hook|
|
|
98
|
+
klass.instance_method(hook).arity.zero?
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def copy_instance_variables(original, copy)
|
|
103
|
+
reader = Object.instance_method(:instance_variable_get).bind(original)
|
|
104
|
+
writer = Object.instance_method(:instance_variable_set).bind(copy)
|
|
105
|
+
Object.instance_method(:instance_variables).bind(original).call.each do |name|
|
|
106
|
+
writer.call(name, reader.call(name))
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
private_class_method :copy_struct, :duplicate_struct, :copy_hooks_shadowed?, :copy_instance_variables
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "set"
|
|
4
|
+
|
|
5
|
+
require_relative "transformer"
|
|
6
|
+
|
|
7
|
+
module Keycase
|
|
8
|
+
module Support
|
|
9
|
+
module StructureKeys
|
|
10
|
+
module_function
|
|
11
|
+
|
|
12
|
+
def transform(structure, options = {}, &key_converter)
|
|
13
|
+
opts = Transformer.enrich_options(options)
|
|
14
|
+
state = Transformer::State.new(::Set.new, 0, opts, :none)
|
|
15
|
+
|
|
16
|
+
Transformer.transform_value(structure, state, &key_converter)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -7,13 +7,9 @@ module Keycase
|
|
|
7
7
|
|
|
8
8
|
def words(value)
|
|
9
9
|
value
|
|
10
|
-
.gsub(/(
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
.gsub(/(?<=[0-9a-z])(?=[A-Z])/) do |_|
|
|
14
|
-
"_"
|
|
15
|
-
end
|
|
16
|
-
.scan(/[0-9A-Za-z]+/)
|
|
10
|
+
.gsub(/(?<=\p{Lu})(?=\p{Lu}\p{Ll})/, "_")
|
|
11
|
+
.gsub(/(?<=[\p{Ll}\p{Nd}])(?=\p{Lu})/, "_")
|
|
12
|
+
.scan(/[\p{L}\p{N}]+/)
|
|
17
13
|
end
|
|
18
14
|
end
|
|
19
15
|
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Keycase
|
|
4
|
+
module Support
|
|
5
|
+
module Transformer
|
|
6
|
+
class << self
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def keep_existing_key?(memo, new_key, original_key, on_collision)
|
|
10
|
+
return false unless memo.key?(new_key)
|
|
11
|
+
|
|
12
|
+
if on_collision == :raise
|
|
13
|
+
message = "Keycase detected a key collision: #{original_key.inspect} converted to " \
|
|
14
|
+
"#{new_key.inspect}, which already exists in the transformed structure"
|
|
15
|
+
raise KeyCollisionError, message
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
on_collision == :keep_first
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def resolve_on_collision!(options)
|
|
22
|
+
mode = options.fetch(:on_collision, :raise)
|
|
23
|
+
return mode if %i[raise overwrite keep_first].include?(mode)
|
|
24
|
+
|
|
25
|
+
raise ArgumentError, "Keycase on_collision must be :raise, :overwrite, or :keep_first (got #{mode.inspect})"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Keycase
|
|
4
|
+
module Support
|
|
5
|
+
module Transformer
|
|
6
|
+
class << self
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def convert_key?(key, options)
|
|
10
|
+
klasses = options[:__keycase_only_klasses]
|
|
11
|
+
return true if klasses.nil?
|
|
12
|
+
|
|
13
|
+
klasses.any? do |klass|
|
|
14
|
+
# Struct keys can hide is_a? with a member accessor.
|
|
15
|
+
klass === key # rubocop:disable Style/CaseEquality
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def normalize_only_classes(only)
|
|
20
|
+
return nil if only.nil?
|
|
21
|
+
|
|
22
|
+
raise ArgumentError, "Keycase only must be an Array (got #{only.class})" unless only.is_a?(Array)
|
|
23
|
+
|
|
24
|
+
only.map do |spec|
|
|
25
|
+
normalize_only_spec(spec)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def normalize_only_spec(spec)
|
|
30
|
+
case spec
|
|
31
|
+
when Class
|
|
32
|
+
assert_only_allowed_class!(spec)
|
|
33
|
+
spec
|
|
34
|
+
when :string, "string" then String
|
|
35
|
+
when :symbol, "symbol" then Symbol
|
|
36
|
+
else
|
|
37
|
+
raise ArgumentError, "Keycase only: unsupported entry #{spec.inspect}"
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def assert_only_allowed_class!(spec)
|
|
42
|
+
return if [String, Symbol].include?(spec)
|
|
43
|
+
|
|
44
|
+
raise ArgumentError,
|
|
45
|
+
"Keycase only allows String and Symbol as classes (got #{spec})"
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Keycase
|
|
4
|
+
module Support
|
|
5
|
+
module Transformer
|
|
6
|
+
class << self
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def build_transformed_struct(struct, state, on_collision, &key_converter)
|
|
10
|
+
pairs = build_transformed_members(
|
|
11
|
+
StructInstantiation.each_member_pair(struct), child_state(state, :struct), on_collision, &key_converter
|
|
12
|
+
)
|
|
13
|
+
StructInstantiation.rebuild(struct, pairs)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def build_transformed_members(members, child, on_collision, &key_converter)
|
|
17
|
+
result = members.is_a?(Hash) && members.compare_by_identity? ? {}.compare_by_identity : {}
|
|
18
|
+
members.each_with_object(result) do |(key, value), memo|
|
|
19
|
+
new_key = convert_member_key(key, child, &key_converter)
|
|
20
|
+
next if keep_existing_key?(memo, new_key, key, on_collision)
|
|
21
|
+
|
|
22
|
+
memo[new_key] = transform_value(value, child, &key_converter)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def convert_member_key(key, state, &key_converter)
|
|
27
|
+
return key unless convert_key?(key, state.options)
|
|
28
|
+
|
|
29
|
+
new_key = key_converter.call(key)
|
|
30
|
+
# Only Struct members prohibit empty names; nested Hash keys may be empty.
|
|
31
|
+
return key if state.parent_kind == :struct && new_key.to_s.empty?
|
|
32
|
+
|
|
33
|
+
new_key
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Keycase
|
|
4
|
+
module Support
|
|
5
|
+
module Transformer
|
|
6
|
+
class << self
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def check_depth!(depth, max_depth)
|
|
10
|
+
return if max_depth.nil?
|
|
11
|
+
|
|
12
|
+
raise StructureTooDeepError, "Keycase nesting exceeds max_depth (#{max_depth})" if depth > max_depth
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def visit_structure(structure, visiting, kind)
|
|
16
|
+
oid = Object.instance_method(:object_id).bind(structure).call
|
|
17
|
+
raise CircularStructureError, circular_reference_message(kind) if visiting.include?(oid)
|
|
18
|
+
|
|
19
|
+
visiting.add(oid)
|
|
20
|
+
begin
|
|
21
|
+
yield
|
|
22
|
+
ensure
|
|
23
|
+
visiting.delete(oid)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def circular_reference_message(kind)
|
|
28
|
+
"Keycase detected a circular reference in a #{kind}"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -1,68 +1,82 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "errors"
|
|
4
|
+
require_relative "struct_instantiation"
|
|
5
|
+
require_relative "transformer/visit"
|
|
6
|
+
require_relative "transformer/only_normalization"
|
|
7
|
+
require_relative "transformer/collision"
|
|
8
|
+
require_relative "transformer/structure_builders"
|
|
4
9
|
|
|
5
10
|
module Keycase
|
|
6
11
|
module Support
|
|
7
12
|
module Transformer
|
|
13
|
+
State = Struct.new(:visiting, :depth, :options, :parent_kind)
|
|
14
|
+
|
|
8
15
|
class << self
|
|
9
|
-
def
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
hash.each_with_object({}) do |(key, value), memo|
|
|
18
|
-
new_key = key_converter.call(key)
|
|
19
|
-
if memo.key?(new_key)
|
|
20
|
-
message = "Keycase detected a key collision: #{key.inspect} converted to " \
|
|
21
|
-
"#{new_key.inspect}, which already exists in the transformed hash"
|
|
22
|
-
raise KeyCollisionError, message
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
memo[new_key] = transform_value(
|
|
26
|
-
value,
|
|
27
|
-
visiting,
|
|
28
|
-
depth + 1,
|
|
29
|
-
max_depth,
|
|
30
|
-
&key_converter
|
|
31
|
-
)
|
|
32
|
-
end
|
|
33
|
-
ensure
|
|
34
|
-
visiting.delete(oid)
|
|
16
|
+
def enrich_options(options)
|
|
17
|
+
recursive = options.fetch(:recursive, true)
|
|
18
|
+
arrays = options.fetch(:arrays, true)
|
|
19
|
+
unless [true, false].include?(recursive)
|
|
20
|
+
raise ArgumentError, "Keycase recursive must be true or false (got #{recursive.inspect})"
|
|
21
|
+
end
|
|
22
|
+
unless [true, false].include?(arrays)
|
|
23
|
+
raise ArgumentError, "Keycase arrays must be true or false (got #{arrays.inspect})"
|
|
35
24
|
end
|
|
25
|
+
|
|
26
|
+
options.merge(
|
|
27
|
+
recursive: recursive,
|
|
28
|
+
arrays: arrays,
|
|
29
|
+
__keycase_only_klasses: normalize_only_classes(options[:only])
|
|
30
|
+
)
|
|
36
31
|
end
|
|
37
32
|
|
|
38
|
-
def
|
|
39
|
-
|
|
33
|
+
def transform_hash(hash, state, &key_converter)
|
|
34
|
+
return hash if state.options[:recursive] == false && state.parent_kind == :hash
|
|
40
35
|
|
|
41
|
-
|
|
42
|
-
|
|
36
|
+
on_collision = resolve_on_collision!(state.options)
|
|
37
|
+
check_depth!(state.depth, state.options[:max_depth])
|
|
43
38
|
|
|
44
|
-
visiting
|
|
45
|
-
|
|
39
|
+
visit_structure(hash, state.visiting, "Hash") do
|
|
40
|
+
build_transformed_members(hash, child_state(state, :hash), on_collision, &key_converter)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def transform_array(array, state, &key_converter)
|
|
45
|
+
if state.options[:arrays] == false
|
|
46
|
+
return array.map do |element|
|
|
47
|
+
element
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
check_depth!(state.depth, state.options[:max_depth])
|
|
52
|
+
|
|
53
|
+
visit_structure(array, state.visiting, "Array") do
|
|
54
|
+
child = child_state(state, :array)
|
|
46
55
|
array.map do |element|
|
|
47
|
-
transform_value(
|
|
48
|
-
element,
|
|
49
|
-
visiting,
|
|
50
|
-
depth + 1,
|
|
51
|
-
max_depth,
|
|
52
|
-
&key_converter
|
|
53
|
-
)
|
|
56
|
+
transform_value(element, child, &key_converter)
|
|
54
57
|
end
|
|
55
|
-
ensure
|
|
56
|
-
visiting.delete(oid)
|
|
57
58
|
end
|
|
58
59
|
end
|
|
59
60
|
|
|
60
|
-
def
|
|
61
|
+
def transform_struct(struct, state, &key_converter)
|
|
62
|
+
return struct if state.options[:recursive] == false && state.parent_kind == :struct
|
|
63
|
+
|
|
64
|
+
on_collision = resolve_on_collision!(state.options)
|
|
65
|
+
check_depth!(state.depth, state.options[:max_depth])
|
|
66
|
+
|
|
67
|
+
visit_structure(struct, state.visiting, "Struct") do
|
|
68
|
+
build_transformed_struct(struct, state, on_collision, &key_converter)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def transform_value(value, state, &key_converter)
|
|
61
73
|
case value
|
|
62
74
|
when Hash
|
|
63
|
-
transform_hash(value,
|
|
75
|
+
transform_hash(value, state, &key_converter)
|
|
64
76
|
when Array
|
|
65
|
-
transform_array(value,
|
|
77
|
+
transform_array(value, state, &key_converter)
|
|
78
|
+
when ::Struct
|
|
79
|
+
transform_struct(value, state, &key_converter)
|
|
66
80
|
else
|
|
67
81
|
value
|
|
68
82
|
end
|
|
@@ -70,10 +84,8 @@ module Keycase
|
|
|
70
84
|
|
|
71
85
|
private
|
|
72
86
|
|
|
73
|
-
def
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
raise StructureTooDeepError, "Keycase nesting exceeds max_depth (#{max_depth})" if depth > max_depth
|
|
87
|
+
def child_state(state, parent_kind)
|
|
88
|
+
State.new(state.visiting, state.depth + 1, state.options, parent_kind)
|
|
77
89
|
end
|
|
78
90
|
end
|
|
79
91
|
end
|
data/lib/keycase/version.rb
CHANGED
data/lib/keycase.rb
CHANGED
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# Recursively converts string/symbol case and hash keys (camelCase, snake_case, etc.).
|
|
4
|
+
#
|
|
5
|
+
# +Keycase+ is reopened in +lib/keycase/<case>.rb+; module functions (+Keycase.camel_case+ and
|
|
6
|
+
# friends) and refinement modules (+Keycase::CamelCase+, ...) live in those files, not here.
|
|
7
|
+
#
|
|
8
|
+
# Call module functions on +Keycase+ when +using Keycase::CamelCase+ (or another case module)
|
|
9
|
+
# is impractical, such as in a Rails initializer, another gem, or shared code. Refinements remain
|
|
10
|
+
# available for lexically scoped +to_*+ / +with_*_keys+ instance methods.
|
|
3
11
|
require "keycase/version"
|
|
4
12
|
require "keycase/support/errors"
|
|
5
13
|
require "keycase/support/transformer"
|
|
6
14
|
require "keycase/support/tokenizer"
|
|
7
15
|
require "keycase/camel_case"
|
|
8
16
|
require "keycase/kebab_case"
|
|
9
|
-
require "keycase/pascal_case"
|
|
10
|
-
require "keycase/screaming_snake_case"
|
|
11
17
|
require "keycase/snake_case"
|
|
12
|
-
require "keycase/train_case"
|
|
13
18
|
|
|
14
19
|
module Keycase
|
|
15
20
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: keycase
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- naoigcat
|
|
@@ -11,7 +11,7 @@ date: 1980-01-02 00:00:00.000000000 Z
|
|
|
11
11
|
dependencies: []
|
|
12
12
|
description: |2
|
|
13
13
|
This gem converts the case of strings, symbols, and keys of hash recursively.
|
|
14
|
-
The convertible cases are camelCase,
|
|
14
|
+
The convertible cases are camelCase, snake_case, kebab-case.
|
|
15
15
|
email:
|
|
16
16
|
- 17925623+naoigcat@users.noreply.github.com
|
|
17
17
|
executables: []
|
|
@@ -24,13 +24,17 @@ files:
|
|
|
24
24
|
- lib/keycase.rb
|
|
25
25
|
- lib/keycase/camel_case.rb
|
|
26
26
|
- lib/keycase/kebab_case.rb
|
|
27
|
-
- lib/keycase/pascal_case.rb
|
|
28
|
-
- lib/keycase/screaming_snake_case.rb
|
|
29
27
|
- lib/keycase/snake_case.rb
|
|
28
|
+
- lib/keycase/support/acronyms.rb
|
|
30
29
|
- lib/keycase/support/errors.rb
|
|
30
|
+
- lib/keycase/support/struct_instantiation.rb
|
|
31
|
+
- lib/keycase/support/structure_keys.rb
|
|
31
32
|
- lib/keycase/support/tokenizer.rb
|
|
32
33
|
- lib/keycase/support/transformer.rb
|
|
33
|
-
- lib/keycase/
|
|
34
|
+
- lib/keycase/support/transformer/collision.rb
|
|
35
|
+
- lib/keycase/support/transformer/only_normalization.rb
|
|
36
|
+
- lib/keycase/support/transformer/structure_builders.rb
|
|
37
|
+
- lib/keycase/support/transformer/visit.rb
|
|
34
38
|
- lib/keycase/version.rb
|
|
35
39
|
homepage: https://github.com/naoigcat/ruby-keycase
|
|
36
40
|
licenses:
|