dev_suite 0.2.11 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3aa7bc365cd71c523150cc3dde4eb92eb84c7c7b3cff4435ac373474f7d73871
4
- data.tar.gz: 7ec2c14843c493cfa0ef819350463e994344e407a248a5d83f317ec6d9c021ff
3
+ metadata.gz: 89babbbfc8efcac3d35e1f60db7c5c880183eb7b77f5d43cd0a9380081815795
4
+ data.tar.gz: 34000a8d5129edf959325eaf30244333484128f399a72cc07e91329597a719fe
5
5
  SHA512:
6
- metadata.gz: e86091d8c8ca69fa6dec4a1a8a7b5403671e7fcd8a08db8a364b71a473704c613d8135a70645e990741e8a23b00cac8ffdab1cb419c2582e9b75476829842a9d
7
- data.tar.gz: 6fbbf0e0cb281dd9ead32c6a4eeeeec414d3cf2216865f2c3c01908e3d25eb946b9de82de68002c34d170e05bd7038cbe2fae7ba86b88a5c78de412e49dff279
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.11)
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
@@ -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
@@ -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
@@ -4,167 +4,21 @@ module DevSuite
4
4
  module Utils
5
5
  module Data
6
6
  module PathAccess
7
+ require_relative "path_access/path_accessor"
8
+
7
9
  # Fetch value from a nested structure using a path
8
10
  def get_value_by_path(data, path)
9
- keys = parse_path(path)
10
- traverse_path_for_get(data, keys)
11
+ PathAccessor.get(data, path)
11
12
  end
12
13
 
13
14
  # Set value in a nested structure using a path
14
15
  def set_value_by_path(data, path, value)
15
- keys = parse_path(path)
16
- last_key = keys.pop
17
- target = traverse_path_for_set(data, keys)
18
-
19
- set_final_value(target, last_key, value)
16
+ PathAccessor.set(data, path, value)
20
17
  end
21
18
 
22
19
  # Delete a key from a nested structure using a path
23
20
  def delete_key_by_path(data, path)
24
- keys = parse_path(path)
25
- last_key = keys.pop
26
- target = traverse_path_for_delete(data, keys)
27
-
28
- delete_final_key(target, last_key)
29
- end
30
-
31
- private
32
-
33
- # Parse the path into an array of keys/symbols/integers
34
- def parse_path(path)
35
- return path if path.is_a?(Array)
36
- return [path] if single_symbol_path?(path)
37
-
38
- parse_symbol_or_string_path(path)
39
- end
40
-
41
- # Check if the path is a symbol without dots
42
- def single_symbol_path?(path)
43
- path.is_a?(Symbol) && !path.to_s.include?(".")
44
- end
45
-
46
- # Parse a symbol or string path into an array of keys
47
- def parse_symbol_or_string_path(path)
48
- if path.is_a?(Symbol)
49
- parse_symbol_path(path)
50
- else
51
- parse_string_path(path)
52
- end
53
- end
54
-
55
- # Parse a symbol path that contains dots (e.g., :"test.1")
56
- def parse_symbol_path(path)
57
- path.to_s.split(".").map { |part| parse_part(part) }
58
- end
59
-
60
- # Parse a string path into keys (e.g., 'users.1.name')
61
- def parse_string_path(path)
62
- path.to_s.split(/\.|\[|\]/).reject(&:empty?).map { |part| parse_part(part) }
63
- end
64
-
65
- # Parse each part into either a symbol or integer
66
- def parse_part(part)
67
- part.match?(/^\d+$/) ? part.to_i : part.to_sym
68
- end
69
-
70
- # Helper to traverse the path for getting values
71
- def traverse_path_for_get(data, keys)
72
- keys.reduce(data) do |current_data, key|
73
- check_invalid_path(current_data, key)
74
-
75
- case current_data
76
- when Hash
77
- fetch_from_hash(current_data, key)
78
- when Array
79
- fetch_from_array(current_data, key)
80
- else
81
- raise KeyError, "Invalid data type at '#{key}'"
82
- end
83
- end
84
- end
85
-
86
- # Helper to traverse the path for setting values
87
- def traverse_path_for_set(data, keys)
88
- keys.reduce(data) do |current_data, key|
89
- check_invalid_path(current_data, key)
90
-
91
- case current_data
92
- when Hash
93
- current_data[find_existing_key(current_data, key)] ||= {}
94
- when Array
95
- current_data[key.to_i] ||= {}
96
- else
97
- break nil
98
- end
99
- end
100
- end
101
-
102
- # Helper to check for invalid paths
103
- def check_invalid_path(data, key)
104
- raise KeyError, "Invalid path at '#{key}'" if data.nil?
105
- end
106
-
107
- # Fetch value from a hash, trying both symbol and string keys
108
- def fetch_from_hash(hash, key)
109
- hash[key.to_sym] || hash[key.to_s]
110
- end
111
-
112
- # Fetch value from an array if the key is an integer
113
- def fetch_from_array(array, key)
114
- key.is_a?(Integer) ? array[key] : nil
115
- end
116
-
117
- # Set the final value in a hash or array
118
- def set_final_value(target, last_key, value)
119
- case target
120
- when Hash
121
- target[find_existing_key(target, last_key)] = value
122
- when Array
123
- if last_key.is_a?(Integer)
124
- target[last_key] = value
125
- else
126
- raise KeyError, "Invalid path or type at '#{last_key}'"
127
- end
128
- else
129
- raise KeyError, "Invalid target type for path at '#{last_key}'"
130
- end
131
- end
132
-
133
- # Check if the key already exists and return the key in its original type (symbol or string)
134
- def find_existing_key(hash, key)
135
- return key if hash.key?(key) # Key already exists in original form
136
- return key.to_s if hash.key?(key.to_s) # Exists as a string
137
- return key.to_sym if hash.key?(key.to_sym) # Exists as a symbol
138
-
139
- key # Otherwise, return the key as-is (use the incoming type)
140
- end
141
-
142
- # Helper to traverse the path for deletion
143
- def traverse_path_for_delete(data, keys)
144
- keys.reduce(data) do |current_data, key|
145
- check_invalid_path(current_data, key)
146
-
147
- case current_data
148
- when Hash
149
- current_data[find_existing_key(current_data, key)]
150
- when Array
151
- current_data[key.to_i]
152
- else
153
- raise KeyError, "Invalid data type at '#{key}'"
154
- end
155
- end
156
- end
157
-
158
- # Helper to delete the final key in a hash or array
159
- def delete_final_key(target, last_key)
160
- case target
161
- when Hash
162
- target.delete(find_existing_key(target, last_key))
163
- when Array
164
- target.delete_at(last_key.to_i) if last_key.is_a?(Integer)
165
- else
166
- raise KeyError, "Cannot delete key from unsupported data type"
167
- end
21
+ PathAccessor.delete(data, path)
168
22
  end
169
23
  end
170
24
  end
@@ -10,13 +10,23 @@ module DevSuite
10
10
 
11
11
  class << self
12
12
  def write(path, content)
13
- writer = WriterManager.new
14
- writer.write(path, content)
13
+ WriterManager.write(path, content)
14
+ end
15
+
16
+ def append(path, content)
17
+ WriterManager.append(path, content)
18
+ end
19
+
20
+ def delete_lines(path, start_line, end_line = start_line)
21
+ WriterManager.delete_lines(path, start_line, end_line)
15
22
  end
16
23
 
17
24
  def update_key(path, key, value)
18
- writer = WriterManager.new
19
- writer.update_key(path, key, value)
25
+ WriterManager.update_key(path, key, value)
26
+ end
27
+
28
+ def delete_key(path, key)
29
+ WriterManager.delete_key(path, key)
20
30
  end
21
31
  end
22
32
  end
@@ -5,21 +5,51 @@ module DevSuite
5
5
  module FileWriter
6
6
  module Writer
7
7
  class Base < Utils::Construct::Component::Base
8
- # Abstract method that subclasses must implement for custom file write operations
9
- def write(_path, _content)
10
- raise NotImplementedError, "Subclasses must implement the `write` method"
8
+ attr_reader :path
9
+
10
+ def initialize(path)
11
+ super()
12
+ @path = path
13
+ end
14
+
15
+ def read
16
+ FileLoader.load(path)
11
17
  end
12
18
 
13
- # General method to update a key in any structured file
14
- def update_key(path, key, value, **options)
15
- # Step 1: Load the existing content of the file (this will be subclass-specific)
16
- content = load_file_content(path)
19
+ def write(content, backup: false)
20
+ create_backup if backup
21
+ AtomicWriter.new(path, content).write
22
+ end
17
23
 
18
- # Step 2: Use a utility function to modify the content at the specified key path
24
+ def append(content)
25
+ current_content = read
26
+ updated_content = current_content.merge(content)
27
+ write(updated_content)
28
+ end
29
+
30
+ def delete_lines(start_line, end_line = start_line)
31
+ lines = ::File.readlines(path)
32
+ lines.slice!(start_line - 1, end_line - start_line + 1)
33
+ write(lines.join)
34
+ end
35
+
36
+ def update_key(key, value, **options)
37
+ content = read
19
38
  Utils::Data.set_value_by_path(content, key, value)
39
+ write(content, **options)
40
+ end
41
+
42
+ def delete_key(key, **options)
43
+ content = read
44
+ Utils::Data.delete_key_by_path(content, key)
45
+ write(content, **options)
46
+ end
20
47
 
21
- # Step 3: Write the updated content back to the file using the subclass's write method
22
- write(path, content, **options)
48
+ def append_array(array_key, new_elements)
49
+ data = read
50
+ data[array_key] ||= []
51
+ data[array_key].concat(new_elements)
52
+ write(data)
23
53
  end
24
54
 
25
55
  private
@@ -28,11 +58,6 @@ module DevSuite
28
58
  ::File.exist?(path)
29
59
  end
30
60
 
31
- # Load the file content using a file loader
32
- def load_file_content(file_path)
33
- FileLoader.load(file_path)
34
- end
35
-
36
61
  def create_backup(path)
37
62
  BackupManager.new(path).create_backup if file_exists?(path)
38
63
  end
@@ -5,7 +5,7 @@ module DevSuite
5
5
  module FileWriter
6
6
  module Writer
7
7
  class Json < Base
8
- def write(path, content, pretty: false, backup: false)
8
+ def write(content, pretty: false, backup: false)
9
9
  create_backup(path) if backup
10
10
 
11
11
  json_content = convert_to_json(content, pretty)
@@ -5,20 +5,22 @@ module DevSuite
5
5
  module FileWriter
6
6
  module Writer
7
7
  class Text < Base
8
- def write(path, content, backup: false)
9
- create_backup(path) if backup
10
-
11
- AtomicWriter.new(path, content).write
12
- end
13
-
14
8
  # Updates a key-like pattern in a plain text file (find and replace)
15
- def update_key(path, key, value, backup: false)
16
- content = load_file_content(path)
9
+ def update_key(key, value, backup: false)
10
+ content = read
17
11
 
18
12
  # Simple pattern matching and replacement (e.g., `key: value`)
19
13
  updated_content = content.gsub(/^#{Regexp.escape(key)}:.*/, "#{key}: #{value}")
20
14
 
21
- write(path, updated_content, backup: backup)
15
+ write(updated_content, backup: backup)
16
+ end
17
+
18
+ def append(content)
19
+ ::File.open(path, "a") do |file|
20
+ file.write("\n") if ::File.size(path).nonzero?
21
+
22
+ file.write(content.strip)
23
+ end
22
24
  end
23
25
  end
24
26
  end
@@ -5,7 +5,7 @@ module DevSuite
5
5
  module FileWriter
6
6
  module Writer
7
7
  class Yaml < Base
8
- def write(path, content, normalize: false, backup: false, yaml_options: {})
8
+ def write(content, normalize: false, backup: false, yaml_options: {})
9
9
  validate_content(content)
10
10
  create_backup(path) if backup
11
11
 
@@ -3,27 +3,50 @@
3
3
  module DevSuite
4
4
  module Utils
5
5
  module FileWriter
6
+ class UnsupportedFileTypeError < StandardError; end
7
+
6
8
  class WriterManager
7
- def write(path, content)
8
- writer = writer_for(path)
9
- writer.write(path, content)
10
- end
9
+ WRITERS = {
10
+ ".json" => Writer::Json,
11
+ ".yml" => Writer::Yaml,
12
+ ".yaml" => Writer::Yaml,
13
+ ".txt" => Writer::Text,
14
+ }.freeze
11
15
 
12
- def update_key(path, key, value)
13
- writer = writer_for(path)
14
- writer.update_key(path, key, value)
15
- end
16
+ class << self
17
+ def write(path, content)
18
+ writer_instance(path).write(content)
19
+ end
20
+
21
+ def append(path, content)
22
+ writer_instance(path).append(content)
23
+ end
24
+
25
+ def delete_lines(path, start_line, end_line = start_line)
26
+ writer_instance(path).delete_lines(start_line, end_line)
27
+ end
16
28
 
17
- private
29
+ def update_key(path, key, value)
30
+ writer_instance(path).update_key(key, value)
31
+ end
32
+
33
+ def delete_key(path, key)
34
+ writer_instance(path).delete_key(key)
35
+ end
36
+
37
+ private
38
+
39
+ # Returns the appropriate writer instance based on the file extension
40
+ def writer_instance(path)
41
+ writer_class = WRITERS[file_extension(path)]
42
+ raise UnsupportedFileTypeError, "Unsupported file type: #{file_extension(path)}" unless writer_class
43
+
44
+ writer_class.new(path)
45
+ end
18
46
 
19
- def writer_for(path)
20
- case ::File.extname(path)
21
- when ".json"
22
- Writer::Json.new
23
- when ".yml", ".yaml"
24
- Writer::Yaml.new
25
- else
26
- Writer::Text.new
47
+ # Returns the file extension from the path
48
+ def file_extension(path)
49
+ ::File.extname(path).downcase
27
50
  end
28
51
  end
29
52
  end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DevSuite
4
+ module Utils
5
+ module Logger
6
+ require_relative "formatter"
7
+ require_relative "emoji"
8
+ require_relative "errors"
9
+
10
+ class Base
11
+ LOG_LEVELS = [:none, :info, :warn, :error, :debug].freeze
12
+
13
+ attr_accessor :log_level
14
+
15
+ def initialize(log_level: :none)
16
+ validate_log_level(log_level)
17
+ @log_level = log_level
18
+ end
19
+
20
+ # Logs a message with optional emoji, prefix, and color.
21
+ def log(message, level: :none, emoji: nil, prefix: nil, color: nil)
22
+ validate_log_level(level)
23
+ return if skip_logging?(level)
24
+
25
+ options = {
26
+ level: level,
27
+ emoji: emoji,
28
+ prefix: prefix,
29
+ color: color,
30
+ }
31
+
32
+ formatted_message = Formatter.format(message, options)
33
+ output_log(formatted_message)
34
+ end
35
+
36
+ private
37
+
38
+ def validate_log_level(level)
39
+ return if LOG_LEVELS.include?(level)
40
+
41
+ raise InvalidLogLevelError, "Invalid log level: #{level}. Valid levels are: #{LOG_LEVELS.join(", ")}."
42
+ end
43
+
44
+ def skip_logging?(level)
45
+ LOG_LEVELS.index(level) < LOG_LEVELS.index(@log_level)
46
+ end
47
+
48
+ def output_log(formatted_message)
49
+ puts formatted_message if formatted_message
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DevSuite
4
+ module Utils
5
+ module Logger
6
+ module Emoji
7
+ class << self
8
+ def resolve(emoji)
9
+ return "" unless emoji
10
+
11
+ emoji.is_a?(Symbol) ? Utils::Emoji.get(emoji) : emoji
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DevSuite
4
+ module Utils
5
+ module Logger
6
+ class InvalidLogLevelError < StandardError; end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DevSuite
4
+ module Utils
5
+ module Logger
6
+ module Formatter
7
+ LOG_DETAILS = {
8
+ none: { prefix: "", color: :white },
9
+ info: { prefix: "[INFO]", color: :green },
10
+ warn: { prefix: "[WARNING]", color: :yellow },
11
+ error: { prefix: "[ERROR]", color: :red },
12
+ debug: { prefix: "[DEBUG]", color: :blue },
13
+ }.freeze
14
+
15
+ class << self
16
+ def format(message, options = {})
17
+ return "" if message.nil? || message.strip.empty?
18
+
19
+ details = fetch_log_details(options[:level])
20
+ prefix = options[:prefix] || details[:prefix]
21
+ color = options[:color] || details[:color]
22
+ emoji_icon = Emoji.resolve(options[:emoji])
23
+
24
+ formatted_message = build_message(prefix, emoji_icon, message)
25
+ Utils::Color.colorize(formatted_message, color: color)
26
+ end
27
+
28
+ private
29
+
30
+ def fetch_log_details(level)
31
+ LOG_DETAILS[level || :none]
32
+ end
33
+
34
+ def build_message(prefix, emoji_icon, message)
35
+ "#{prefix} #{emoji_icon} #{message}".strip
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DevSuite
4
+ module Utils
5
+ module Logger
6
+ require_relative "base"
7
+
8
+ class << self
9
+ # Provides access to the global logger instance
10
+ #
11
+ # @return [Base] the global logger instance
12
+ def logger
13
+ @logger ||= Base.new
14
+ end
15
+
16
+ # Logs a message using the global logger instance
17
+ #
18
+ # @param message [String] The message to log.
19
+ # @param level [Symbol] The log level (:none, :info, :warn, :error, :debug).
20
+ # @param emoji [String, Symbol, nil] Optional emoji to prepend to the message.
21
+ # @param prefix [String, nil] Custom prefix for the message.
22
+ # @param color [Symbol, nil] Custom color for the message.
23
+ def log(message, level: :none, emoji: nil, prefix: nil, color: nil)
24
+ logger.log(message, level: level, emoji: emoji, prefix: prefix, color: color)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -3,58 +3,7 @@
3
3
  module DevSuite
4
4
  module Utils
5
5
  module Logger
6
- LOG_DETAILS = {
7
- none: { prefix: "", color: :white },
8
- info: { prefix: "[INFO]", color: :green },
9
- warn: { prefix: "[WARNING]", color: :yellow },
10
- error: { prefix: "[ERROR]", color: :red },
11
- debug: { prefix: "[DEBUG]", color: :blue },
12
- }.freeze
13
-
14
- class << self
15
- # Logs a message with an optional emoji and specified log level.
16
- #
17
- # @param message [String] The message to log.
18
- # @param level [Symbol] The log level (:info, :warn, :error, :debug).
19
- # @param emoji [String, Symbol] Optional emoji to prepend to the message.
20
- def log(message, level: :none, emoji: nil, prefix: nil, color: nil)
21
- emoji_icon = resolve_emoji(emoji)
22
- formatted_message = format_message("#{emoji_icon} #{message}", level, prefix, color)
23
- output_log(formatted_message)
24
- end
25
-
26
- private
27
-
28
- # Resolves the emoji, either from a symbol or directly as a string.
29
- #
30
- # @param emoji [String, Symbol, nil] The emoji or its symbol key.
31
- # @return [String] The resolved emoji or an empty string if none is provided.
32
- def resolve_emoji(emoji)
33
- return "" unless emoji
34
-
35
- emoji.is_a?(Symbol) ? Emoji.get(emoji) : emoji
36
- end
37
-
38
- # Formats the log message with the appropriate prefix and color.
39
- #
40
- # @param message [String] The message to format.
41
- # @param level [Symbol] The log level (:info, :warn, :error, :debug).
42
- # @return [String] The formatted log message.
43
- def format_message(message, level, custom_prefix, custom_color)
44
- details = LOG_DETAILS[level]
45
- prefix = custom_prefix || details[:prefix]
46
- color = custom_color || details[:color]
47
-
48
- Utils::Color.colorize("#{prefix} #{message}", color: color)
49
- end
50
-
51
- # Outputs the formatted log message to the console.
52
- #
53
- # @param formatted_message [String] The message to output.
54
- def output_log(formatted_message)
55
- puts formatted_message if formatted_message
56
- end
57
- end
6
+ require_relative "logger/logger"
58
7
  end
59
8
  end
60
9
  end
@@ -49,7 +49,7 @@ module DevSuite
49
49
 
50
50
  total_width = column_widths.sum + column_widths.size * 3 - 1
51
51
  title_str = "| #{table.title.center(total_width - 2)} |"
52
- colorize(title_str, settings.get("colors.title"))
52
+ puts colorize(title_str, settings.get("colors.title"))
53
53
  end
54
54
 
55
55
  def render_header(table, column_widths)
@@ -59,13 +59,13 @@ module DevSuite
59
59
  text_align(column.name, column_widths[index])
60
60
  end
61
61
  header_str = "| #{header.join(" | ")} |"
62
- colorize(header_str, settings.get("colors.column"))
62
+ puts colorize(header_str, settings.get("colors.column"))
63
63
  end
64
64
 
65
65
  def render_separator(column_widths)
66
66
  separator = column_widths.map { |width| "-" * width }.join("-+-")
67
67
  separator_str = "+-#{separator}-+"
68
- colorize(separator_str, settings.get("colors.border"))
68
+ puts colorize(separator_str, settings.get("colors.border"))
69
69
  end
70
70
 
71
71
  def render_rows(table, column_widths)
@@ -73,7 +73,7 @@ module DevSuite
73
73
  render_row(row, column_widths)
74
74
  end
75
75
  cells_str = cells.join("\n")
76
- colorize(cells_str, settings.get("colors.row"))
76
+ puts colorize(cells_str, settings.get("colors.row"))
77
77
  end
78
78
 
79
79
  def render_row(row, column_widths)
@@ -81,7 +81,7 @@ module DevSuite
81
81
  text_align(cell.to_s, column_widths[index])
82
82
  end
83
83
  cell_str = "| #{cell.join(" | ")} |"
84
- colorize(cell_str, settings.get("colors.row"))
84
+ puts colorize(cell_str, settings.get("colors.row"))
85
85
  end
86
86
  end
87
87
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DevSuite
4
- VERSION = "0.2.11"
4
+ VERSION = "0.2.12"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dev_suite
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.11
4
+ version: 0.2.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Huy Nguyen
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-09-27 00:00:00.000000000 Z
11
+ date: 2024-10-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: benchmark
@@ -258,6 +258,11 @@ files:
258
258
  - lib/dev_suite/utils/data/base_operations.rb
259
259
  - lib/dev_suite/utils/data/data.rb
260
260
  - lib/dev_suite/utils/data/path_access.rb
261
+ - lib/dev_suite/utils/data/path_access/data_traverser.rb
262
+ - lib/dev_suite/utils/data/path_access/errors.rb
263
+ - lib/dev_suite/utils/data/path_access/key_handler.rb
264
+ - lib/dev_suite/utils/data/path_access/path_accessor.rb
265
+ - lib/dev_suite/utils/data/path_access/path_parser.rb
261
266
  - lib/dev_suite/utils/data/search_filter.rb
262
267
  - lib/dev_suite/utils/data/serialization.rb
263
268
  - lib/dev_suite/utils/data/transformations.rb
@@ -289,6 +294,11 @@ files:
289
294
  - lib/dev_suite/utils/file_writer/writer/yaml.rb
290
295
  - lib/dev_suite/utils/file_writer/writer_manager.rb
291
296
  - lib/dev_suite/utils/logger.rb
297
+ - lib/dev_suite/utils/logger/base.rb
298
+ - lib/dev_suite/utils/logger/emoji.rb
299
+ - lib/dev_suite/utils/logger/errors.rb
300
+ - lib/dev_suite/utils/logger/formatter.rb
301
+ - lib/dev_suite/utils/logger/logger.rb
292
302
  - lib/dev_suite/utils/path_matcher.rb
293
303
  - lib/dev_suite/utils/path_matcher/matcher.rb
294
304
  - lib/dev_suite/utils/path_matcher/path_matcher.rb