snakommit 0.1.1 → 0.1.2

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.
@@ -21,7 +21,7 @@ module Snakommit
21
21
  { 'name' => 'perf', 'description' => 'A code change that improves performance' },
22
22
  { 'name' => 'test', 'description' => 'Adding missing tests or correcting existing tests' },
23
23
  { 'name' => 'build', 'description' => 'Changes that affect the build system or external dependencies' },
24
- { 'name' => 'ci', 'description' => 'Changes to our CI configuration files and scripts' },
24
+ { 'name' => 'ci/cd', 'description' => 'Changes to our CI/CD configuration files and scripts' },
25
25
  { 'name' => 'chore', 'description' => 'Other changes that don\'t modify src or test files' }
26
26
  ],
27
27
  'scopes' => [],
@@ -29,126 +29,98 @@ module Snakommit
29
29
  'max_body_line_length' => 72
30
30
  }.freeze
31
31
 
32
- # Initialiser les variables de classe
33
- @config_cache = {}
34
- @config_last_modified = nil
35
-
36
- # Load configuration from file, creating default if needed
37
- # @return [Hash] The configuration hash
38
- # @raise [ConfigError] If configuration can't be loaded
39
- def self.load
40
- create_default_config unless File.exist?(CONFIG_FILE)
41
-
42
- # Check if config file has been modified since last load
43
- current_mtime = File.mtime(CONFIG_FILE) rescue nil
44
-
45
- # Return cached config if it exists and file hasn't been modified
46
- if @config_cache && @config_last_modified == current_mtime
47
- return @config_cache.dup
32
+ class << self
33
+ # Initialize class variables
34
+ def setup_cache
35
+ @config_cache ||= {}
36
+ @config_last_modified ||= nil
37
+ true
48
38
  end
49
-
50
- # Load and cache the configuration
51
- @config_cache = YAML.load_file(CONFIG_FILE) || {}
52
- @config_last_modified = current_mtime
53
-
54
- # Return a copy to prevent unintentional modifications
55
- @config_cache.dup
56
- rescue Errno::EACCES, Errno::ENOENT => e
57
- raise ConfigError, "Could not load configuration: #{e.message}"
58
- rescue => e
59
- raise ConfigError, "Unexpected error loading configuration: #{e.message}"
60
- end
61
39
 
62
- # Create the default configuration file
63
- # @return [Boolean] True if successful
64
- # @raise [ConfigError] If default config can't be created
65
- def self.create_default_config
66
- # Check if directory exists
67
- config_dir = File.dirname(CONFIG_FILE)
68
- unless Dir.exist?(config_dir)
69
- begin
70
- FileUtils.mkdir_p(config_dir)
71
- rescue Errno::EACCES => e
72
- raise ConfigError, "Permission denied creating config directory: #{e.message}"
73
- end
40
+ def load
41
+ create_default_config unless File.exist?(CONFIG_FILE)
42
+
43
+ # Use cached config if file hasn't been modified
44
+ current_mtime = File.mtime(CONFIG_FILE) rescue nil
45
+ # Ensure cache is initialized
46
+ setup_cache
47
+ return @config_cache.dup if @config_cache && @config_last_modified == current_mtime
48
+
49
+ # Load and cache the configuration
50
+ @config_cache = YAML.load_file(CONFIG_FILE) || {}
51
+ @config_last_modified = current_mtime
52
+
53
+ @config_cache.dup
54
+ rescue Errno::EACCES, Errno::ENOENT => e
55
+ raise ConfigError, "Could not load configuration: #{e.message}"
56
+ rescue => e
57
+ raise ConfigError, "Unexpected error loading configuration: #{e.message}"
74
58
  end
75
-
76
- # Write config file if it doesn't exist
77
- unless File.exist?(CONFIG_FILE)
78
- begin
59
+
60
+ def create_default_config
61
+ # Ensure config directory exists
62
+ config_dir = File.dirname(CONFIG_FILE)
63
+ FileUtils.mkdir_p(config_dir) unless Dir.exist?(config_dir)
64
+
65
+ # Write default config if file doesn't exist
66
+ unless File.exist?(CONFIG_FILE)
79
67
  File.write(CONFIG_FILE, DEFAULT_CONFIG.to_yaml)
80
68
 
81
69
  # Update cache
82
70
  @config_cache = DEFAULT_CONFIG.dup
83
71
  @config_last_modified = File.mtime(CONFIG_FILE) rescue nil
84
- rescue Errno::EACCES => e
85
- raise ConfigError, "Permission denied creating config file: #{e.message}"
86
- rescue => e
87
- raise ConfigError, "Unexpected error creating config file: #{e.message}"
88
72
  end
73
+
74
+ true
75
+ rescue Errno::EACCES => e
76
+ raise ConfigError, "Permission denied: #{e.message}"
77
+ rescue => e
78
+ raise ConfigError, "Failed to create config file: #{e.message}"
89
79
  end
90
80
 
91
- true
92
- end
93
-
94
- # Update configuration values
95
- # @param updates [Hash] Configuration values to update
96
- # @return [Hash] The updated configuration
97
- # @raise [ConfigError] If configuration can't be updated
98
- def self.update(updates)
99
- config = load
100
- config.merge!(updates)
101
-
102
- # Create a backup of the current configuration
103
- backup_config if File.exist?(CONFIG_FILE)
104
-
105
- # Write the updated configuration
106
- File.write(CONFIG_FILE, config.to_yaml)
81
+ def update(updates)
82
+ config = load.merge(updates)
83
+
84
+ # Backup and write updated config
85
+ backup_config if File.exist?(CONFIG_FILE)
86
+ File.write(CONFIG_FILE, config.to_yaml)
87
+
88
+ # Update cache
89
+ @config_cache = config.dup
90
+ @config_last_modified = File.mtime(CONFIG_FILE) rescue nil
91
+
92
+ config
93
+ rescue => e
94
+ raise ConfigError, "Failed to update configuration: #{e.message}"
95
+ end
107
96
 
108
- # Update cache
109
- @config_cache = config.dup
110
- @config_last_modified = File.mtime(CONFIG_FILE) rescue nil
97
+ def get(key, default = nil)
98
+ load.fetch(key, default)
99
+ end
111
100
 
112
- config
113
- rescue => e
114
- raise ConfigError, "Failed to update configuration: #{e.message}"
115
- end
116
-
117
- # Get a specific configuration value
118
- # @param key [String] Configuration key
119
- # @param default [Object] Default value if key not found
120
- # @return [Object] Configuration value or default
121
- def self.get(key, default = nil)
122
- config = load
123
- config.fetch(key, default)
124
- end
125
-
126
- # Reset configuration to defaults
127
- # @return [Hash] The default configuration
128
- def self.reset
129
- backup_config if File.exist?(CONFIG_FILE)
130
- File.write(CONFIG_FILE, DEFAULT_CONFIG.to_yaml)
101
+ def reset
102
+ backup_config if File.exist?(CONFIG_FILE)
103
+ File.write(CONFIG_FILE, DEFAULT_CONFIG.to_yaml)
104
+
105
+ # Update cache
106
+ @config_cache = DEFAULT_CONFIG.dup
107
+ @config_last_modified = File.mtime(CONFIG_FILE) rescue nil
108
+
109
+ DEFAULT_CONFIG.dup
110
+ rescue => e
111
+ raise ConfigError, "Failed to reset configuration: #{e.message}"
112
+ end
131
113
 
132
- # Update cache
133
- @config_cache = DEFAULT_CONFIG.dup
134
- @config_last_modified = File.mtime(CONFIG_FILE) rescue nil
114
+ private
135
115
 
136
- DEFAULT_CONFIG.dup
137
- rescue => e
138
- raise ConfigError, "Failed to reset configuration: #{e.message}"
139
- end
140
-
141
- private
142
-
143
- # Backup the current configuration
144
- # @return [String] Path to backup file
145
- def self.backup_config
146
- backup_file = "#{CONFIG_FILE}.bak"
147
- FileUtils.cp(CONFIG_FILE, backup_file)
148
- backup_file
149
- rescue => e
150
- warn "Warning: Failed to backup configuration: #{e.message}"
151
- nil
152
- end
116
+ def backup_config
117
+ backup_file = "#{CONFIG_FILE}.bak"
118
+ FileUtils.cp(CONFIG_FILE, backup_file)
119
+ backup_file
120
+ rescue => e
121
+ warn "Warning: Failed to backup configuration: #{e.message}"
122
+ nil
123
+ end
124
+ end # End of class << self
153
125
  end
154
- end
126
+ end
data/lib/snakommit/git.rb CHANGED
@@ -102,7 +102,7 @@ module Snakommit
102
102
  # Commit with the given message
103
103
  def commit(message)
104
104
  with_temp_file(message) do |message_file|
105
- stdout, stderr, status = Open3.capture3('git', 'commit', '-F', message_file)
105
+ _, stderr, status = Open3.capture3('git', 'commit', '-F', message_file)
106
106
 
107
107
  # Clear any saved selections after successful commit
108
108
  clear_saved_selections
@@ -205,11 +205,10 @@ module Snakommit
205
205
  end
206
206
 
207
207
  false
208
- rescue => e
208
+ rescue StandardError
209
209
  # If we can't read the file, it's not our hook
210
210
  false
211
211
  end
212
-
213
212
  # Backup an existing hook file
214
213
  # @param hook_path [String] Path to the hook file
215
214
  # @return [String, nil] Path to the backup file or nil if no backup created
@@ -255,4 +254,4 @@ module Snakommit
255
254
  false
256
255
  end
257
256
  end
258
- end
257
+ end
@@ -7,10 +7,9 @@ module Snakommit
7
7
  class Performance
8
8
  # Cache for expensive Git operations
9
9
  class Cache
10
- # Initialize a new cache
11
- # @param max_size [Integer] Maximum number of items to cache
12
- # @param ttl [Integer] Time-to-live in seconds for cached items
13
- def initialize(max_size = 100, ttl = 300) # 5 minutes TTL by default
10
+ attr_reader :max_size, :ttl
11
+
12
+ def initialize(max_size = 100, ttl = 300)
14
13
  @cache = {}
15
14
  @max_size = max_size
16
15
  @ttl = ttl
@@ -18,9 +17,6 @@ module Snakommit
18
17
  @misses = 0
19
18
  end
20
19
 
21
- # Get a value from the cache
22
- # @param key [Object] Cache key
23
- # @return [Object, nil] Cached value or nil if not found or expired
24
20
  def get(key)
25
21
  return nil unless @cache.key?(key)
26
22
  entry = @cache[key]
@@ -34,10 +30,6 @@ module Snakommit
34
30
  entry[:value]
35
31
  end
36
32
 
37
- # Set a value in the cache
38
- # @param key [Object] Cache key
39
- # @param value [Object] Value to cache
40
- # @return [Object] The value that was cached
41
33
  def set(key, value)
42
34
  cleanup if @cache.size >= @max_size
43
35
  @cache[key] = { value: value, timestamp: Time.now }
@@ -45,22 +37,15 @@ module Snakommit
45
37
  value
46
38
  end
47
39
 
48
- # Remove a specific key from the cache
49
- # @param key [Object] Cache key to invalidate
50
- # @return [nil]
51
40
  def invalidate(key)
52
41
  @cache.delete(key)
53
42
  nil
54
43
  end
55
44
 
56
- # Clear the entire cache
57
- # @return [Hash] Empty hash
58
45
  def clear
59
46
  @cache = {}
60
47
  end
61
48
 
62
- # Get cache stats
63
- # @return [Hash] Cache statistics
64
49
  def stats
65
50
  {
66
51
  size: @cache.size,
@@ -72,8 +57,6 @@ module Snakommit
72
57
  }
73
58
  end
74
59
 
75
- # Calculate cache hit rate
76
- # @return [Float] Cache hit rate as a percentage
77
60
  def hit_rate
78
61
  total = @hits + @misses
79
62
  return 0.0 if total.zero?
@@ -82,17 +65,14 @@ module Snakommit
82
65
 
83
66
  private
84
67
 
85
- # Clean up expired or oldest entries when cache is full
86
- # @return [nil]
87
68
  def cleanup
88
69
  # Remove expired entries first
89
- expired_keys = @cache.select { |_, v| Time.now - v[:timestamp] > @ttl }.keys
90
- @cache.delete_if { |k, _| expired_keys.include?(k) }
70
+ @cache.delete_if { |_, v| Time.now - v[:timestamp] > @ttl }
91
71
 
92
72
  # If still too large, remove oldest entries
93
73
  if @cache.size >= @max_size
94
74
  sorted_keys = @cache.sort_by { |_, v| v[:timestamp] }.map(&:first)
95
- sorted_keys[0...(@cache.size - @max_size / 2)].each { |k| @cache.delete(k) }
75
+ sorted_keys.first(@cache.size - @max_size / 2).each { |k| @cache.delete(k) }
96
76
  end
97
77
 
98
78
  nil
@@ -101,25 +81,19 @@ module Snakommit
101
81
 
102
82
  # Batch processing for Git operations
103
83
  class BatchProcessor
104
- # Initialize a new batch processor
105
- # @param batch_size [Integer] Default batch size for processing
84
+ attr_reader :batch_size, :total_processed, :batch_count
85
+
106
86
  def initialize(batch_size = 100)
107
87
  @batch_size = batch_size
108
88
  @total_processed = 0
109
89
  @batch_count = 0
110
90
  end
111
91
 
112
- # Process files in batches
113
- # @param files [Array<String>] List of files to process
114
- # @param batch_size [Integer, nil] Optional override for batch size
115
- # @yield [batch] Yields each batch of files for processing
116
- # @yieldparam batch [Array<String>] A batch of files
117
- # @return [Array] Combined results from all batches
118
92
  def process_files(files, batch_size = nil, &block)
119
93
  size = batch_size || @batch_size
120
94
  results = []
121
95
 
122
- files.each_slice(size).each_with_index do |batch, index|
96
+ files.each_slice(size).each do |batch|
123
97
  @batch_count += 1
124
98
  batch_result = block.call(batch)
125
99
  @total_processed += batch.size
@@ -129,8 +103,6 @@ module Snakommit
129
103
  results
130
104
  end
131
105
 
132
- # Get batch processing stats
133
- # @return [Hash] Batch processing statistics
134
106
  def stats
135
107
  {
136
108
  batch_size: @batch_size,
@@ -140,8 +112,6 @@ module Snakommit
140
112
  }
141
113
  end
142
114
 
143
- # Calculate average batch size
144
- # @return [Float] Average batch size
145
115
  def average_batch_size
146
116
  return 0.0 if @batch_count.zero?
147
117
  @total_processed.to_f / @batch_count
@@ -150,10 +120,8 @@ module Snakommit
150
120
 
151
121
  # Helper for parallel processing where appropriate
152
122
  class ParallelHelper
153
- # Check if parallel processing is available
154
- # @return [Boolean] True if the parallel gem is available
155
123
  def self.available?
156
- begin
124
+ @available ||= begin
157
125
  require 'parallel'
158
126
  true
159
127
  rescue LoadError
@@ -161,49 +129,34 @@ module Snakommit
161
129
  end
162
130
  end
163
131
 
164
- # Process items in parallel if possible, otherwise sequentially
165
- # @param items [Array] Items to process
166
- # @param options [Hash] Options for parallel processing
167
- # @option options [Integer] :threshold Minimum number of items to use parallel processing
168
- # @option options [Integer] :workers Number of workers to use (defaults to processor count)
169
- # @yield [item] Block to process each item
170
- # @yieldparam item [Object] An item to process
171
- # @return [Array] Results of processing all items
172
132
  def self.process(items, options = {}, &block)
173
133
  threshold = options.delete(:threshold) || 10
174
134
  workers = options.delete(:workers) || processor_count
175
135
 
176
136
  if available? && items.size > threshold
177
137
  require 'parallel'
178
- Parallel.map(items, { in_processes: workers }.merge(options)) { |item| block.call(item) }
138
+ Parallel.map(items, { in_processes: workers }.merge(options), &block)
179
139
  else
180
140
  items.map(&block)
181
141
  end
182
142
  end
183
143
 
184
- # Get number of available processors
185
- # @return [Integer] Number of processors available
186
144
  def self.processor_count
187
145
  if defined?(Etc) && Etc.respond_to?(:nprocessors)
188
146
  Etc.nprocessors
189
147
  else
190
- 2 # Conservative default
148
+ 2
191
149
  end
192
150
  end
193
151
  end
194
152
 
195
153
  # Performance monitoring and reporting
196
154
  class Monitor
197
- # Initialize a new monitor
198
155
  def initialize
199
156
  @timings = {}
200
157
  @counts = {}
201
158
  end
202
159
 
203
- # Measure execution time of a block
204
- # @param label [String, Symbol] Label for the measurement
205
- # @yield Block to measure
206
- # @return [Object] Result of the block
207
160
  def measure(label)
208
161
  start_time = Time.now
209
162
  result = yield
@@ -218,8 +171,6 @@ module Snakommit
218
171
  result
219
172
  end
220
173
 
221
- # Get a report of all timings
222
- # @return [Array<String>] Formatted timing report lines
223
174
  def report
224
175
  @timings.sort_by { |_, v| -v }.map do |k, v|
225
176
  count = @counts[k]
@@ -228,8 +179,6 @@ module Snakommit
228
179
  end
229
180
  end
230
181
 
231
- # Reset all timings
232
- # @return [nil]
233
182
  def reset
234
183
  @timings.clear
235
184
  @counts.clear
@@ -239,11 +188,6 @@ module Snakommit
239
188
 
240
189
  # Benchmarking utility for snakommit operations
241
190
  class Benchmark
242
- # Run a benchmark test
243
- # @param label [String] Label for the benchmark
244
- # @param iterations [Integer] Number of iterations to run
245
- # @yield Block to benchmark
246
- # @return [Hash] Benchmark results
247
191
  def self.run(label, iterations = 1)
248
192
  results = {}
249
193
 
@@ -262,12 +206,6 @@ module Snakommit
262
206
  results
263
207
  end
264
208
 
265
- # Compare performance of multiple implementations
266
- # @param options [Hash] Options for comparison
267
- # @option options [Integer] :iterations Number of iterations
268
- # @option options [Boolean] :verbose Print results
269
- # @yield Block that returns a hash of callable objects to compare
270
- # @return [Hash] Comparison results
271
209
  def self.compare(options = {})
272
210
  iterations = options[:iterations] || 100
273
211
  verbose = options[:verbose] || false
@@ -292,25 +230,15 @@ module Snakommit
292
230
 
293
231
  # Memory usage tracking
294
232
  class Memory
295
- # Get current memory usage in KB
296
- # @return [Integer] Memory usage in KB
297
233
  def self.usage
298
234
  case RbConfig::CONFIG['host_os']
299
- when /linux/
235
+ when /linux/, /darwin/
300
236
  `ps -o rss= -p #{Process.pid}`.to_i
301
- when /darwin/
302
- `ps -o rss= -p #{Process.pid}`.to_i
303
- when /windows|mswin|mingw/
304
- # Not implemented for Windows
305
- 0
306
237
  else
307
238
  0
308
239
  end
309
240
  end
310
241
 
311
- # Measure memory usage before and after a block execution
312
- # @yield Block to measure
313
- # @return [Hash] Memory usage statistics
314
242
  def self.measure
315
243
  before = usage
316
244
  result = yield