dev_suite 0.2.10 → 0.2.12
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/.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
|