hind 0.1.4 → 0.1.6

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 89ca540ebc5d2ae79689ed9eb4c4ad9a89cea5b66991adecb4de8262f8d50867
4
- data.tar.gz: c1d5b39a3a772b1c4079376491361f67cb0dc2853a484e43ceca18b370798f1b
3
+ metadata.gz: 66f58f99f053b938815d051ff6404a478a4f340178f74556ce6ab63f0a93c2e4
4
+ data.tar.gz: 02e04a6c88687f9baf37013fc216dc7df52b5fa6e81f60170cbb31146335c91b
5
5
  SHA512:
6
- metadata.gz: cbb72f614d4355c2aa7f4d96b6c5f9babbd61565c7aa6e571c1f33096b5ed8154bda34cb20c9a55523bf5b9a628ec79f7901d0a04d71ceef992cd9930bd62f19
7
- data.tar.gz: 3e2288b97ef3f4028060ff754b069bf5aa457a8429edce1e9177c75307d605a872b87eced63184cbda4578e17ffc5b39c2529ac0317d968e0a75d83e364a7b43
6
+ metadata.gz: 529d648a5f0677180c4c73daa8d316661f4b82b7bd4802318c8bb42a7ed7bd8d2ffb38b69a82a274483638ea7bc1c161ea9d6bb6f3c7f84e3ff75464a1968c75
7
+ data.tar.gz: dc734f7623cffa1c0cc6260627d14916e6e9809c3b06afcad1bd87015c8c32b8bbe979ed978abb922adc3af7a19c7c3099f3217c00016711260aacd5079fab6c
data/exe/hind CHANGED
@@ -1,5 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
- require "hind"
3
- require "hind/cli"
2
+ # frozen_string_literal: true
3
+
4
+ require_relative '../lib/hind'
5
+ require_relative '../lib/hind/cli'
4
6
 
5
7
  Hind::CLI.start(ARGV)
data/lib/hind/cli.rb CHANGED
@@ -1,7 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'thor'
2
4
  require 'json'
3
5
  require 'pathname'
4
6
  require 'fileutils'
7
+ require 'yaml'
5
8
 
6
9
  module Hind
7
10
  class CLI < Thor
@@ -14,43 +17,47 @@ module Hind
14
17
  method_option :glob, type: :string, aliases: '-g', default: '**/*.rb', desc: 'File pattern to match'
15
18
  method_option :force, type: :boolean, aliases: '-f', desc: 'Overwrite output file if it exists'
16
19
  method_option :exclude, type: :array, aliases: '-e', desc: 'Patterns to exclude'
20
+ method_option :workers, type: :numeric, aliases: '-w', default: 1, desc: 'Number of parallel workers'
17
21
  def lsif
18
- validate_directory(options[:directory])
19
- validate_output_file(options[:output], options[:force])
22
+ config = load_config(options[:config])
23
+ opts = config.merge(symbolize_keys(options))
24
+
25
+ validate_directory(opts[:directory])
26
+ validate_output_file(opts[:output], opts[:force])
20
27
 
21
- files = find_files(options[:directory], options[:glob], options[:exclude])
22
- abort "No files found matching pattern '#{options[:glob]}'" if files.empty?
28
+ files = find_files(opts[:directory], opts[:glob], opts[:exclude])
29
+ abort "No files found matching pattern '#{opts[:glob]}'" if files.empty?
23
30
 
24
- say "Found #{files.length} files to process", :green if options[:verbose]
31
+ say "Found #{files.length} files to process", :green if opts[:verbose]
25
32
 
26
33
  begin
27
- generate_lsif(files, options)
28
- say "\nLSIF data has been written to: #{options[:output]}", :green if options[:verbose]
34
+ generate_lsif(files, opts)
35
+ say "\nLSIF data has been written to: #{opts[:output]}", :green if opts[:verbose]
29
36
  rescue StandardError => e
30
- abort "Error generating LSIF: #{e.message}"
37
+ handle_error(e, opts[:verbose])
31
38
  end
32
39
  end
33
40
 
34
- desc 'scip', 'Generate SCIP index'
35
- method_option :directory, type: :string, aliases: '-d', default: '.', desc: 'Root directory to process'
36
- method_option :output, type: :string, aliases: '-o', default: 'index.scip', desc: 'Output file path'
37
- method_option :glob, type: :string, aliases: '-g', default: '**/*.rb', desc: 'File pattern to match'
38
- method_option :force, type: :boolean, aliases: '-f', desc: 'Overwrite output file if it exists'
39
- method_option :exclude, type: :array, aliases: '-e', desc: 'Patterns to exclude'
40
- def scip
41
- validate_directory(options[:directory])
42
- validate_output_file(options[:output], options[:force])
41
+ desc 'check', 'Check LSIF dump file for validity and provide insights'
42
+ method_option :file, type: :string, aliases: '-f', default: 'dump.lsif', desc: 'LSIF dump file to check'
43
+ method_option :json, type: :boolean, desc: 'Output results in JSON format'
44
+ method_option :strict, type: :boolean, desc: 'Treat warnings as errors'
45
+ def check
46
+ abort "Error: File '#{options[:file]}' does not exist" unless File.exist?(options[:file])
43
47
 
44
- files = find_files(options[:directory], options[:glob], options[:exclude])
45
- abort "No files found matching pattern '#{options[:glob]}'" if files.empty?
48
+ begin
49
+ checker = Hind::LSIF::Checker.new(options[:file])
50
+ results = checker.check
46
51
 
47
- say "Found #{files.length} files to process", :green if options[:verbose]
52
+ if options[:json]
53
+ puts JSON.pretty_generate(results)
54
+ else
55
+ print_check_results(results)
56
+ end
48
57
 
49
- begin
50
- generate_scip(files, options)
51
- say "\nSCIP data has been written to: #{options[:output]}", :green if options[:verbose]
58
+ exit(1) if !results[:valid] || (options[:strict] && results[:warnings].any?)
52
59
  rescue StandardError => e
53
- abort "Error generating SCIP: #{e.message}"
60
+ handle_error(e, options[:verbose])
54
61
  end
55
62
  end
56
63
 
@@ -59,8 +66,73 @@ module Hind
59
66
  say "Hind version #{Hind::VERSION}"
60
67
  end
61
68
 
69
+ desc 'init', 'Initialize Hind configuration file'
70
+ method_option :force, type: :boolean, aliases: '-f', desc: 'Overwrite existing configuration'
71
+ def init
72
+ config_file = '.hind.yml'
73
+ if File.exist?(config_file) && !options[:force]
74
+ abort "Configuration file already exists. Use --force to overwrite."
75
+ end
76
+
77
+ create_default_config(config_file)
78
+ say "Created configuration file: #{config_file}", :green
79
+ end
80
+
62
81
  private
63
82
 
83
+ def generate_lsif(files, options)
84
+ # Initialize generator with absolute project root
85
+ generator = Hind::LSIF::Generator.new(
86
+ {
87
+ vertex_id: 1,
88
+ initial: true,
89
+ projectRoot: File.expand_path(options[:directory])
90
+ }
91
+ )
92
+
93
+ # Create file content map with relative paths
94
+ file_contents = {}
95
+ files.each do |file|
96
+ absolute_path = File.expand_path(file)
97
+ relative_path = Pathname.new(absolute_path)
98
+ .relative_path_from(Pathname.new(generator.metadata[:projectRoot]))
99
+ .to_s
100
+ file_contents[relative_path] = File.read(absolute_path)
101
+ rescue StandardError => e
102
+ warn "Warning: Failed to read file '#{file}': #{e.message}"
103
+ next
104
+ end
105
+
106
+ File.open(options[:output], 'w') do |output_file|
107
+ say "First pass: Collecting declarations...", :cyan if options[:verbose]
108
+
109
+ # First pass: Process all files to collect declarations
110
+ declaration_data = generator.collect_declarations(file_contents)
111
+
112
+ say "Found #{declaration_data[:declarations].size} declarations", :cyan if options[:verbose]
113
+ say "Processing files...", :cyan if options[:verbose]
114
+
115
+ # Second pass: Process each file
116
+ file_contents.each do |relative_path, content|
117
+ if options[:verbose]
118
+ say "Processing file: #{relative_path}", :cyan
119
+ end
120
+
121
+ lsif_data = generator.process_file(
122
+ content: content,
123
+ uri: relative_path
124
+ )
125
+
126
+ output_file.puts(lsif_data.map(&:to_json).join("\n"))
127
+ end
128
+
129
+ # Write cross-reference data
130
+ say "Finalizing cross-references...", :cyan if options[:verbose]
131
+ cross_refs = generator.finalize_cross_references
132
+ output_file.puts(cross_refs.map(&:to_json).join("\n")) if cross_refs&.any?
133
+ end
134
+ end
135
+
64
136
  def validate_directory(directory)
65
137
  abort "Error: Directory '#{directory}' does not exist" unless Dir.exist?(directory)
66
138
  end
@@ -87,55 +159,100 @@ module Hind
87
159
  files
88
160
  end
89
161
 
90
- def generate_lsif(files, options)
91
- global_state = Hind::LSIF::GlobalState.new
92
- vertex_id = 1
93
- initial = true
162
+ def load_config(config_path)
163
+ return {} unless config_path && File.exist?(config_path)
94
164
 
95
- File.open(options[:output], 'w') do |output_file|
96
- files.each do |file|
97
- if options[:verbose]
98
- say "Processing file: #{file}", :cyan
99
- end
165
+ begin
166
+ YAML.load_file(config_path) || {}
167
+ rescue StandardError => e
168
+ abort "Error loading config file: #{e.message}"
169
+ end
170
+ end
100
171
 
101
- relative_path = Pathname.new(file).relative_path_from(Pathname.new(options[:directory])).to_s
102
-
103
- begin
104
- generator = Hind::LSIF::Generator.new(
105
- {
106
- uri: relative_path,
107
- vertex_id: vertex_id,
108
- initial: initial,
109
- projectRoot: options[:directory]
110
- },
111
- global_state
112
- )
113
-
114
- output = generator.generate(File.read(file))
115
- vertex_id = output.last[:id].to_i + 1
116
- output_file.puts(output.map(&:to_json).join("\n"))
117
- initial = false
118
- rescue StandardError => e
119
- warn "Warning: Failed to process file '#{file}': #{e.message}"
120
- next
121
- end
122
- end
172
+ def create_default_config(config_file)
173
+ config = {
174
+ 'directory' => '.',
175
+ 'output' => 'dump.lsif',
176
+ 'glob' => '**/*.rb',
177
+ 'exclude' => [
178
+ 'test/**/*',
179
+ 'spec/**/*',
180
+ 'vendor/**/*'
181
+ ],
182
+ 'workers' => 1
183
+ }
184
+
185
+ File.write(config_file, config.to_yaml)
186
+ end
187
+
188
+ def print_check_results(results)
189
+ print_check_status(results[:valid])
190
+ print_check_errors(results[:errors])
191
+ print_check_warnings(results[:warnings])
192
+ print_check_statistics(results[:statistics])
193
+ end
194
+
195
+ def print_check_status(valid)
196
+ status = valid ? "✅ LSIF dump is valid" : "❌ LSIF dump contains errors"
197
+ say(status, valid ? :green : :red)
198
+ puts
199
+ end
200
+
201
+ def print_check_errors(errors)
202
+ return if errors.empty?
203
+
204
+ say "Errors:", :red
205
+ errors.each do |error|
206
+ say " • #{error}", :red
123
207
  end
208
+ puts
124
209
  end
125
210
 
126
- def generate_scip(files, options)
127
- raise NotImplementedError, "SCIP generation not yet implemented"
128
- # Similar to generate_lsif but using SCIP generator
211
+ def print_check_warnings(warnings)
212
+ return if warnings.empty?
213
+
214
+ say "Warnings:", :yellow
215
+ warnings.each do |warning|
216
+ say " • #{warning}", :yellow
217
+ end
218
+ puts
129
219
  end
130
220
 
131
- def load_config(config_path)
132
- return {} unless config_path && File.exist?(config_path)
221
+ def print_check_statistics(stats)
222
+ say "Statistics:", :cyan
223
+ say " Total Elements: #{stats[:total_elements]}"
224
+ say " Vertices: #{stats[:vertices][:total]}"
225
+ say " Edges: #{stats[:edges][:total]}"
226
+ say " Vertex/Edge Ratio: #{stats[:vertex_to_edge_ratio]}"
227
+ puts
133
228
 
134
- begin
135
- YAML.load_file(config_path) || {}
136
- rescue StandardError => e
137
- abort "Error loading config file: #{e.message}"
229
+ say " Documents: #{stats[:documents]}"
230
+ say " Ranges: #{stats[:ranges]}"
231
+ say " Definitions: #{stats[:definitions]}"
232
+ say " References: #{stats[:references]}"
233
+ say " Hovers: #{stats[:hovers]}"
234
+ puts
235
+
236
+ say " Vertex Types:", :cyan
237
+ stats[:vertices][:by_type].each do |type, count|
238
+ say " #{type}: #{count}"
239
+ end
240
+ puts
241
+
242
+ say " Edge Types:", :cyan
243
+ stats[:edges][:by_type].each do |type, count|
244
+ say " #{type}: #{count}"
138
245
  end
139
246
  end
247
+
248
+ def handle_error(error, verbose)
249
+ message = "Error: #{error.message}"
250
+ message += "\n#{error.backtrace.join("\n")}" if verbose
251
+ abort message
252
+ end
253
+
254
+ def symbolize_keys(hash)
255
+ hash.transform_keys(&:to_sym)
256
+ end
140
257
  end
141
258
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Hind
2
4
  module LSIF
3
5
  class Edge
@@ -12,7 +14,7 @@ module Hind
12
14
  @document = document
13
15
  end
14
16
 
15
- def to_json
17
+ def to_json(*_args)
16
18
  json = {
17
19
  id: @id,
18
20
  type: 'edge',