hind 0.1.4 → 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
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',