dev_suite 0.2.10 → 0.2.12
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.codeclimate.yml +49 -7
- data/Gemfile +4 -0
- data/Gemfile.lock +14 -1
- data/README.md +4 -0
- data/examples/setup.rb +3 -0
- data/examples/workflow/conditional_workflow.rb +1 -1
- data/examples/workflow/full_workflow.rb +2 -2
- data/examples/workflow/loop_workflow.rb +1 -1
- data/examples/workflow/order_processing_workflow.rb +13 -11
- data/lib/dev_suite/method_tracer/logger.rb +52 -21
- data/lib/dev_suite/utils/color/colorizer.rb +1 -1
- data/lib/dev_suite/utils/color/palette/default.rb +33 -0
- data/lib/dev_suite/utils/construct/component/manager.rb +6 -1
- data/lib/dev_suite/utils/data/path_access/data_traverser.rb +86 -0
- data/lib/dev_suite/utils/data/path_access/errors.rb +12 -0
- data/lib/dev_suite/utils/data/path_access/key_handler.rb +27 -0
- data/lib/dev_suite/utils/data/path_access/path_accessor.rb +36 -0
- data/lib/dev_suite/utils/data/path_access/path_parser.rb +35 -0
- data/lib/dev_suite/utils/data/path_access.rb +5 -151
- data/lib/dev_suite/utils/file_writer/file_writer.rb +14 -4
- data/lib/dev_suite/utils/file_writer/writer/base.rb +40 -15
- data/lib/dev_suite/utils/file_writer/writer/json.rb +1 -1
- data/lib/dev_suite/utils/file_writer/writer/text.rb +11 -9
- data/lib/dev_suite/utils/file_writer/writer/yaml.rb +1 -1
- data/lib/dev_suite/utils/file_writer/writer_manager.rb +40 -17
- data/lib/dev_suite/utils/logger/base.rb +54 -0
- data/lib/dev_suite/utils/logger/emoji.rb +17 -0
- data/lib/dev_suite/utils/logger/errors.rb +9 -0
- data/lib/dev_suite/utils/logger/formatter.rb +41 -0
- data/lib/dev_suite/utils/logger/logger.rb +29 -0
- data/lib/dev_suite/utils/logger.rb +1 -52
- data/lib/dev_suite/utils/store/store.rb +3 -1
- data/lib/dev_suite/utils/table/renderer/simple.rb +5 -5
- data/lib/dev_suite/version.rb +1 -1
- metadata +13 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 89babbbfc8efcac3d35e1f60db7c5c880183eb7b77f5d43cd0a9380081815795
|
4
|
+
data.tar.gz: 34000a8d5129edf959325eaf30244333484128f399a72cc07e91329597a719fe
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9aef9871a87ca4f4c076ad72191a179dc894acf9667b5ef1f1ca52389ce0c08dda75477d5f59fc611bbe2b0c6c26ef19df8a9ec0f4bfe1b025d83809390d6c1c
|
7
|
+
data.tar.gz: 0f99342d6399fde0bd6c7f3104bbcd490b64ae67f450d706f4518057c2534110660e420a24f2d38b5481a17757a97092e79f9d6bca450ea0b88273eeae959dda
|
data/.codeclimate.yml
CHANGED
@@ -1,12 +1,54 @@
|
|
1
1
|
version: "2"
|
2
2
|
checks:
|
3
|
-
|
3
|
+
argument-count:
|
4
4
|
enabled: true
|
5
|
-
|
5
|
+
complexity:
|
6
6
|
enabled: true
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
duplication:
|
8
|
+
enabled: true
|
9
|
+
file-lines:
|
10
|
+
enabled: true
|
11
|
+
method-lines:
|
12
|
+
enabled: true
|
13
|
+
return-statements:
|
14
|
+
enabled: true
|
15
|
+
similar-code:
|
16
|
+
enabled: true
|
17
|
+
npath-complexity:
|
18
|
+
enabled: true
|
19
|
+
identical-code:
|
20
|
+
enabled: true
|
21
|
+
|
22
|
+
engines:
|
23
|
+
rubocop:
|
24
|
+
enabled: true
|
25
|
+
channel: rubocop-1-48-1
|
26
|
+
config:
|
27
|
+
file: .rubocop.yml
|
28
|
+
bundler-audit:
|
29
|
+
enabled: true
|
30
|
+
channel: bundler-audit-0-9-1
|
31
|
+
duplication:
|
32
|
+
enabled: true
|
33
|
+
config:
|
34
|
+
languages:
|
35
|
+
ruby:
|
36
|
+
mass_threshold: 50
|
37
|
+
paths:
|
38
|
+
- "lib/**/*.rb"
|
39
|
+
fixme:
|
40
|
+
enabled: false
|
41
|
+
config:
|
42
|
+
strings:
|
43
|
+
- "TODO"
|
44
|
+
- "FIXME"
|
45
|
+
|
10
46
|
exclude_patterns:
|
11
|
-
- "
|
12
|
-
- "
|
47
|
+
- "spec/"
|
48
|
+
- "test/"
|
49
|
+
- "vendor/"
|
50
|
+
- "bin/"
|
51
|
+
- "log/"
|
52
|
+
- "tmp/"
|
53
|
+
- "coverage/"
|
54
|
+
- "examples/"
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
dev_suite (0.2.
|
4
|
+
dev_suite (0.2.12)
|
5
5
|
benchmark (~> 0.1)
|
6
6
|
csv (~> 3.0)
|
7
7
|
get_process_mem (~> 1.0)
|
@@ -10,11 +10,16 @@ PATH
|
|
10
10
|
GEM
|
11
11
|
remote: https://rubygems.org/
|
12
12
|
specs:
|
13
|
+
addressable (2.8.7)
|
14
|
+
public_suffix (>= 2.0.2, < 7.0)
|
13
15
|
ast (2.4.2)
|
14
16
|
benchmark (0.3.0)
|
15
17
|
bigdecimal (3.1.8)
|
16
18
|
coderay (1.1.3)
|
17
19
|
concurrent-ruby (1.3.4)
|
20
|
+
crack (1.0.0)
|
21
|
+
bigdecimal
|
22
|
+
rexml
|
18
23
|
csv (3.3.0)
|
19
24
|
diff-lcs (1.5.1)
|
20
25
|
docile (1.4.1)
|
@@ -47,6 +52,7 @@ GEM
|
|
47
52
|
get_process_mem (1.0.0)
|
48
53
|
bigdecimal (>= 2.0)
|
49
54
|
ffi (~> 1.0)
|
55
|
+
hashdiff (1.1.1)
|
50
56
|
i18n (1.14.5)
|
51
57
|
concurrent-ruby (~> 1.0)
|
52
58
|
json (2.7.2)
|
@@ -60,10 +66,12 @@ GEM
|
|
60
66
|
pry (0.14.2)
|
61
67
|
coderay (~> 1.1)
|
62
68
|
method_source (~> 1.0)
|
69
|
+
public_suffix (6.0.1)
|
63
70
|
racc (1.8.1)
|
64
71
|
rainbow (3.1.1)
|
65
72
|
rake (13.2.1)
|
66
73
|
regexp_parser (2.9.2)
|
74
|
+
rexml (3.3.8)
|
67
75
|
rspec (3.13.0)
|
68
76
|
rspec-core (~> 3.13.0)
|
69
77
|
rspec-expectations (~> 3.13.0)
|
@@ -101,6 +109,10 @@ GEM
|
|
101
109
|
simplecov_json_formatter (0.1.4)
|
102
110
|
thor (1.3.2)
|
103
111
|
unicode-display_width (2.5.0)
|
112
|
+
webmock (3.24.0)
|
113
|
+
addressable (>= 2.8.0)
|
114
|
+
crack (>= 0.3.2)
|
115
|
+
hashdiff (>= 0.4.0, < 2.0.0)
|
104
116
|
|
105
117
|
PLATFORMS
|
106
118
|
arm64-darwin-23
|
@@ -115,6 +127,7 @@ DEPENDENCIES
|
|
115
127
|
rubocop (~> 1.65)
|
116
128
|
rubocop-shopify (~> 2.15)
|
117
129
|
simplecov (~> 0.21)
|
130
|
+
webmock (~> 3.13)
|
118
131
|
|
119
132
|
BUNDLED WITH
|
120
133
|
2.5.17
|
data/README.md
CHANGED
@@ -428,6 +428,10 @@ Trace all method calls within a specific block of code, including optional loggi
|
|
428
428
|
🏁 #depth:1 < MathOperations#greet #=> "Hello, Ruby!" at (irb):12 in 0.02ms
|
429
429
|
Hello, Ruby!
|
430
430
|
```
|
431
|
+
|
432
|
+
**Tips**:
|
433
|
+
|
434
|
+
Use `max_depth` to Limit Tracing: If you have deeply nested method calls, use the `max_depth` option to limit the depth of tracing. This can help reduce the amount of log output and focus on the most relevant parts of your code. A recommended value for `max_depth` is between `2` and `5`, depending on the complexity of your code.
|
431
435
|
</details>
|
432
436
|
|
433
437
|
## Development
|
data/examples/setup.rb
ADDED
@@ -7,7 +7,7 @@ require "dev_suite"
|
|
7
7
|
engine = DevSuite::Workflow::Engine.new(user: "Bob", role: "admin")
|
8
8
|
|
9
9
|
# Add a conditional step
|
10
|
-
conditional_step = DevSuite::Workflow.create_conditional_step("Admin Greeting", ->(ctx) {
|
10
|
+
conditional_step = DevSuite::Workflow.create_conditional_step("Admin Greeting", condition: ->(ctx) {
|
11
11
|
ctx.get(:role) == "admin"
|
12
12
|
}) do |context|
|
13
13
|
puts "Welcome Admin, #{context.get(:user)}!"
|
@@ -20,14 +20,14 @@ greet_step = DevSuite::Workflow.create_step("Greet User") do |context|
|
|
20
20
|
end
|
21
21
|
|
22
22
|
# Step 2: Create a conditional step to greet only admins
|
23
|
-
admin_step = DevSuite::Workflow.create_conditional_step("Admin Greeting", ->(ctx) {
|
23
|
+
admin_step = DevSuite::Workflow.create_conditional_step("Admin Greeting", condition: ->(ctx) {
|
24
24
|
ctx.get(:role) == "admin"
|
25
25
|
}) do |context|
|
26
26
|
puts "Welcome, Admin #{context.get(:user)}!"
|
27
27
|
end
|
28
28
|
|
29
29
|
# Step 3: Create a loop step that repeats 3 times
|
30
|
-
loop_step = DevSuite::Workflow.create_loop_step("Loop Step", 3) do |ctx|
|
30
|
+
loop_step = DevSuite::Workflow.create_loop_step("Loop Step", iterations: 3) do |ctx|
|
31
31
|
iteration = ctx.get(:iteration_count) + 1
|
32
32
|
ctx.update({ iteration_count: iteration })
|
33
33
|
puts "Iteration #{iteration} completed."
|
@@ -7,7 +7,7 @@ require "dev_suite"
|
|
7
7
|
engine = DevSuite::Workflow::Engine.new(iteration_count: 0)
|
8
8
|
|
9
9
|
# Add a loop step
|
10
|
-
loop_step = DevSuite::Workflow.create_loop_step("Repeat Task", 3) do |context|
|
10
|
+
loop_step = DevSuite::Workflow.create_loop_step("Repeat Task", iterations: 3) do |context|
|
11
11
|
iteration = context.get(:iteration_count) + 1
|
12
12
|
context.update({ iteration_count: iteration })
|
13
13
|
puts "Iteration #{iteration}"
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "../
|
3
|
+
require_relative "../setup"
|
4
4
|
|
5
5
|
$LOAD_PATH.unshift(File.expand_path("../../lib", __dir__))
|
6
6
|
require "dev_suite"
|
@@ -95,7 +95,7 @@ register_step = DevSuite::Workflow.create_step("Register Users") do |ctx|
|
|
95
95
|
ctx.data
|
96
96
|
end
|
97
97
|
|
98
|
-
save_users_step = DevSuite::Workflow.create_conditional_step("Save Users to Store", ->(ctx) {
|
98
|
+
save_users_step = DevSuite::Workflow.create_conditional_step("Save Users to Store", condition: ->(ctx) {
|
99
99
|
ctx.get(:users)&.any?
|
100
100
|
}) do |ctx|
|
101
101
|
ctx.store.set(:users, ctx.get(:users))
|
@@ -103,7 +103,7 @@ save_users_step = DevSuite::Workflow.create_conditional_step("Save Users to Stor
|
|
103
103
|
ctx.data
|
104
104
|
end
|
105
105
|
|
106
|
-
login_step = DevSuite::Workflow.create_conditional_step("Login Users", ->(ctx) {
|
106
|
+
login_step = DevSuite::Workflow.create_conditional_step("Login Users", condition: ->(ctx) {
|
107
107
|
ctx.get(:username) && ctx.get(:password)
|
108
108
|
}) do |ctx|
|
109
109
|
response = login_user(ctx.get(:username), ctx.get(:password))
|
@@ -119,7 +119,7 @@ login_step = DevSuite::Workflow.create_conditional_step("Login Users", ->(ctx) {
|
|
119
119
|
ctx.data
|
120
120
|
end
|
121
121
|
|
122
|
-
fetch_order_step = DevSuite::Workflow.create_conditional_step("Fetch Orders", ->(ctx) {
|
122
|
+
fetch_order_step = DevSuite::Workflow.create_conditional_step("Fetch Orders", condition: ->(ctx) {
|
123
123
|
ctx.get(:token) && ctx.get(:user_id)
|
124
124
|
}) do |ctx|
|
125
125
|
response = fetch_order(ctx.get(:token), ctx.get(:user_id))
|
@@ -134,7 +134,7 @@ fetch_order_step = DevSuite::Workflow.create_conditional_step("Fetch Orders", ->
|
|
134
134
|
ctx.data
|
135
135
|
end
|
136
136
|
|
137
|
-
process_payment_step = DevSuite::Workflow.create_conditional_step("Process Payment", ->(ctx) {
|
137
|
+
process_payment_step = DevSuite::Workflow.create_conditional_step("Process Payment", condition: ->(ctx) {
|
138
138
|
ctx.get(:order_id) && ctx.get(:token) && ctx.get(:user_id)
|
139
139
|
}) do |ctx|
|
140
140
|
response = process_payment(ctx.get(:order_id), ctx.get(:token), ctx.get(:user_id))
|
@@ -155,9 +155,11 @@ workflow = DevSuite::Workflow.create_engine(
|
|
155
155
|
path: STORE_PATH,
|
156
156
|
)
|
157
157
|
|
158
|
-
|
159
|
-
.step(
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
158
|
+
DevSuite::MethodTracer.trace do
|
159
|
+
workflow.step(register_step)
|
160
|
+
.step(save_users_step)
|
161
|
+
.step(login_step)
|
162
|
+
.step(fetch_order_step)
|
163
|
+
.step(process_payment_step)
|
164
|
+
.execute
|
165
|
+
end
|
@@ -7,39 +7,70 @@ module DevSuite
|
|
7
7
|
def log_method_call(tp, tracer)
|
8
8
|
tracer.depth += 1
|
9
9
|
|
10
|
-
method_name = Helpers.format_method_name(tp)
|
11
|
-
params_str = Helpers.format_params(tp) if tracer.show_params
|
12
|
-
indent = Helpers.calculate_indent(tracer.depth)
|
13
|
-
|
14
10
|
# Store the start time for execution time calculation
|
15
11
|
tracer.start_times[tp.method_id] = Time.now
|
16
12
|
|
17
|
-
message =
|
18
|
-
Utils::Logger.log(
|
19
|
-
message,
|
20
|
-
emoji: :start,
|
21
|
-
color: :blue,
|
22
|
-
)
|
13
|
+
message = build_call_message(tp, tracer)
|
14
|
+
Utils::Logger.log(message, emoji: :start, color: :blue)
|
23
15
|
end
|
24
16
|
|
25
17
|
def log_method_return(tp, tracer)
|
26
18
|
duration = Helpers.calculate_duration(tp, tracer.start_times)
|
27
19
|
|
28
|
-
|
29
|
-
|
30
|
-
exec_time_str = Helpers.format_execution_time(duration) if tracer.show_execution_time
|
31
|
-
indent = Helpers.calculate_indent(tracer.depth)
|
32
|
-
|
33
|
-
message = "#{indent}#depth:#{tracer.depth} < #{method_name}#{result_str} at #{tp.path}:#{tp.lineno}#{exec_time_str}"
|
34
|
-
Utils::Logger.log(
|
35
|
-
message,
|
36
|
-
emoji: :finish,
|
37
|
-
color: :cyan,
|
38
|
-
)
|
20
|
+
message = build_return_message(tp, tracer, duration)
|
21
|
+
Utils::Logger.log(message, emoji: :finish, color: :cyan)
|
39
22
|
|
40
23
|
# Decrement depth safely
|
41
24
|
tracer.depth = [tracer.depth - 1, 0].max
|
42
25
|
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
# Builds the log message for method calls
|
30
|
+
#
|
31
|
+
# @param tp [TracePoint] The TracePoint object
|
32
|
+
# @param tracer [Tracer] The tracer instance
|
33
|
+
# @return [String] The formatted log message
|
34
|
+
def build_call_message(tp, tracer)
|
35
|
+
method_name = colorize_text(Helpers.format_method_name(tp), :blue)
|
36
|
+
params_str = tracer.show_params ? colorize_text(Helpers.format_params(tp), :yellow) : ""
|
37
|
+
indent = Helpers.calculate_indent(tracer.depth)
|
38
|
+
depth_str = colorize_text("#depth:#{tracer.depth}", :magenta)
|
39
|
+
|
40
|
+
"#{indent}#{depth_str} > #{method_name} at #{tp.path}:#{tp.lineno} #{params_str}"
|
41
|
+
end
|
42
|
+
|
43
|
+
# Builds the log message for method returns
|
44
|
+
#
|
45
|
+
# @param tp [TracePoint] The TracePoint object
|
46
|
+
# @param tracer [Tracer] The tracer instance
|
47
|
+
# @param duration [Float] The execution time
|
48
|
+
# @return [String] The formatted log message
|
49
|
+
def build_return_message(tp, tracer, duration)
|
50
|
+
method_name = colorize_text(Helpers.format_method_name(tp), :cyan)
|
51
|
+
result_str = tracer.show_results ? colorize_text(Helpers.format_result(tp), :green) : ""
|
52
|
+
exec_time_str = if tracer.show_execution_time
|
53
|
+
colorize_text(
|
54
|
+
Helpers.format_execution_time(duration),
|
55
|
+
:light_white,
|
56
|
+
)
|
57
|
+
else
|
58
|
+
""
|
59
|
+
end
|
60
|
+
indent = Helpers.calculate_indent(tracer.depth)
|
61
|
+
depth_str = colorize_text("#depth:#{tracer.depth}", :magenta)
|
62
|
+
|
63
|
+
"#{indent}#{depth_str} < #{method_name}#{result_str} at #{tp.path}:#{tp.lineno}#{exec_time_str}"
|
64
|
+
end
|
65
|
+
|
66
|
+
# Helper method to colorize text
|
67
|
+
#
|
68
|
+
# @param text [String] The text to colorize
|
69
|
+
# @param color [Symbol] The color to use
|
70
|
+
# @return [String] The colorized text
|
71
|
+
def colorize_text(text, color)
|
72
|
+
Utils::Color.colorize(text, color: color)
|
73
|
+
end
|
43
74
|
end
|
44
75
|
end
|
45
76
|
end
|
@@ -6,6 +6,8 @@ module DevSuite
|
|
6
6
|
module Palette
|
7
7
|
class Default < Base
|
8
8
|
COLORS = {
|
9
|
+
# Standard Colors
|
10
|
+
black: 30,
|
9
11
|
red: 31,
|
10
12
|
green: 32,
|
11
13
|
yellow: 33,
|
@@ -14,6 +16,37 @@ module DevSuite
|
|
14
16
|
cyan: 36,
|
15
17
|
white: 37,
|
16
18
|
default: 39,
|
19
|
+
|
20
|
+
# Light Colors
|
21
|
+
light_black: 90,
|
22
|
+
light_red: 91,
|
23
|
+
light_green: 92,
|
24
|
+
light_yellow: 93,
|
25
|
+
light_blue: 94,
|
26
|
+
light_magenta: 95,
|
27
|
+
light_cyan: 96,
|
28
|
+
light_white: 97,
|
29
|
+
|
30
|
+
# Background Colors
|
31
|
+
bg_black: 40,
|
32
|
+
bg_red: 41,
|
33
|
+
bg_green: 42,
|
34
|
+
bg_yellow: 43,
|
35
|
+
bg_blue: 44,
|
36
|
+
bg_magenta: 45,
|
37
|
+
bg_cyan: 46,
|
38
|
+
bg_white: 47,
|
39
|
+
bg_default: 49,
|
40
|
+
|
41
|
+
# Light Background Colors
|
42
|
+
bg_light_black: 100,
|
43
|
+
bg_light_red: 101,
|
44
|
+
bg_light_green: 102,
|
45
|
+
bg_light_yellow: 103,
|
46
|
+
bg_light_blue: 104,
|
47
|
+
bg_light_magenta: 105,
|
48
|
+
bg_light_cyan: 106,
|
49
|
+
bg_light_white: 107,
|
17
50
|
}.freeze
|
18
51
|
end
|
19
52
|
end
|
@@ -32,7 +32,12 @@ module DevSuite
|
|
32
32
|
|
33
33
|
raise ArgumentError, "Component not found for key: #{component_key}" unless component_class
|
34
34
|
|
35
|
-
|
35
|
+
# Check if options are empty to avoid passing unnecessary keyword arguments in Ruby 2.6
|
36
|
+
if options.empty?
|
37
|
+
component_class.new(&block)
|
38
|
+
else
|
39
|
+
component_class.new(**options, &block)
|
40
|
+
end
|
36
41
|
end
|
37
42
|
|
38
43
|
# Build multiple components by filtering registered ones
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "key_handler"
|
4
|
+
|
5
|
+
module DevSuite
|
6
|
+
module Utils
|
7
|
+
module Data
|
8
|
+
module PathAccess
|
9
|
+
module DataTraverser
|
10
|
+
extend self
|
11
|
+
|
12
|
+
# Traverse nested data for retrieving a value
|
13
|
+
def fetch(data, keys)
|
14
|
+
keys.reduce(data) { |current, key| traverse_data(current, key) }
|
15
|
+
end
|
16
|
+
|
17
|
+
# Traverse nested data for setting a value
|
18
|
+
def assign(data, keys, value)
|
19
|
+
last_key = keys.pop
|
20
|
+
target = keys.reduce(data) { |current, key| traverse_or_create(current, key) }
|
21
|
+
set_final_value(target, last_key, value)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Traverse nested data for deleting a value
|
25
|
+
def remove(data, keys)
|
26
|
+
last_key = keys.pop
|
27
|
+
target = keys.reduce(data) { |current, key| traverse_data(current, key) }
|
28
|
+
delete_final_value(target, last_key)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
# Traverse through hash or array data types
|
34
|
+
def traverse_data(current, key)
|
35
|
+
KeyHandler.validate_path(current, key)
|
36
|
+
case current
|
37
|
+
when Hash
|
38
|
+
current[KeyHandler.find_key(current, key)]
|
39
|
+
when Array
|
40
|
+
current[key.to_i]
|
41
|
+
else
|
42
|
+
raise DataStructureError, "Unexpected data type at '#{key}'"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Traverse or create new hash elements when setting a value
|
47
|
+
def traverse_or_create(current, key)
|
48
|
+
KeyHandler.validate_path(current, key)
|
49
|
+
case current
|
50
|
+
when Hash
|
51
|
+
current[KeyHandler.find_key(current, key)] ||= {}
|
52
|
+
when Array
|
53
|
+
current[key.to_i] ||= {}
|
54
|
+
else
|
55
|
+
raise DataStructureError, "Unexpected data type at '#{key}'"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Set the final value in hash or array
|
60
|
+
def set_final_value(target, key, value)
|
61
|
+
case target
|
62
|
+
when Hash
|
63
|
+
target[KeyHandler.find_key(target, key)] = value
|
64
|
+
when Array
|
65
|
+
target[key] = value if key.is_a?(Integer)
|
66
|
+
else
|
67
|
+
raise DataStructureError, "Cannot set value on unsupported data type"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Delete the final value from hash or array
|
72
|
+
def delete_final_value(target, key)
|
73
|
+
case target
|
74
|
+
when Hash
|
75
|
+
target.delete(KeyHandler.find_key(target, key))
|
76
|
+
when Array
|
77
|
+
target.delete_at(key) if key.is_a?(Integer)
|
78
|
+
else
|
79
|
+
raise DataStructureError, "Cannot delete key from unsupported data type"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DevSuite
|
4
|
+
module Utils
|
5
|
+
module Data
|
6
|
+
module PathAccess
|
7
|
+
module KeyHandler
|
8
|
+
extend self
|
9
|
+
|
10
|
+
# Find an existing key in a hash, handling both symbol and string keys
|
11
|
+
def find_key(hash, key)
|
12
|
+
return key if hash.key?(key)
|
13
|
+
return key.to_s if hash.key?(key.to_s)
|
14
|
+
return key.to_sym if hash.key?(key.to_sym)
|
15
|
+
|
16
|
+
key
|
17
|
+
end
|
18
|
+
|
19
|
+
# Validate the path by checking if it's pointing to a valid data structure
|
20
|
+
def validate_path(data, key)
|
21
|
+
raise InvalidPathError, "Invalid path at '#{key}'" if data.nil?
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "path_parser"
|
4
|
+
require_relative "data_traverser"
|
5
|
+
require_relative "key_handler"
|
6
|
+
require_relative "errors"
|
7
|
+
|
8
|
+
module DevSuite
|
9
|
+
module Utils
|
10
|
+
module Data
|
11
|
+
module PathAccess
|
12
|
+
module PathAccessor
|
13
|
+
extend self
|
14
|
+
|
15
|
+
# Get value from nested data
|
16
|
+
def get(data, path)
|
17
|
+
keys = PathParser.parse(path)
|
18
|
+
DataTraverser.fetch(data, keys)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Set value in nested data
|
22
|
+
def set(data, path, value)
|
23
|
+
keys = PathParser.parse(path)
|
24
|
+
DataTraverser.assign(data, keys, value)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Delete key in nested data
|
28
|
+
def delete(data, path)
|
29
|
+
keys = PathParser.parse(path)
|
30
|
+
DataTraverser.remove(data, keys)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DevSuite
|
4
|
+
module Utils
|
5
|
+
module Data
|
6
|
+
module PathAccess
|
7
|
+
module PathParser
|
8
|
+
extend self
|
9
|
+
|
10
|
+
def parse(path)
|
11
|
+
return path if path.is_a?(Array)
|
12
|
+
return parse_symbol_path(path) if path.is_a?(Symbol)
|
13
|
+
return parse_string_path(path) if path.is_a?(String)
|
14
|
+
|
15
|
+
[path]
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def parse_symbol_path(symbol_path)
|
21
|
+
symbol_path.to_s.split(/\.|\[|\]/).reject(&:empty?).map { |part| parse_part(part) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def parse_string_path(string_path)
|
25
|
+
string_path.split(/\.|\[|\]/).reject(&:empty?).map { |part| parse_part(part) }
|
26
|
+
end
|
27
|
+
|
28
|
+
def parse_part(part)
|
29
|
+
part.match?(/^\d+$/) ? part.to_i : part.to_sym
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|