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.
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