dev_suite 0.2.11 → 0.2.12

Sign up to get free protection for your applications and to get access to all the features.
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