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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +49 -7
  3. data/Gemfile +4 -0
  4. data/Gemfile.lock +14 -1
  5. data/README.md +4 -0
  6. data/examples/setup.rb +3 -0
  7. data/examples/workflow/conditional_workflow.rb +1 -1
  8. data/examples/workflow/full_workflow.rb +2 -2
  9. data/examples/workflow/loop_workflow.rb +1 -1
  10. data/examples/workflow/order_processing_workflow.rb +13 -11
  11. data/lib/dev_suite/method_tracer/logger.rb +52 -21
  12. data/lib/dev_suite/utils/color/colorizer.rb +1 -1
  13. data/lib/dev_suite/utils/color/palette/default.rb +33 -0
  14. data/lib/dev_suite/utils/construct/component/manager.rb +6 -1
  15. data/lib/dev_suite/utils/data/path_access/data_traverser.rb +86 -0
  16. data/lib/dev_suite/utils/data/path_access/errors.rb +12 -0
  17. data/lib/dev_suite/utils/data/path_access/key_handler.rb +27 -0
  18. data/lib/dev_suite/utils/data/path_access/path_accessor.rb +36 -0
  19. data/lib/dev_suite/utils/data/path_access/path_parser.rb +35 -0
  20. data/lib/dev_suite/utils/data/path_access.rb +5 -151
  21. data/lib/dev_suite/utils/file_writer/file_writer.rb +14 -4
  22. data/lib/dev_suite/utils/file_writer/writer/base.rb +40 -15
  23. data/lib/dev_suite/utils/file_writer/writer/json.rb +1 -1
  24. data/lib/dev_suite/utils/file_writer/writer/text.rb +11 -9
  25. data/lib/dev_suite/utils/file_writer/writer/yaml.rb +1 -1
  26. data/lib/dev_suite/utils/file_writer/writer_manager.rb +40 -17
  27. data/lib/dev_suite/utils/logger/base.rb +54 -0
  28. data/lib/dev_suite/utils/logger/emoji.rb +17 -0
  29. data/lib/dev_suite/utils/logger/errors.rb +9 -0
  30. data/lib/dev_suite/utils/logger/formatter.rb +41 -0
  31. data/lib/dev_suite/utils/logger/logger.rb +29 -0
  32. data/lib/dev_suite/utils/logger.rb +1 -52
  33. data/lib/dev_suite/utils/store/store.rb +3 -1
  34. data/lib/dev_suite/utils/table/renderer/simple.rb +5 -5
  35. data/lib/dev_suite/version.rb +1 -1
  36. metadata +13 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 92ff382f54afdf8f68f1f3d5f2f3bec8c496138b0af875b0970b2cceaec24e36
4
- data.tar.gz: 743a74d46e4448c8e363d7b00e501bd1b2740eb5a10eb1bd019432cb01afd4ea
3
+ metadata.gz: 89babbbfc8efcac3d35e1f60db7c5c880183eb7b77f5d43cd0a9380081815795
4
+ data.tar.gz: 34000a8d5129edf959325eaf30244333484128f399a72cc07e91329597a719fe
5
5
  SHA512:
6
- metadata.gz: 7dffd5d2cf8ca2f1712e4e9599e5432666e654a8b6c5696fc185a7898342f574018c2d1311fd96a832a956f4c3aacf1023a7c3ffd83523c3094fb8823b3133f2
7
- data.tar.gz: db80d0b5c8d9f425ac520b59b6b0988bf5dabadaaaff2647312511aa47bb5eaf738661ec6efe6253028d031baedd49012b910cd0bda65dac6872eaa7c314affe
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
- argument_count:
3
+ argument-count:
4
4
  enabled: true
5
- file_lines:
5
+ complexity:
6
6
  enabled: true
7
- ratings:
8
- paths:
9
- - "**.rb"
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
- - "examples/**" # Ignore examples folder
12
- - "tmp/**" # Ignore temp files
47
+ - "spec/"
48
+ - "test/"
49
+ - "vendor/"
50
+ - "bin/"
51
+ - "log/"
52
+ - "tmp/"
53
+ - "coverage/"
54
+ - "examples/"
data/Gemfile CHANGED
@@ -21,3 +21,7 @@ group :development do
21
21
  gem "rubocop", "~> 1.65", require: false
22
22
  gem "rubocop-shopify", "~> 2.15", require: false
23
23
  end
24
+
25
+ group :test do
26
+ gem "webmock", "~> 3.13"
27
+ end
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- dev_suite (0.2.10)
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
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "helpers/helpers"
@@ -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 "../helpers"
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
- workflow.step(register_step)
159
- .step(save_users_step)
160
- .step(login_step)
161
- .step(fetch_order_step)
162
- .step(process_payment_step)
163
- .execute
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 = "#{indent}#depth:#{tracer.depth} > #{method_name} at #{tp.path}:#{tp.lineno} #{params_str}"
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
- method_name = Helpers.format_method_name(tp)
29
- result_str = Helpers.format_result(tp) if tracer.show_results
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
@@ -5,7 +5,7 @@ module DevSuite
5
5
  module Color
6
6
  class Colorizer
7
7
  def colorize(text, **kargs)
8
- puts Config.configuration.strategy.colorize(text, **kargs)
8
+ Config.configuration.strategy.colorize(text, **kargs)
9
9
  end
10
10
  end
11
11
 
@@ -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
- component_class.new(**options, &block)
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,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DevSuite
4
+ module Utils
5
+ module Data
6
+ module PathAccess
7
+ class InvalidPathError < StandardError; end
8
+ class DataStructureError < StandardError; end
9
+ end
10
+ end
11
+ end
12
+ 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