orfeas_lyra 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +222 -0
  3. data/LICENSE +21 -0
  4. data/README.md +1165 -0
  5. data/Rakefile +728 -0
  6. data/app/controllers/lyra/application_controller.rb +23 -0
  7. data/app/controllers/lyra/dashboard_controller.rb +624 -0
  8. data/app/controllers/lyra/flow_controller.rb +224 -0
  9. data/app/controllers/lyra/privacy_controller.rb +182 -0
  10. data/app/views/lyra/dashboard/audit_trail.html.erb +324 -0
  11. data/app/views/lyra/dashboard/discrepancies.html.erb +125 -0
  12. data/app/views/lyra/dashboard/event_graph_view.html.erb +525 -0
  13. data/app/views/lyra/dashboard/heatmap_view.html.erb +155 -0
  14. data/app/views/lyra/dashboard/index.html.erb +119 -0
  15. data/app/views/lyra/dashboard/model_overview.html.erb +115 -0
  16. data/app/views/lyra/dashboard/projections.html.erb +302 -0
  17. data/app/views/lyra/dashboard/schema.html.erb +283 -0
  18. data/app/views/lyra/dashboard/schema_history.html.erb +78 -0
  19. data/app/views/lyra/dashboard/schema_version.html.erb +340 -0
  20. data/app/views/lyra/dashboard/verification.html.erb +370 -0
  21. data/app/views/lyra/flow/crud_mapping.html.erb +125 -0
  22. data/app/views/lyra/flow/timeline.html.erb +260 -0
  23. data/app/views/lyra/privacy/pii_detection.html.erb +148 -0
  24. data/app/views/lyra/privacy/policy.html.erb +188 -0
  25. data/app/workflows/es_async_mode_workflow.rb +80 -0
  26. data/app/workflows/es_sync_mode_workflow.rb +64 -0
  27. data/app/workflows/hijack_mode_workflow.rb +54 -0
  28. data/app/workflows/lifecycle_workflow.rb +43 -0
  29. data/app/workflows/monitor_mode_workflow.rb +39 -0
  30. data/config/privacy_policies.rb +273 -0
  31. data/config/routes.rb +48 -0
  32. data/lib/lyra/aggregate.rb +131 -0
  33. data/lib/lyra/associations/event_aware.rb +225 -0
  34. data/lib/lyra/command.rb +81 -0
  35. data/lib/lyra/command_handler.rb +155 -0
  36. data/lib/lyra/configuration.rb +124 -0
  37. data/lib/lyra/consistency/read_your_writes.rb +91 -0
  38. data/lib/lyra/correlation.rb +144 -0
  39. data/lib/lyra/dual_view.rb +231 -0
  40. data/lib/lyra/engine.rb +67 -0
  41. data/lib/lyra/event.rb +71 -0
  42. data/lib/lyra/event_analyzer.rb +135 -0
  43. data/lib/lyra/event_flow.rb +449 -0
  44. data/lib/lyra/event_mapper.rb +106 -0
  45. data/lib/lyra/event_store_adapter.rb +72 -0
  46. data/lib/lyra/id_generator.rb +137 -0
  47. data/lib/lyra/interceptors/association_interceptor.rb +169 -0
  48. data/lib/lyra/interceptors/crud_interceptor.rb +543 -0
  49. data/lib/lyra/privacy/gdpr_compliance.rb +161 -0
  50. data/lib/lyra/privacy/pii_detector.rb +85 -0
  51. data/lib/lyra/privacy/pii_masker.rb +66 -0
  52. data/lib/lyra/privacy/policy_integration.rb +253 -0
  53. data/lib/lyra/projection.rb +94 -0
  54. data/lib/lyra/projections/async_projection_job.rb +63 -0
  55. data/lib/lyra/projections/cached_projection.rb +322 -0
  56. data/lib/lyra/projections/cached_relation.rb +757 -0
  57. data/lib/lyra/projections/event_store_reader.rb +127 -0
  58. data/lib/lyra/projections/model_projection.rb +143 -0
  59. data/lib/lyra/schema/diff.rb +331 -0
  60. data/lib/lyra/schema/event_class_registrar.rb +63 -0
  61. data/lib/lyra/schema/generator.rb +190 -0
  62. data/lib/lyra/schema/reporter.rb +188 -0
  63. data/lib/lyra/schema/store.rb +156 -0
  64. data/lib/lyra/schema/validator.rb +100 -0
  65. data/lib/lyra/strict_data_access.rb +363 -0
  66. data/lib/lyra/verification/crud_lifecycle_workflow.rb +456 -0
  67. data/lib/lyra/verification/workflow_generator.rb +540 -0
  68. data/lib/lyra/version.rb +3 -0
  69. data/lib/lyra/visualization/activity_heatmap.rb +215 -0
  70. data/lib/lyra/visualization/event_graph.rb +310 -0
  71. data/lib/lyra/visualization/timeline.rb +398 -0
  72. data/lib/lyra.rb +150 -0
  73. data/lib/tasks/dist.rake +391 -0
  74. data/lib/tasks/gems.rake +185 -0
  75. data/lib/tasks/lyra_schema.rake +231 -0
  76. data/lib/tasks/lyra_workflows.rake +452 -0
  77. data/lib/tasks/public_release.rake +351 -0
  78. data/lib/tasks/stats.rake +175 -0
  79. data/lib/tasks/testbed.rake +479 -0
  80. data/lib/tasks/version.rake +159 -0
  81. metadata +221 -0
@@ -0,0 +1,391 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :lyra do
4
+ desc "Create distribution zip for open science evaluation"
5
+ task :dist do
6
+ require "fileutils"
7
+ require "date"
8
+
9
+ # Read version from Lyra version file
10
+ version = extract_lyra_version
11
+ date_stamp = Date.today.strftime("%Y%m%d")
12
+ dist_name = "lyra-orfeas-v#{version}-#{date_stamp}"
13
+ dist_dir = "dist/#{dist_name}"
14
+ zip_file = "dist/#{dist_name}.zip"
15
+
16
+ puts "\n" + "=" * 70
17
+ puts "Creating Lyra/ORFEAS Distribution Package"
18
+ puts "=" * 70
19
+ puts "Version: #{version}"
20
+ puts "Output: #{zip_file}"
21
+ puts "=" * 70
22
+
23
+ # Clean previous builds
24
+ FileUtils.rm_rf("dist")
25
+ FileUtils.mkdir_p(dist_dir)
26
+
27
+ # Define what to include
28
+ includes = [
29
+ # Core framework
30
+ "lib",
31
+ "app",
32
+ "config",
33
+ "db",
34
+
35
+ # Gems
36
+ "gems/petri_flow",
37
+ "gems/pam_dsl",
38
+
39
+ # Tests
40
+ "test",
41
+
42
+ # Examples (excluding aegean_epay_testbed which is large and has sensitive config)
43
+ "examples/blog_app",
44
+ "examples/privacy_examples.rb",
45
+
46
+ # Documentation (markdown files only, not Jekyll site)
47
+ "docs",
48
+
49
+ # Root files
50
+ "Gemfile",
51
+ "Gemfile.lock",
52
+ "lyra.gemspec",
53
+ "Rakefile",
54
+ "docker-compose.yml",
55
+ "README.md",
56
+ "LICENSE",
57
+ "docs/ARCHITECTURE.md",
58
+ "docs/GETTING_STARTED.md",
59
+ "docs/MONOREPO.md",
60
+ "CHANGELOG.md"
61
+ ]
62
+
63
+ # Define exclusions (patterns)
64
+ exclusions = [
65
+ # Dependency directories
66
+ "**/vendor/**",
67
+ "**/.bundle/**",
68
+ "**/node_modules/**",
69
+
70
+ # Git
71
+ "**/.git/**",
72
+
73
+ # Generated/temp files
74
+ "**/tmp/**",
75
+ "**/log/**",
76
+ "**/coverage/**",
77
+ "**/_site/**",
78
+ "**/.jekyll-cache/**",
79
+ "**/.sass-cache/**",
80
+
81
+ # Database files
82
+ "**/*.sqlite3",
83
+ "**/*.sqlite3-*",
84
+
85
+ # Log files
86
+ "**/*.log",
87
+
88
+ # OS files
89
+ "**/.DS_Store",
90
+
91
+ # Editor/debug files
92
+ "**/.byebug_history",
93
+ "**/.ruby-version",
94
+
95
+ # Test outputs
96
+ "**/test/dummy/tmp/**",
97
+ "**/test/dummy/log/**",
98
+ "**/test/reports/**",
99
+ "**/test_results/**",
100
+ "**/examples/**/tmp/**",
101
+ "**/examples/**/log/**",
102
+ "**/examples/**/reports/**",
103
+
104
+ # Sensitive/internal directories
105
+ "**/webbank_guide/**",
106
+ "**/research/papers/**",
107
+ "**/images/programs/**",
108
+ "proposal/**",
109
+ "thesis/**",
110
+ "paper/**",
111
+ "papertse/**"
112
+ ]
113
+
114
+ puts "\nCopying files..."
115
+
116
+ includes.each do |item|
117
+ next unless File.exist?(item)
118
+
119
+ dest = "#{dist_dir}/#{item}"
120
+
121
+ if File.directory?(item)
122
+ copy_directory(item, dest, exclusions)
123
+ else
124
+ FileUtils.mkdir_p(File.dirname(dest))
125
+ FileUtils.cp(item, dest)
126
+ puts " + #{item}"
127
+ end
128
+ end
129
+
130
+ # Generate MANIFEST.txt
131
+ puts "\nGenerating MANIFEST.txt..."
132
+ generate_manifest(dist_dir)
133
+
134
+ # Generate INSTALL.md
135
+ File.write("#{dist_dir}/INSTALL.md", <<~INSTALL)
136
+ # Lyra/ORFEAS Installation Guide
137
+
138
+ ## Prerequisites
139
+
140
+ - Ruby 4.0+
141
+ - Rails 8.0+
142
+ - Bundler
143
+ - PostgreSQL 14+ (required for event sourcing features)
144
+
145
+ ## Quick Start
146
+
147
+ ```bash
148
+ # Install dependencies
149
+ bundle install
150
+
151
+ # Start PostgreSQL (via Docker)
152
+ rake docker:start
153
+
154
+ # Run Lyra core tests
155
+ bundle exec rake test
156
+ ```
157
+
158
+ ## Running Tests
159
+
160
+ ```bash
161
+ # Lyra core tests only
162
+ bundle exec rake test
163
+
164
+ # All component tests (Lyra + PetriFlow + PAM DSL)
165
+ bundle exec rake test:all
166
+
167
+ # Unit tests only (excludes controller tests)
168
+ bundle exec rake test:unit
169
+
170
+ # Controller tests only (requires Rails dummy app)
171
+ bundle exec rake test:controllers
172
+
173
+ # Individual gem tests
174
+ cd gems/petri_flow && bundle exec rake test
175
+ cd gems/pam_dsl && bundle exec rake test
176
+ ```
177
+
178
+ ## Running Example Application
179
+
180
+ ### Blog App (Simple Example)
181
+
182
+ ```bash
183
+ cd examples/blog_app
184
+ bundle install
185
+ rails db:create db:migrate db:seed
186
+ rails console
187
+ ```
188
+
189
+ ## Documentation
190
+
191
+ Documentation is available in the `docs/` directory as Markdown files:
192
+
193
+ - `docs/API_REFERENCE.md` - API documentation
194
+ - `gems/petri_flow/docs/PETRIFLOW_EXPORT.md` - PetriFlow export formats
195
+ - `docs/PRIVACY_COMPLIANCE_VALIDATION.md` - Privacy compliance
196
+
197
+ ## Gem Dependencies
198
+
199
+ If you need to rebuild gem dependencies:
200
+
201
+ ```bash
202
+ # Main project
203
+ bundle install
204
+
205
+ # PetriFlow gem
206
+ cd gems/petri_flow && bundle install
207
+
208
+ # PAM DSL gem
209
+ cd gems/pam_dsl && bundle install
210
+ ```
211
+
212
+ ## PostgreSQL Setup
213
+
214
+ The project includes a Docker Compose file for PostgreSQL:
215
+
216
+ ```bash
217
+ # Start PostgreSQL container (port 5433)
218
+ rake docker:start
219
+
220
+ # Check status
221
+ rake docker:status
222
+
223
+ # Stop container
224
+ rake docker:stop
225
+ ```
226
+
227
+ Or install PostgreSQL natively and configure `config/database.yml`.
228
+
229
+ ## Troubleshooting
230
+
231
+ ### Native extension errors
232
+ Some gems require native extensions. On macOS:
233
+ ```bash
234
+ xcode-select --install
235
+ ```
236
+
237
+ ### PostgreSQL connection errors
238
+ Ensure PostgreSQL is running on port 5433 (Docker) or 5432 (native).
239
+
240
+ ## License
241
+
242
+ MIT License - see LICENSE file.
243
+ INSTALL
244
+ puts " + INSTALL.md"
245
+
246
+ # Generate VERSION.txt
247
+ File.write("#{dist_dir}/VERSION.txt", <<~VERSION)
248
+ Lyra/ORFEAS Framework
249
+ Version: #{version}
250
+ Date: #{Date.today}
251
+
252
+ CRUD to Event Sourcing Transformation Engine
253
+ Part of the ORFEAS (Object-Relational to Event-Sourcing Architecture) Framework
254
+
255
+ Author: Michail Pantelelis (mpantel@aegean.gr)
256
+ Institution: University of the Aegean
257
+
258
+ License: MIT
259
+ VERSION
260
+ puts " + VERSION.txt"
261
+
262
+ # Create zip
263
+ puts "\nCreating zip archive..."
264
+ Dir.chdir("dist") do
265
+ system("zip -rq #{dist_name}.zip #{dist_name}")
266
+ end
267
+
268
+ # Calculate size
269
+ zip_size = File.size(zip_file)
270
+ zip_size_mb = (zip_size / 1024.0 / 1024.0).round(2)
271
+
272
+ # Count files
273
+ file_count = Dir.glob("#{dist_dir}/**/*", File::FNM_DOTMATCH).count { |f| File.file?(f) }
274
+
275
+ puts "\n" + "=" * 70
276
+ puts "Distribution Package Created Successfully"
277
+ puts "=" * 70
278
+ puts "Output: #{zip_file}"
279
+ puts "Size: #{zip_size_mb} MB"
280
+ puts "Files: #{file_count}"
281
+ puts "=" * 70
282
+ puts "\nTo extract: unzip #{zip_file}"
283
+ puts "=" * 70 + "\n\n"
284
+ end
285
+
286
+ desc "List contents of distribution (dry run)"
287
+ task :dist_list do
288
+ includes = %w[lib app config db gems/petri_flow gems/pam_dsl test examples/blog_app docs]
289
+
290
+ exclusions = %w[vendor .bundle .git node_modules tmp log coverage _site .jekyll-cache reports]
291
+
292
+ puts "\n" + "=" * 70
293
+ puts "Distribution Contents (Dry Run)"
294
+ puts "=" * 70
295
+
296
+ total_files = 0
297
+ total_size = 0
298
+
299
+ includes.each do |dir|
300
+ next unless Dir.exist?(dir)
301
+
302
+ files = Dir.glob("#{dir}/**/*").select do |f|
303
+ File.file?(f) && exclusions.none? { |ex| f.include?("/#{ex}/") }
304
+ end
305
+
306
+ size = files.sum { |f| File.size(f) }
307
+ total_files += files.count
308
+ total_size += size
309
+
310
+ puts "%-30s %6d files %8.2f MB" % [dir, files.count, size / 1024.0 / 1024.0]
311
+ end
312
+
313
+ puts "-" * 70
314
+ puts "%-30s %6d files %8.2f MB" % ["TOTAL", total_files, total_size / 1024.0 / 1024.0]
315
+ puts "=" * 70 + "\n\n"
316
+ end
317
+
318
+ private
319
+
320
+ def extract_lyra_version
321
+ version_file = "lib/lyra/version.rb"
322
+ return "0.0.0" unless File.exist?(version_file)
323
+
324
+ content = File.read(version_file)
325
+ match = content.match(/VERSION\s*=\s*["']([^"']+)["']/)
326
+ match ? match[1] : "0.0.0"
327
+ end
328
+
329
+ def copy_directory(src, dest, exclusions)
330
+ Dir.glob("#{src}/**/*", File::FNM_DOTMATCH).each do |file|
331
+ next if File.directory?(file)
332
+ next if file.end_with?("/.", "/..")
333
+
334
+ # Check exclusions - match against full path and path segments
335
+ excluded = exclusions.any? do |pattern|
336
+ File.fnmatch?(pattern, file, File::FNM_PATHNAME | File::FNM_DOTMATCH) ||
337
+ file.include?("/_site/") ||
338
+ file.include?("/vendor/") ||
339
+ file.include?("/.bundle/") ||
340
+ file.include?("/node_modules/") ||
341
+ file.include?("/tmp/") ||
342
+ file.include?("/log/") ||
343
+ file.include?("/coverage/") ||
344
+ file.include?("/.git/") ||
345
+ file.include?("/.jekyll-cache/") ||
346
+ file.include?("/.sass-cache/") ||
347
+ file.include?("/test/reports/") ||
348
+ file.include?("/test_results/") ||
349
+ file.include?("/reports/") ||
350
+ file.include?("/webbank_guide/") ||
351
+ file.include?("/research/papers/") ||
352
+ file.include?("/images/programs/") ||
353
+ file.start_with?("proposal/") ||
354
+ file.start_with?("thesis/") ||
355
+ file.start_with?("paper/") ||
356
+ file.start_with?("papertse/") ||
357
+ file.end_with?(".sqlite3") ||
358
+ file.end_with?(".log")
359
+ end
360
+ next if excluded
361
+
362
+ relative = file.sub("#{src}/", "")
363
+ dest_file = "#{dest}/#{relative}"
364
+
365
+ FileUtils.mkdir_p(File.dirname(dest_file))
366
+ FileUtils.cp(file, dest_file)
367
+ end
368
+ puts " + #{src}/"
369
+ end
370
+
371
+ def generate_manifest(dist_dir)
372
+ files = Dir.glob("#{dist_dir}/**/*", File::FNM_DOTMATCH)
373
+ .select { |f| File.file?(f) }
374
+ .map { |f| f.sub("#{dist_dir}/", "") }
375
+ .sort
376
+
377
+ manifest_content = <<~HEADER
378
+ LYRA/ORFEAS DISTRIBUTION MANIFEST
379
+ ==================================
380
+ Generated: #{Time.now}
381
+ Total Files: #{files.count}
382
+
383
+ FILES:
384
+ ------
385
+ HEADER
386
+
387
+ manifest_content += files.join("\n")
388
+
389
+ File.write("#{dist_dir}/MANIFEST.txt", manifest_content)
390
+ end
391
+ end
@@ -0,0 +1,185 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Rake tasks for publishing gems to RubyGems
4
+ #
5
+ # Usage:
6
+ # rake gems:build # Build all gems
7
+ # rake gems:build:petri_flow # Build petri_flow gem
8
+ # rake gems:build:pam_dsl # Build pam_dsl gem
9
+ # rake gems:push # Push all gems to RubyGems
10
+ # rake gems:push:petri_flow # Push petri_flow to RubyGems
11
+ # rake gems:push:pam_dsl # Push pam_dsl to RubyGems
12
+ # rake gems:release # Build and push all gems
13
+ # rake gems:version # Show gem versions
14
+
15
+ require "fileutils"
16
+
17
+ namespace :gems do
18
+ GEMS = {
19
+ orfeas_lyra: {
20
+ path: ".",
21
+ gemspec: "lyra.gemspec"
22
+ },
23
+ orfeas_petri_flow: {
24
+ path: "gems/petri_flow",
25
+ gemspec: "petri_flow.gemspec"
26
+ },
27
+ orfeas_pam_dsl: {
28
+ path: "gems/pam_dsl",
29
+ gemspec: "pam_dsl.gemspec"
30
+ }
31
+ }.freeze
32
+
33
+ desc "Show gem versions"
34
+ task :version do
35
+ puts "Gem versions:"
36
+ puts "-" * 40
37
+ GEMS.each do |name, config|
38
+ version = gem_version(config[:path])
39
+ puts " #{name}: #{version}"
40
+ end
41
+ end
42
+
43
+ desc "Build all gems"
44
+ task :build do
45
+ GEMS.each_key { |name| Rake::Task["gems:build:#{name}"].invoke }
46
+ end
47
+
48
+ desc "Push all gems to RubyGems"
49
+ task :push do
50
+ GEMS.each_key { |name| Rake::Task["gems:push:#{name}"].invoke }
51
+ end
52
+
53
+ desc "Release all gems (build + push)"
54
+ task :release => [:build, :push]
55
+
56
+ desc "Clean built gems"
57
+ task :clean do
58
+ GEMS.each do |name, config|
59
+ Dir.glob("#{config[:path]}/*.gem").each do |gem_file|
60
+ FileUtils.rm(gem_file)
61
+ puts "Removed #{gem_file}"
62
+ end
63
+ end
64
+ end
65
+
66
+ namespace :build do
67
+ GEMS.each do |name, config|
68
+ desc "Build #{name} gem"
69
+ task name do
70
+ puts "Building #{name}..."
71
+ Dir.chdir(config[:path]) do
72
+ # Clean old gem files
73
+ Dir.glob("*.gem").each { |f| FileUtils.rm(f) }
74
+
75
+ # Build gem
76
+ result = system("gem build #{config[:gemspec]}")
77
+ raise "Failed to build #{name}" unless result
78
+
79
+ gem_file = Dir.glob("*.gem").first
80
+ puts " Built: #{gem_file}"
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ namespace :push do
87
+ GEMS.each do |name, config|
88
+ desc "Push #{name} to RubyGems"
89
+ task name do
90
+ Dir.chdir(config[:path]) do
91
+ gem_file = Dir.glob("*.gem").first
92
+
93
+ unless gem_file
94
+ puts "No gem file found for #{name}. Run 'rake gems:build:#{name}' first."
95
+ next
96
+ end
97
+
98
+ puts "Pushing #{gem_file} to RubyGems..."
99
+ result = system("gem push #{gem_file}")
100
+ raise "Failed to push #{name}" unless result
101
+
102
+ puts " Published #{name} to RubyGems"
103
+ end
104
+ end
105
+ end
106
+ end
107
+
108
+ namespace :install do
109
+ GEMS.each do |name, config|
110
+ desc "Install #{name} gem locally"
111
+ task name => "gems:build:#{name}" do
112
+ Dir.chdir(config[:path]) do
113
+ gem_file = Dir.glob("*.gem").first
114
+ system("gem install #{gem_file}")
115
+ puts " Installed #{name} locally"
116
+ end
117
+ end
118
+ end
119
+ end
120
+
121
+ namespace :bump do
122
+ %w[major minor patch].each do |level|
123
+ desc "Bump #{level} version for all gems"
124
+ task level do
125
+ GEMS.each do |name, config|
126
+ bump_version(config[:path], level)
127
+ puts "Bumped #{name} #{level} version"
128
+ end
129
+ end
130
+ end
131
+
132
+ GEMS.each do |name, config|
133
+ namespace name do
134
+ %w[major minor patch].each do |level|
135
+ desc "Bump #{name} #{level} version"
136
+ task level do
137
+ bump_version(config[:path], level)
138
+ new_version = gem_version(config[:path])
139
+ puts "Bumped #{name} to #{new_version}"
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end
145
+
146
+ # Helper methods
147
+
148
+ def gem_version(path)
149
+ version_file = Dir.glob("#{path}/lib/**/version.rb").first
150
+ return "unknown" unless version_file && File.exist?(version_file)
151
+
152
+ content = File.read(version_file)
153
+ if content =~ /VERSION\s*=\s*["']([^"']+)["']/
154
+ $1
155
+ else
156
+ "unknown"
157
+ end
158
+ end
159
+
160
+ def bump_version(path, level)
161
+ version_file = Dir.glob("#{path}/lib/**/version.rb").first
162
+ return unless version_file && File.exist?(version_file)
163
+
164
+ content = File.read(version_file)
165
+ if content =~ /VERSION\s*=\s*["'](\d+)\.(\d+)\.(\d+)["']/
166
+ major, minor, patch = $1.to_i, $2.to_i, $3.to_i
167
+
168
+ case level
169
+ when "major"
170
+ major += 1
171
+ minor = 0
172
+ patch = 0
173
+ when "minor"
174
+ minor += 1
175
+ patch = 0
176
+ when "patch"
177
+ patch += 1
178
+ end
179
+
180
+ new_version = "#{major}.#{minor}.#{patch}"
181
+ new_content = content.gsub(/VERSION\s*=\s*["'][^"']+["']/, "VERSION = \"#{new_version}\"")
182
+ File.write(version_file, new_content)
183
+ end
184
+ end
185
+ end