t-ruby 0.0.1

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.
@@ -0,0 +1,569 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+
5
+ module TRuby
6
+ # Integrates T-Ruby type packages with Bundler/RubyGems ecosystem
7
+ class BundlerIntegration
8
+ TYPES_GROUP = :types
9
+ TYPE_SUFFIX = "-types"
10
+ GEMFILE = "Gemfile"
11
+ GEMFILE_LOCK = "Gemfile.lock"
12
+
13
+ attr_reader :project_dir, :errors
14
+
15
+ def initialize(project_dir: ".")
16
+ @project_dir = project_dir
17
+ @errors = []
18
+ @type_gems = {}
19
+ end
20
+
21
+ # Check if project uses Bundler
22
+ def bundler_project?
23
+ File.exist?(gemfile_path)
24
+ end
25
+
26
+ # Initialize T-Ruby types support in existing Bundler project
27
+ def init
28
+ unless bundler_project?
29
+ @errors << "No Gemfile found. Run 'bundle init' first."
30
+ return false
31
+ end
32
+
33
+ add_types_group_to_gemfile unless types_group_exists?
34
+ create_types_directory
35
+ true
36
+ end
37
+
38
+ # Find type packages for installed gems
39
+ def discover_type_packages
40
+ return {} unless bundler_project?
41
+
42
+ installed_gems = parse_gemfile_lock
43
+ type_packages = {}
44
+
45
+ installed_gems.each do |gem_name, version|
46
+ type_gem = find_type_gem(gem_name)
47
+ type_packages[gem_name] = type_gem if type_gem
48
+ end
49
+
50
+ type_packages
51
+ end
52
+
53
+ # Add a type package dependency
54
+ def add_type_gem(gem_name, version: nil)
55
+ type_gem_name = "#{gem_name}#{TYPE_SUFFIX}"
56
+ version_constraint = version || ">= 0"
57
+
58
+ append_to_gemfile(type_gem_name, version_constraint, group: TYPES_GROUP)
59
+ @type_gems[gem_name] = { name: type_gem_name, version: version_constraint }
60
+
61
+ { gem: type_gem_name, version: version_constraint, status: :added }
62
+ end
63
+
64
+ # Remove a type package dependency
65
+ def remove_type_gem(gem_name)
66
+ type_gem_name = "#{gem_name}#{TYPE_SUFFIX}"
67
+ remove_from_gemfile(type_gem_name)
68
+ @type_gems.delete(gem_name)
69
+
70
+ { gem: type_gem_name, status: :removed }
71
+ end
72
+
73
+ # Sync type definitions from installed type gems
74
+ def sync_types
75
+ return { synced: [], errors: @errors } unless bundler_project?
76
+
77
+ synced = []
78
+ type_gems = find_installed_type_gems
79
+
80
+ type_gems.each do |gem_info|
81
+ result = sync_gem_types(gem_info)
82
+ synced << result if result[:success]
83
+ end
84
+
85
+ { synced: synced, errors: @errors }
86
+ end
87
+
88
+ # Generate a .trb-bundle.json manifest for compatibility
89
+ def generate_bundle_manifest
90
+ manifest = {
91
+ bundler_integration: true,
92
+ version: TRuby::VERSION,
93
+ types_group: TYPES_GROUP.to_s,
94
+ type_gems: list_type_gems,
95
+ local_types: list_local_types,
96
+ generated_at: Time.now.iso8601
97
+ }
98
+
99
+ manifest_path = File.join(@project_dir, ".trb-bundle.json")
100
+ File.write(manifest_path, JSON.pretty_generate(manifest))
101
+ manifest_path
102
+ end
103
+
104
+ # Load type definitions from Bundler-managed gems
105
+ def load_bundled_types
106
+ type_definitions = {}
107
+
108
+ find_installed_type_gems.each do |gem_info|
109
+ defs = load_gem_type_definitions(gem_info)
110
+ type_definitions.merge!(defs)
111
+ end
112
+
113
+ # Also load local types
114
+ local_types = load_local_type_definitions
115
+ type_definitions.merge!(local_types)
116
+
117
+ type_definitions
118
+ end
119
+
120
+ # Check compatibility between gem version and type version
121
+ def check_version_compatibility
122
+ issues = []
123
+ gemfile_lock = parse_gemfile_lock
124
+
125
+ @type_gems.each do |base_gem, type_info|
126
+ base_version = gemfile_lock[base_gem]
127
+ type_version = gemfile_lock[type_info[:name]]
128
+
129
+ next unless base_version && type_version
130
+
131
+ unless versions_compatible?(base_version, type_version)
132
+ issues << {
133
+ gem: base_gem,
134
+ gem_version: base_version,
135
+ type_gem: type_info[:name],
136
+ type_version: type_version,
137
+ message: "Version mismatch: #{base_gem}@#{base_version} vs #{type_info[:name]}@#{type_version}"
138
+ }
139
+ end
140
+ end
141
+
142
+ issues
143
+ end
144
+
145
+ # Create a new type gem scaffold
146
+ def create_type_gem_scaffold(gem_name, output_dir: nil)
147
+ type_gem_name = "#{gem_name}#{TYPE_SUFFIX}"
148
+ output = output_dir || File.join(@project_dir, type_gem_name)
149
+
150
+ FileUtils.mkdir_p(output)
151
+ FileUtils.mkdir_p(File.join(output, "lib", type_gem_name.gsub("-", "_")))
152
+ FileUtils.mkdir_p(File.join(output, "sig"))
153
+
154
+ # Create gemspec
155
+ create_type_gemspec(type_gem_name, gem_name, output)
156
+
157
+ # Create main type file
158
+ create_main_type_file(type_gem_name, gem_name, output)
159
+
160
+ # Create README
161
+ create_type_gem_readme(type_gem_name, gem_name, output)
162
+
163
+ { path: output, gem_name: type_gem_name, status: :created }
164
+ end
165
+
166
+ private
167
+
168
+ def gemfile_path
169
+ File.join(@project_dir, GEMFILE)
170
+ end
171
+
172
+ def gemfile_lock_path
173
+ File.join(@project_dir, GEMFILE_LOCK)
174
+ end
175
+
176
+ def types_group_exists?
177
+ return false unless File.exist?(gemfile_path)
178
+
179
+ content = File.read(gemfile_path)
180
+ content.include?("group :#{TYPES_GROUP}") || content.include?("group :types")
181
+ end
182
+
183
+ def add_types_group_to_gemfile
184
+ content = File.read(gemfile_path)
185
+
186
+ types_group = <<~RUBY
187
+
188
+ # T-Ruby type definitions
189
+ group :types do
190
+ # Add type gems here, e.g.:
191
+ # gem 'rails-types', '~> 7.0'
192
+ end
193
+ RUBY
194
+
195
+ File.write(gemfile_path, content + types_group)
196
+ end
197
+
198
+ def create_types_directory
199
+ types_dir = File.join(@project_dir, "types")
200
+ FileUtils.mkdir_p(types_dir)
201
+
202
+ # Create a sample .d.trb file
203
+ sample_path = File.join(types_dir, "custom.d.trb")
204
+ unless File.exist?(sample_path)
205
+ File.write(sample_path, <<~TRB)
206
+ # Custom type definitions for your project
207
+ # These types are available throughout your T-Ruby code
208
+
209
+ # Example type alias
210
+ # type UserId = String
211
+
212
+ # Example interface
213
+ # interface Serializable
214
+ # to_json: String
215
+ # from_json: (String) -> self
216
+ # end
217
+ TRB
218
+ end
219
+ end
220
+
221
+ def append_to_gemfile(gem_name, version, group:)
222
+ content = File.read(gemfile_path)
223
+
224
+ # Find the types group and add gem there
225
+ if content.include?("group :#{group}")
226
+ # Add inside existing group
227
+ new_content = content.gsub(
228
+ /(group :#{group}.*?do\s*\n)/m,
229
+ "\\1 gem '#{gem_name}', '#{version}'\n"
230
+ )
231
+ File.write(gemfile_path, new_content)
232
+ else
233
+ # Create group with gem
234
+ File.write(gemfile_path, content + <<~RUBY)
235
+
236
+ group :#{group} do
237
+ gem '#{gem_name}', '#{version}'
238
+ end
239
+ RUBY
240
+ end
241
+ end
242
+
243
+ def remove_from_gemfile(gem_name)
244
+ content = File.read(gemfile_path)
245
+ new_content = content.gsub(/^\s*gem ['"]#{gem_name}['"].*$\n?/, "")
246
+ File.write(gemfile_path, new_content)
247
+ end
248
+
249
+ def parse_gemfile_lock
250
+ return {} unless File.exist?(gemfile_lock_path)
251
+
252
+ gems = {}
253
+ in_specs = false
254
+
255
+ File.readlines(gemfile_lock_path).each do |line|
256
+ if line.strip == "specs:"
257
+ in_specs = true
258
+ next
259
+ end
260
+
261
+ if in_specs && line.match?(/^\s{4}(\S+)\s+\((.+)\)/)
262
+ match = line.match(/^\s{4}(\S+)\s+\((.+)\)/)
263
+ gems[match[1]] = match[2]
264
+ end
265
+
266
+ in_specs = false if in_specs && !line.start_with?(" ")
267
+ end
268
+
269
+ gems
270
+ end
271
+
272
+ def find_type_gem(gem_name)
273
+ type_gem_name = "#{gem_name}#{TYPE_SUFFIX}"
274
+
275
+ # Check if type gem exists in known registries
276
+ # This is a simplified check - in production would query RubyGems API
277
+ {
278
+ name: type_gem_name,
279
+ available: check_gem_availability(type_gem_name)
280
+ }
281
+ end
282
+
283
+ def check_gem_availability(gem_name)
284
+ # Simplified availability check
285
+ # In production, would use: Gem::SpecFetcher.fetcher.detect(:latest)
286
+ # For now, return based on common type packages
287
+ common_type_gems = %w[
288
+ rails-types
289
+ activerecord-types
290
+ activesupport-types
291
+ rspec-types
292
+ sidekiq-types
293
+ redis-types
294
+ pg-types
295
+ ]
296
+
297
+ common_type_gems.include?(gem_name)
298
+ end
299
+
300
+ def find_installed_type_gems
301
+ gems = parse_gemfile_lock
302
+ gems.select { |name, _| name.end_with?(TYPE_SUFFIX) }.map do |name, version|
303
+ base_gem = name.sub(/#{TYPE_SUFFIX}$/, "")
304
+ {
305
+ name: name,
306
+ base_gem: base_gem,
307
+ version: version,
308
+ path: find_gem_path(name, version)
309
+ }
310
+ end
311
+ end
312
+
313
+ def find_gem_path(gem_name, version)
314
+ # Try to find gem in standard locations
315
+ possible_paths = [
316
+ File.join(ENV["GEM_HOME"] || "", "gems", "#{gem_name}-#{version}"),
317
+ File.join(Dir.home, ".gem", "ruby", "*", "gems", "#{gem_name}-#{version}"),
318
+ File.join(@project_dir, "vendor", "bundle", "**", "gems", "#{gem_name}-#{version}")
319
+ ]
320
+
321
+ possible_paths.each do |pattern|
322
+ matches = Dir.glob(pattern)
323
+ return matches.first if matches.any?
324
+ end
325
+
326
+ nil
327
+ end
328
+
329
+ def sync_gem_types(gem_info)
330
+ return { success: false, gem: gem_info[:name] } unless gem_info[:path]
331
+
332
+ # Look for type definitions in the gem
333
+ type_files = Dir.glob(File.join(gem_info[:path], "**", "*.d.trb"))
334
+ rbs_files = Dir.glob(File.join(gem_info[:path], "sig", "**", "*.rbs"))
335
+
336
+ target_dir = File.join(@project_dir, ".trb-types", gem_info[:name])
337
+ FileUtils.mkdir_p(target_dir)
338
+
339
+ copied = []
340
+
341
+ (type_files + rbs_files).each do |file|
342
+ target = File.join(target_dir, File.basename(file))
343
+ FileUtils.cp(file, target)
344
+ copied << target
345
+ end
346
+
347
+ { success: true, gem: gem_info[:name], files: copied }
348
+ end
349
+
350
+ def load_gem_type_definitions(gem_info)
351
+ definitions = {}
352
+ return definitions unless gem_info[:path]
353
+
354
+ type_files = Dir.glob(File.join(gem_info[:path], "**", "*.d.trb"))
355
+
356
+ type_files.each do |file|
357
+ content = File.read(file)
358
+ parsed = parse_type_definitions(content)
359
+ definitions.merge!(parsed)
360
+ end
361
+
362
+ definitions
363
+ end
364
+
365
+ def load_local_type_definitions
366
+ definitions = {}
367
+ types_dir = File.join(@project_dir, "types")
368
+
369
+ return definitions unless Dir.exist?(types_dir)
370
+
371
+ Dir.glob(File.join(types_dir, "**", "*.d.trb")).each do |file|
372
+ content = File.read(file)
373
+ parsed = parse_type_definitions(content)
374
+ definitions.merge!(parsed)
375
+ end
376
+
377
+ definitions
378
+ end
379
+
380
+ def parse_type_definitions(content)
381
+ definitions = {}
382
+
383
+ # Parse type aliases
384
+ content.scan(/^\s*type\s+(\w+)\s*=\s*(.+)$/).each do |match|
385
+ definitions[match[0]] = { kind: :alias, definition: match[1] }
386
+ end
387
+
388
+ # Parse interfaces
389
+ content.scan(/^\s*interface\s+(\w+)/).each do |match|
390
+ definitions[match[0]] = { kind: :interface }
391
+ end
392
+
393
+ definitions
394
+ end
395
+
396
+ def list_type_gems
397
+ find_installed_type_gems.map do |gem_info|
398
+ {
399
+ name: gem_info[:name],
400
+ base_gem: gem_info[:base_gem],
401
+ version: gem_info[:version]
402
+ }
403
+ end
404
+ end
405
+
406
+ def list_local_types
407
+ types_dir = File.join(@project_dir, "types")
408
+ return [] unless Dir.exist?(types_dir)
409
+
410
+ Dir.glob(File.join(types_dir, "**", "*.d.trb")).map do |file|
411
+ File.basename(file)
412
+ end
413
+ end
414
+
415
+ def versions_compatible?(gem_version, type_version)
416
+ # Check if major.minor versions match
417
+ gem_parts = gem_version.split(".")
418
+ type_parts = type_version.split(".")
419
+
420
+ gem_parts[0] == type_parts[0] && gem_parts[1] == type_parts[1]
421
+ end
422
+
423
+ def create_type_gemspec(type_gem_name, base_gem, output_dir)
424
+ gemspec_content = <<~RUBY
425
+ # frozen_string_literal: true
426
+
427
+ Gem::Specification.new do |spec|
428
+ spec.name = "#{type_gem_name}"
429
+ spec.version = "0.1.0"
430
+ spec.authors = ["Your Name"]
431
+ spec.email = ["your.email@example.com"]
432
+
433
+ spec.summary = "T-Ruby type definitions for #{base_gem}"
434
+ spec.description = "Type definitions for #{base_gem} to be used with T-Ruby"
435
+ spec.homepage = "https://github.com/your-username/#{type_gem_name}"
436
+ spec.license = "MIT"
437
+ spec.required_ruby_version = ">= 3.0.0"
438
+
439
+ spec.metadata["rubygems_mfa_required"] = "true"
440
+ spec.metadata["source_code_uri"] = spec.homepage
441
+ spec.metadata["changelog_uri"] = "\#{spec.homepage}/blob/main/CHANGELOG.md"
442
+
443
+ spec.files = Dir.glob("{lib,sig}/**/*") + %w[README.md LICENSE.txt]
444
+ spec.require_paths = ["lib"]
445
+
446
+ # Match the base gem version
447
+ spec.add_dependency "#{base_gem}"
448
+ end
449
+ RUBY
450
+
451
+ File.write(File.join(output_dir, "#{type_gem_name}.gemspec"), gemspec_content)
452
+ end
453
+
454
+ def create_main_type_file(type_gem_name, base_gem, output_dir)
455
+ module_name = type_gem_name.gsub("-", "_").split("_").map(&:capitalize).join
456
+ lib_dir = File.join(output_dir, "lib", type_gem_name.gsub("-", "_"))
457
+
458
+ main_file = <<~RUBY
459
+ # frozen_string_literal: true
460
+
461
+ # Type definitions for #{base_gem}
462
+ # Auto-generated scaffold - customize as needed
463
+
464
+ module #{module_name}
465
+ VERSION = "0.1.0"
466
+ end
467
+ RUBY
468
+
469
+ File.write(File.join(lib_dir, "version.rb"), main_file)
470
+
471
+ # Create types directory and sample file
472
+ types_dir = File.join(output_dir, "sig")
473
+ FileUtils.mkdir_p(types_dir)
474
+
475
+ types_file = <<~TRB
476
+ # Type definitions for #{base_gem}
477
+ # Add your type definitions here
478
+
479
+ # Example:
480
+ # interface #{base_gem.capitalize}Client
481
+ # connect: (String) -> Boolean
482
+ # disconnect: () -> void
483
+ # end
484
+ TRB
485
+
486
+ File.write(File.join(types_dir, "#{base_gem}.d.trb"), types_file)
487
+ end
488
+
489
+ def create_type_gem_readme(type_gem_name, base_gem, output_dir)
490
+ readme = <<~MARKDOWN
491
+ # #{type_gem_name}
492
+
493
+ T-Ruby type definitions for [#{base_gem}](https://rubygems.org/gems/#{base_gem}).
494
+
495
+ ## Installation
496
+
497
+ Add this line to your Gemfile:
498
+
499
+ ```ruby
500
+ group :types do
501
+ gem '#{type_gem_name}'
502
+ end
503
+ ```
504
+
505
+ Then run:
506
+
507
+ ```bash
508
+ bundle install
509
+ ```
510
+
511
+ ## Usage
512
+
513
+ The type definitions will be automatically loaded by T-Ruby when compiling your `.trb` files.
514
+
515
+ ## Contributing
516
+
517
+ Bug reports and pull requests are welcome.
518
+
519
+ ## License
520
+
521
+ MIT License
522
+ MARKDOWN
523
+
524
+ File.write(File.join(output_dir, "README.md"), readme)
525
+ end
526
+ end
527
+
528
+ # Extension to PackageManager for Bundler support
529
+ class PackageManager
530
+ attr_reader :bundler
531
+
532
+ def initialize(project_dir: ".")
533
+ @project_dir = project_dir
534
+ @manifest = PackageManifest.load(File.join(project_dir, PackageManifest::MANIFEST_FILE))
535
+ @registry = PackageRegistry.new(local_path: File.join(project_dir, ".trb-packages"))
536
+ @resolver = DependencyResolver.new(@registry)
537
+ @bundler = BundlerIntegration.new(project_dir: project_dir)
538
+ end
539
+
540
+ # Use Bundler if available, fall back to native package management
541
+ def install_with_bundler_fallback
542
+ if @bundler.bundler_project?
543
+ @bundler.sync_types
544
+ else
545
+ install
546
+ end
547
+ end
548
+
549
+ # Migrate from native T-Ruby packages to Bundler
550
+ def migrate_to_bundler
551
+ return { success: false, error: "Not a Bundler project" } unless @bundler.bundler_project?
552
+
553
+ migrated = []
554
+
555
+ # Read existing T-Ruby manifest
556
+ if @manifest
557
+ @manifest.dependencies.each do |name, version|
558
+ result = @bundler.add_type_gem(name, version: version)
559
+ migrated << result
560
+ end
561
+ end
562
+
563
+ # Generate new bundle manifest
564
+ @bundler.generate_bundle_manifest
565
+
566
+ { success: true, migrated: migrated }
567
+ end
568
+ end
569
+ end