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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +222 -0
- data/LICENSE +21 -0
- data/README.md +1165 -0
- data/Rakefile +728 -0
- data/app/controllers/lyra/application_controller.rb +23 -0
- data/app/controllers/lyra/dashboard_controller.rb +624 -0
- data/app/controllers/lyra/flow_controller.rb +224 -0
- data/app/controllers/lyra/privacy_controller.rb +182 -0
- data/app/views/lyra/dashboard/audit_trail.html.erb +324 -0
- data/app/views/lyra/dashboard/discrepancies.html.erb +125 -0
- data/app/views/lyra/dashboard/event_graph_view.html.erb +525 -0
- data/app/views/lyra/dashboard/heatmap_view.html.erb +155 -0
- data/app/views/lyra/dashboard/index.html.erb +119 -0
- data/app/views/lyra/dashboard/model_overview.html.erb +115 -0
- data/app/views/lyra/dashboard/projections.html.erb +302 -0
- data/app/views/lyra/dashboard/schema.html.erb +283 -0
- data/app/views/lyra/dashboard/schema_history.html.erb +78 -0
- data/app/views/lyra/dashboard/schema_version.html.erb +340 -0
- data/app/views/lyra/dashboard/verification.html.erb +370 -0
- data/app/views/lyra/flow/crud_mapping.html.erb +125 -0
- data/app/views/lyra/flow/timeline.html.erb +260 -0
- data/app/views/lyra/privacy/pii_detection.html.erb +148 -0
- data/app/views/lyra/privacy/policy.html.erb +188 -0
- data/app/workflows/es_async_mode_workflow.rb +80 -0
- data/app/workflows/es_sync_mode_workflow.rb +64 -0
- data/app/workflows/hijack_mode_workflow.rb +54 -0
- data/app/workflows/lifecycle_workflow.rb +43 -0
- data/app/workflows/monitor_mode_workflow.rb +39 -0
- data/config/privacy_policies.rb +273 -0
- data/config/routes.rb +48 -0
- data/lib/lyra/aggregate.rb +131 -0
- data/lib/lyra/associations/event_aware.rb +225 -0
- data/lib/lyra/command.rb +81 -0
- data/lib/lyra/command_handler.rb +155 -0
- data/lib/lyra/configuration.rb +124 -0
- data/lib/lyra/consistency/read_your_writes.rb +91 -0
- data/lib/lyra/correlation.rb +144 -0
- data/lib/lyra/dual_view.rb +231 -0
- data/lib/lyra/engine.rb +67 -0
- data/lib/lyra/event.rb +71 -0
- data/lib/lyra/event_analyzer.rb +135 -0
- data/lib/lyra/event_flow.rb +449 -0
- data/lib/lyra/event_mapper.rb +106 -0
- data/lib/lyra/event_store_adapter.rb +72 -0
- data/lib/lyra/id_generator.rb +137 -0
- data/lib/lyra/interceptors/association_interceptor.rb +169 -0
- data/lib/lyra/interceptors/crud_interceptor.rb +543 -0
- data/lib/lyra/privacy/gdpr_compliance.rb +161 -0
- data/lib/lyra/privacy/pii_detector.rb +85 -0
- data/lib/lyra/privacy/pii_masker.rb +66 -0
- data/lib/lyra/privacy/policy_integration.rb +253 -0
- data/lib/lyra/projection.rb +94 -0
- data/lib/lyra/projections/async_projection_job.rb +63 -0
- data/lib/lyra/projections/cached_projection.rb +322 -0
- data/lib/lyra/projections/cached_relation.rb +757 -0
- data/lib/lyra/projections/event_store_reader.rb +127 -0
- data/lib/lyra/projections/model_projection.rb +143 -0
- data/lib/lyra/schema/diff.rb +331 -0
- data/lib/lyra/schema/event_class_registrar.rb +63 -0
- data/lib/lyra/schema/generator.rb +190 -0
- data/lib/lyra/schema/reporter.rb +188 -0
- data/lib/lyra/schema/store.rb +156 -0
- data/lib/lyra/schema/validator.rb +100 -0
- data/lib/lyra/strict_data_access.rb +363 -0
- data/lib/lyra/verification/crud_lifecycle_workflow.rb +456 -0
- data/lib/lyra/verification/workflow_generator.rb +540 -0
- data/lib/lyra/version.rb +3 -0
- data/lib/lyra/visualization/activity_heatmap.rb +215 -0
- data/lib/lyra/visualization/event_graph.rb +310 -0
- data/lib/lyra/visualization/timeline.rb +398 -0
- data/lib/lyra.rb +150 -0
- data/lib/tasks/dist.rake +391 -0
- data/lib/tasks/gems.rake +185 -0
- data/lib/tasks/lyra_schema.rake +231 -0
- data/lib/tasks/lyra_workflows.rake +452 -0
- data/lib/tasks/public_release.rake +351 -0
- data/lib/tasks/stats.rake +175 -0
- data/lib/tasks/testbed.rake +479 -0
- data/lib/tasks/version.rake +159 -0
- metadata +221 -0
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Rake tasks for extracting public files to lyra-engine repo
|
|
4
|
+
#
|
|
5
|
+
# Usage:
|
|
6
|
+
# rake public:build # Build lyra-engine directory
|
|
7
|
+
# rake public:sync # Sync changes to existing lyra-engine
|
|
8
|
+
# rake public:init # Initialize lyra-engine as git repo
|
|
9
|
+
# rake public:clean # Remove lyra-engine directory
|
|
10
|
+
# rake public:release[tag] # Full release workflow
|
|
11
|
+
|
|
12
|
+
require "fileutils"
|
|
13
|
+
|
|
14
|
+
namespace :public do
|
|
15
|
+
LYRA_ROOT = File.expand_path("../..", __dir__)
|
|
16
|
+
PUBLIC_DIR = File.join(LYRA_ROOT, "lyra-engine")
|
|
17
|
+
|
|
18
|
+
# Files and directories to include in public release
|
|
19
|
+
PUBLIC_INCLUDES = %w[
|
|
20
|
+
app/
|
|
21
|
+
bin/
|
|
22
|
+
config/
|
|
23
|
+
docs/
|
|
24
|
+
examples/blog_app/
|
|
25
|
+
examples/privacy_examples.rb
|
|
26
|
+
examples/privacy_policy_usage.rb
|
|
27
|
+
examples/usage_examples.rb
|
|
28
|
+
gems/
|
|
29
|
+
lib/
|
|
30
|
+
test/
|
|
31
|
+
.gitignore
|
|
32
|
+
.ruby-version
|
|
33
|
+
docker-compose.yml
|
|
34
|
+
Gemfile
|
|
35
|
+
Gemfile.lock
|
|
36
|
+
LICENSE
|
|
37
|
+
lyra.gemspec
|
|
38
|
+
Rakefile
|
|
39
|
+
README.md
|
|
40
|
+
CHANGELOG.md
|
|
41
|
+
].freeze
|
|
42
|
+
|
|
43
|
+
# Patterns to exclude from public release (applied after includes)
|
|
44
|
+
PUBLIC_EXCLUDES = %w[
|
|
45
|
+
.DS_Store
|
|
46
|
+
*.log
|
|
47
|
+
/coverage/
|
|
48
|
+
/tmp/
|
|
49
|
+
/log/
|
|
50
|
+
/vendor/bundle/
|
|
51
|
+
/gems/*/.bundle/
|
|
52
|
+
/gems/*/coverage/
|
|
53
|
+
/test/dummy/db/*.sqlite3
|
|
54
|
+
/test/reports/
|
|
55
|
+
/lib/tasks/gems.rake
|
|
56
|
+
/lib/tasks/public_release.rake
|
|
57
|
+
/lib/tasks/testbed.rake
|
|
58
|
+
/lib/tasks/stats.rake
|
|
59
|
+
/lib/tasks/dist.rake
|
|
60
|
+
].freeze
|
|
61
|
+
|
|
62
|
+
# Files that need sanitization (remove internal references)
|
|
63
|
+
SANITIZE_FILES = %w[
|
|
64
|
+
README.md
|
|
65
|
+
].freeze
|
|
66
|
+
|
|
67
|
+
desc "Build lyra-engine directory with public files"
|
|
68
|
+
task :build => :clean do
|
|
69
|
+
puts "=" * 60
|
|
70
|
+
puts "Building lyra-engine public release"
|
|
71
|
+
puts "=" * 60
|
|
72
|
+
|
|
73
|
+
FileUtils.mkdir_p(PUBLIC_DIR)
|
|
74
|
+
|
|
75
|
+
PUBLIC_INCLUDES.each do |item|
|
|
76
|
+
src = File.join(LYRA_ROOT, item)
|
|
77
|
+
next unless File.exist?(src)
|
|
78
|
+
|
|
79
|
+
dest = File.join(PUBLIC_DIR, item)
|
|
80
|
+
if File.directory?(src)
|
|
81
|
+
sync_directory_public(src, dest)
|
|
82
|
+
puts " + #{item}"
|
|
83
|
+
else
|
|
84
|
+
FileUtils.mkdir_p(File.dirname(dest))
|
|
85
|
+
FileUtils.cp(src, dest)
|
|
86
|
+
puts " + #{item}"
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Sanitize files for public release
|
|
91
|
+
sanitize_readme
|
|
92
|
+
sanitize_changelog
|
|
93
|
+
sanitize_gitignore
|
|
94
|
+
sanitize_gemspec
|
|
95
|
+
remove_internal_rake_tasks
|
|
96
|
+
|
|
97
|
+
puts ""
|
|
98
|
+
puts "=" * 60
|
|
99
|
+
puts "lyra-engine built successfully!"
|
|
100
|
+
puts "Location: #{PUBLIC_DIR}"
|
|
101
|
+
puts "=" * 60
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
desc "Sync changes to existing lyra-engine directory"
|
|
105
|
+
task :sync do
|
|
106
|
+
unless File.directory?(PUBLIC_DIR)
|
|
107
|
+
puts "lyra-engine directory not found. Run 'rake public:build' first."
|
|
108
|
+
exit 1
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
puts "Syncing changes to lyra-engine..."
|
|
112
|
+
Rake::Task["public:build"].invoke
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
desc "Initialize lyra-engine as a git repository"
|
|
116
|
+
task :init => :build do
|
|
117
|
+
Dir.chdir(PUBLIC_DIR) do
|
|
118
|
+
unless File.directory?(".git")
|
|
119
|
+
system("git init")
|
|
120
|
+
system("git add .")
|
|
121
|
+
system("git commit -m 'Initial commit - Lyra Engine v#{lyra_version}'")
|
|
122
|
+
puts ""
|
|
123
|
+
puts "Git repository initialized."
|
|
124
|
+
puts "Add remote with: git remote add origin <url>"
|
|
125
|
+
else
|
|
126
|
+
puts "Git repository already initialized."
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
desc "Clean lyra-engine directory"
|
|
132
|
+
task :clean do
|
|
133
|
+
if File.directory?(PUBLIC_DIR)
|
|
134
|
+
# Preserve .git directory if it exists
|
|
135
|
+
git_dir = File.join(PUBLIC_DIR, ".git")
|
|
136
|
+
git_backup = File.join(LYRA_ROOT, ".lyra-engine-git-backup")
|
|
137
|
+
|
|
138
|
+
if File.directory?(git_dir)
|
|
139
|
+
FileUtils.mv(git_dir, git_backup)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Remove all other contents
|
|
143
|
+
Dir.glob("#{PUBLIC_DIR}/*", File::FNM_DOTMATCH).each do |item|
|
|
144
|
+
next if item.end_with?(".", "..")
|
|
145
|
+
FileUtils.rm_rf(item)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Restore .git directory
|
|
149
|
+
if File.directory?(git_backup)
|
|
150
|
+
FileUtils.mv(git_backup, git_dir)
|
|
151
|
+
puts "Cleaned lyra-engine (preserved .git)"
|
|
152
|
+
else
|
|
153
|
+
puts "Cleaned lyra-engine"
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
desc "Full release workflow: build, init, tag"
|
|
159
|
+
task :release, [:tag] => :init do |t, args|
|
|
160
|
+
tag = args[:tag] || "v#{lyra_version}"
|
|
161
|
+
|
|
162
|
+
Dir.chdir(PUBLIC_DIR) do
|
|
163
|
+
# Stage any new changes
|
|
164
|
+
system("git add .")
|
|
165
|
+
|
|
166
|
+
# Check if there are changes to commit
|
|
167
|
+
changes = `git status --porcelain`.strip
|
|
168
|
+
if changes.empty?
|
|
169
|
+
puts "No changes to commit."
|
|
170
|
+
else
|
|
171
|
+
system("git commit -m 'Release #{tag}'")
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Create tag
|
|
175
|
+
system("git tag -a #{tag} -m 'Release #{tag}'")
|
|
176
|
+
puts ""
|
|
177
|
+
puts "=" * 60
|
|
178
|
+
puts "Release #{tag} prepared!"
|
|
179
|
+
puts ""
|
|
180
|
+
puts "To push to remote:"
|
|
181
|
+
puts " cd lyra-engine"
|
|
182
|
+
puts " git remote add origin git@github.com:mpantel/lyra-engine.git"
|
|
183
|
+
puts " git push -u origin main"
|
|
184
|
+
puts " git push origin #{tag}"
|
|
185
|
+
puts "=" * 60
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
desc "Show what would be included in public release"
|
|
190
|
+
task :preview do
|
|
191
|
+
puts "Files to be included in lyra-engine:"
|
|
192
|
+
puts "-" * 40
|
|
193
|
+
|
|
194
|
+
PUBLIC_INCLUDES.each do |item|
|
|
195
|
+
src = File.join(LYRA_ROOT, item)
|
|
196
|
+
if File.exist?(src)
|
|
197
|
+
if File.directory?(src)
|
|
198
|
+
count = Dir.glob("#{src}/**/*").count { |f| File.file?(f) }
|
|
199
|
+
puts " #{item} (#{count} files)"
|
|
200
|
+
else
|
|
201
|
+
puts " #{item}"
|
|
202
|
+
end
|
|
203
|
+
else
|
|
204
|
+
puts " #{item} (not found)"
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
puts ""
|
|
209
|
+
puts "Excluded patterns:"
|
|
210
|
+
PUBLIC_EXCLUDES.each { |p| puts " - #{p}" }
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Helper methods
|
|
214
|
+
|
|
215
|
+
def sync_directory_public(src, dest)
|
|
216
|
+
FileUtils.mkdir_p(dest)
|
|
217
|
+
|
|
218
|
+
Dir.glob("#{src}/**/*", File::FNM_DOTMATCH).each do |path|
|
|
219
|
+
next if path.end_with?(".", "..")
|
|
220
|
+
next if File.directory?(path)
|
|
221
|
+
next if excluded_public?(path)
|
|
222
|
+
|
|
223
|
+
rel_path = path.sub("#{src}/", "")
|
|
224
|
+
dest_path = File.join(dest, rel_path)
|
|
225
|
+
|
|
226
|
+
FileUtils.mkdir_p(File.dirname(dest_path))
|
|
227
|
+
FileUtils.cp(path, dest_path)
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def excluded_public?(path)
|
|
232
|
+
relative = path.sub(LYRA_ROOT, "")
|
|
233
|
+
|
|
234
|
+
PUBLIC_EXCLUDES.any? do |pattern|
|
|
235
|
+
if pattern.include?("*")
|
|
236
|
+
File.fnmatch(pattern, relative, File::FNM_PATHNAME)
|
|
237
|
+
else
|
|
238
|
+
relative.include?(pattern)
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def lyra_version
|
|
244
|
+
# Extract version from lyra.gemspec or version file
|
|
245
|
+
version_file = File.join(LYRA_ROOT, "lib/lyra/version.rb")
|
|
246
|
+
if File.exist?(version_file)
|
|
247
|
+
content = File.read(version_file)
|
|
248
|
+
if content =~ /VERSION\s*=\s*["']([^"']+)["']/
|
|
249
|
+
return $1
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
"0.6.0"
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def sanitize_readme
|
|
256
|
+
readme_path = File.join(PUBLIC_DIR, "README.md")
|
|
257
|
+
return unless File.exist?(readme_path)
|
|
258
|
+
|
|
259
|
+
content = File.read(readme_path)
|
|
260
|
+
|
|
261
|
+
# Remove aegean-epay-testbed section (case insensitive)
|
|
262
|
+
content.gsub!(/^##+ .*aegean.?e?pay.?testbed.*?(?=^##|\z)/mi, "")
|
|
263
|
+
|
|
264
|
+
# Remove solidus case study section (case insensitive)
|
|
265
|
+
content.gsub!(/^##+ .*solidus.*?(?=^##|\z)/mi, "")
|
|
266
|
+
|
|
267
|
+
# Remove any references to private examples
|
|
268
|
+
content.gsub!(/examples\/aegean_epay_testbed\/?/, "examples/blog_app/")
|
|
269
|
+
content.gsub!(/examples\/solidus_case_study\/?/, "examples/blog_app/")
|
|
270
|
+
|
|
271
|
+
# Clean up multiple consecutive blank lines
|
|
272
|
+
content.gsub!(/\n{3,}/, "\n\n")
|
|
273
|
+
|
|
274
|
+
File.write(readme_path, content)
|
|
275
|
+
puts " Sanitized README.md"
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def sanitize_changelog
|
|
279
|
+
changelog_path = File.join(PUBLIC_DIR, "CHANGELOG.md")
|
|
280
|
+
return unless File.exist?(changelog_path)
|
|
281
|
+
|
|
282
|
+
content = File.read(changelog_path)
|
|
283
|
+
|
|
284
|
+
# Remove lines referencing private examples
|
|
285
|
+
lines = content.lines.reject do |line|
|
|
286
|
+
line =~ /aegean.?epay.?testbed|solidus.?case.?study/i
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
# Replace references in remaining text
|
|
290
|
+
content = lines.join
|
|
291
|
+
content.gsub!(/aegean.?epay.?testbed/i, "blog_app")
|
|
292
|
+
content.gsub!(/solidus.?case.?study/i, "blog_app")
|
|
293
|
+
|
|
294
|
+
# Clean up multiple consecutive blank lines
|
|
295
|
+
content.gsub!(/\n{3,}/, "\n\n")
|
|
296
|
+
|
|
297
|
+
File.write(changelog_path, content)
|
|
298
|
+
puts " Sanitized CHANGELOG.md"
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
def sanitize_gitignore
|
|
302
|
+
gitignore_path = File.join(PUBLIC_DIR, ".gitignore")
|
|
303
|
+
return unless File.exist?(gitignore_path)
|
|
304
|
+
|
|
305
|
+
content = File.read(gitignore_path)
|
|
306
|
+
|
|
307
|
+
# Remove lines referencing private directories
|
|
308
|
+
lines = content.lines.reject do |line|
|
|
309
|
+
line =~ /aegean.?epay.?testbed|solidus|docs-site|paper|papertse|papersp|thesis|proposal|research/i
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
File.write(gitignore_path, lines.join)
|
|
313
|
+
puts " Sanitized .gitignore"
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
def sanitize_gemspec
|
|
317
|
+
gemspec_path = File.join(PUBLIC_DIR, "lyra.gemspec")
|
|
318
|
+
return unless File.exist?(gemspec_path)
|
|
319
|
+
|
|
320
|
+
content = File.read(gemspec_path)
|
|
321
|
+
|
|
322
|
+
# Update homepage to public repo
|
|
323
|
+
content.gsub!(
|
|
324
|
+
/spec\.homepage\s*=\s*["'][^"']*["']/,
|
|
325
|
+
'spec.homepage = "https://github.com/mpantel/lyra-engine"'
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
# Update metadata URLs
|
|
329
|
+
content.gsub!(
|
|
330
|
+
/github\.com\/mpantel\/lyra(?!-engine)/,
|
|
331
|
+
"github.com/mpantel/lyra-engine"
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
File.write(gemspec_path, content)
|
|
335
|
+
puts " Sanitized lyra.gemspec"
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
def remove_internal_rake_tasks
|
|
339
|
+
tasks_dir = File.join(PUBLIC_DIR, "lib/tasks")
|
|
340
|
+
return unless File.directory?(tasks_dir)
|
|
341
|
+
|
|
342
|
+
internal_tasks = %w[gems.rake public_release.rake testbed.rake stats.rake dist.rake]
|
|
343
|
+
internal_tasks.each do |task|
|
|
344
|
+
task_path = File.join(tasks_dir, task)
|
|
345
|
+
if File.exist?(task_path)
|
|
346
|
+
FileUtils.rm(task_path)
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
puts " Removed internal rake tasks"
|
|
350
|
+
end
|
|
351
|
+
end
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
namespace :lyra do
|
|
4
|
+
desc "Display code statistics for Lyra and its components"
|
|
5
|
+
task :stats do
|
|
6
|
+
class CodeStats
|
|
7
|
+
COMPONENTS = {
|
|
8
|
+
"Lyra Core (lib/)" => "lib",
|
|
9
|
+
"Controllers (app/)" => "app",
|
|
10
|
+
"Tests (test/)" => "test",
|
|
11
|
+
"PetriFlow Gem" => "gems/petri_flow",
|
|
12
|
+
"PAM DSL Gem" => "gems/pam_dsl",
|
|
13
|
+
"Examples" => "examples"
|
|
14
|
+
}.freeze
|
|
15
|
+
|
|
16
|
+
def initialize
|
|
17
|
+
@stats = {}
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def run
|
|
21
|
+
print_header
|
|
22
|
+
calculate_all_stats
|
|
23
|
+
print_stats_table
|
|
24
|
+
print_summary
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def print_header
|
|
30
|
+
puts "\n" + "=" * 70
|
|
31
|
+
puts "Lyra Code Statistics"
|
|
32
|
+
puts "=" * 70
|
|
33
|
+
puts "Generated: #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}"
|
|
34
|
+
puts "=" * 70
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def calculate_all_stats
|
|
38
|
+
COMPONENTS.each do |name, path|
|
|
39
|
+
next unless Dir.exist?(path)
|
|
40
|
+
|
|
41
|
+
@stats[name] = calculate_stats(path)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def calculate_stats(path)
|
|
46
|
+
ruby_files = Dir.glob("#{path}/**/*.rb")
|
|
47
|
+
|
|
48
|
+
total_lines = 0
|
|
49
|
+
code_lines = 0
|
|
50
|
+
comment_lines = 0
|
|
51
|
+
blank_lines = 0
|
|
52
|
+
|
|
53
|
+
ruby_files.each do |file|
|
|
54
|
+
File.readlines(file).each do |line|
|
|
55
|
+
total_lines += 1
|
|
56
|
+
stripped = line.strip
|
|
57
|
+
|
|
58
|
+
if stripped.empty?
|
|
59
|
+
blank_lines += 1
|
|
60
|
+
elsif stripped.start_with?("#")
|
|
61
|
+
comment_lines += 1
|
|
62
|
+
else
|
|
63
|
+
code_lines += 1
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
{
|
|
69
|
+
files: ruby_files.count,
|
|
70
|
+
total_lines: total_lines,
|
|
71
|
+
code_lines: code_lines,
|
|
72
|
+
comment_lines: comment_lines,
|
|
73
|
+
blank_lines: blank_lines
|
|
74
|
+
}
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def print_stats_table
|
|
78
|
+
puts "\n%-25s %8s %10s %10s %10s %10s" % ["Component", "Files", "Total", "Code", "Comments", "Blank"]
|
|
79
|
+
puts "-" * 75
|
|
80
|
+
|
|
81
|
+
@stats.each do |name, data|
|
|
82
|
+
puts "%-25s %8d %10d %10d %10d %10d" % [
|
|
83
|
+
name,
|
|
84
|
+
data[:files],
|
|
85
|
+
data[:total_lines],
|
|
86
|
+
data[:code_lines],
|
|
87
|
+
data[:comment_lines],
|
|
88
|
+
data[:blank_lines]
|
|
89
|
+
]
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
puts "-" * 75
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def print_summary
|
|
96
|
+
totals = @stats.values.reduce({
|
|
97
|
+
files: 0, total_lines: 0, code_lines: 0, comment_lines: 0, blank_lines: 0
|
|
98
|
+
}) do |sum, data|
|
|
99
|
+
sum.each_key { |k| sum[k] += data[k] }
|
|
100
|
+
sum
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
puts "%-25s %8d %10d %10d %10d %10d" % [
|
|
104
|
+
"TOTAL",
|
|
105
|
+
totals[:files],
|
|
106
|
+
totals[:total_lines],
|
|
107
|
+
totals[:code_lines],
|
|
108
|
+
totals[:comment_lines],
|
|
109
|
+
totals[:blank_lines]
|
|
110
|
+
]
|
|
111
|
+
|
|
112
|
+
# Additional metrics
|
|
113
|
+
puts "\n" + "=" * 70
|
|
114
|
+
puts "Summary Metrics"
|
|
115
|
+
puts "=" * 70
|
|
116
|
+
|
|
117
|
+
lib_stats = @stats["Lyra Core (lib/)"] || { code_lines: 0 }
|
|
118
|
+
test_stats = @stats["Tests (test/)"] || { code_lines: 0 }
|
|
119
|
+
|
|
120
|
+
if lib_stats[:code_lines] > 0
|
|
121
|
+
ratio = (test_stats[:code_lines].to_f / lib_stats[:code_lines]).round(2)
|
|
122
|
+
puts "Test to Code Ratio: #{ratio}:1"
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
if totals[:code_lines] > 0
|
|
126
|
+
comment_ratio = ((totals[:comment_lines].to_f / totals[:code_lines]) * 100).round(1)
|
|
127
|
+
puts "Comment Density: #{comment_ratio}%"
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
puts "Total Ruby Files: #{totals[:files]}"
|
|
131
|
+
puts "Total Lines of Code: #{totals[:code_lines]}"
|
|
132
|
+
puts "=" * 70 + "\n\n"
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
CodeStats.new.run
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
desc "Display detailed stats for a specific component"
|
|
140
|
+
task :stats_detail, [:component] do |_t, args|
|
|
141
|
+
component = args[:component] || "lib"
|
|
142
|
+
|
|
143
|
+
puts "\n" + "=" * 70
|
|
144
|
+
puts "Detailed Stats for: #{component}"
|
|
145
|
+
puts "=" * 70
|
|
146
|
+
|
|
147
|
+
ruby_files = Dir.glob("#{component}/**/*.rb").sort
|
|
148
|
+
|
|
149
|
+
if ruby_files.empty?
|
|
150
|
+
puts "No Ruby files found in #{component}"
|
|
151
|
+
next
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
puts "\n%-60s %8s" % ["File", "Lines"]
|
|
155
|
+
puts "-" * 70
|
|
156
|
+
|
|
157
|
+
total = 0
|
|
158
|
+
ruby_files.each do |file|
|
|
159
|
+
lines = File.readlines(file).count
|
|
160
|
+
total += lines
|
|
161
|
+
relative_path = file.sub("#{component}/", "")
|
|
162
|
+
puts "%-60s %8d" % [relative_path.truncate(60), lines]
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
puts "-" * 70
|
|
166
|
+
puts "%-60s %8d" % ["TOTAL (#{ruby_files.count} files)", total]
|
|
167
|
+
puts "=" * 70 + "\n\n"
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
class String
|
|
172
|
+
def truncate(max)
|
|
173
|
+
length > max ? "...#{self[-(max-3)..]}" : self
|
|
174
|
+
end
|
|
175
|
+
end
|