hind 0.1.5 → 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: 5fa041a7ffe7e916e1f3715500d21a9dea2a3a6ce60990be8cf4e7578f704702
4
- data.tar.gz: '0779f87c520ddc1edddf0448c96d596194982d5b87fa5c380bb3a1132500bd9e'
3
+ metadata.gz: 66f58f99f053b938815d051ff6404a478a4f340178f74556ce6ab63f0a93c2e4
4
+ data.tar.gz: 02e04a6c88687f9baf37013fc216dc7df52b5fa6e81f60170cbb31146335c91b
5
5
  SHA512:
6
- metadata.gz: 397847759b6c959511602d276eee22f7e89959c5858d840cf8398b874a39ef6ccfeec8d876e9c0539d5cc0bc0cce77cb1e297b7fabc30c9a4562bfb7e85ddb7b
7
- data.tar.gz: 5c823067473c8b784a4868bf696b4374735b548eb8eb9341fb887dc78028e9223aeaac7172c621c624d0e8a3de221b50e101e908177626f9c316797e8ce2de70
6
+ metadata.gz: 529d648a5f0677180c4c73daa8d316661f4b82b7bd4802318c8bb42a7ed7bd8d2ffb38b69a82a274483638ea7bc1c161ea9d6bb6f3c7f84e3ff75464a1968c75
7
+ data.tar.gz: dc734f7623cffa1c0cc6260627d14916e6e9809c3b06afcad1bd87015c8c32b8bbe979ed978abb922adc3af7a19c7c3099f3217c00016711260aacd5079fab6c
data/exe/hind CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- require 'hind'
5
- require 'hind/cli'
4
+ require_relative '../lib/hind'
5
+ require_relative '../lib/hind/cli'
6
6
 
7
7
  Hind::CLI.start(ARGV)
data/lib/hind/cli.rb CHANGED
@@ -4,6 +4,7 @@ require 'thor'
4
4
  require 'json'
5
5
  require 'pathname'
6
6
  require 'fileutils'
7
+ require 'yaml'
7
8
 
8
9
  module Hind
9
10
  class CLI < Thor
@@ -16,43 +17,47 @@ module Hind
16
17
  method_option :glob, type: :string, aliases: '-g', default: '**/*.rb', desc: 'File pattern to match'
17
18
  method_option :force, type: :boolean, aliases: '-f', desc: 'Overwrite output file if it exists'
18
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'
19
21
  def lsif
20
- validate_directory(options[:directory])
21
- validate_output_file(options[:output], options[:force])
22
+ config = load_config(options[:config])
23
+ opts = config.merge(symbolize_keys(options))
22
24
 
23
- files = find_files(options[:directory], options[:glob], options[:exclude])
24
- abort "No files found matching pattern '#{options[:glob]}'" if files.empty?
25
+ validate_directory(opts[:directory])
26
+ validate_output_file(opts[:output], opts[:force])
25
27
 
26
- say "Found #{files.length} files to process", :green if options[:verbose]
28
+ files = find_files(opts[:directory], opts[:glob], opts[:exclude])
29
+ abort "No files found matching pattern '#{opts[:glob]}'" if files.empty?
30
+
31
+ say "Found #{files.length} files to process", :green if opts[:verbose]
27
32
 
28
33
  begin
29
- generate_lsif(files, options)
30
- say "\nLSIF data has been written to: #{options[:output]}", :green if options[:verbose]
31
- rescue => e
32
- abort "Error generating LSIF: #{e.message}"
34
+ generate_lsif(files, opts)
35
+ say "\nLSIF data has been written to: #{opts[:output]}", :green if opts[:verbose]
36
+ rescue StandardError => e
37
+ handle_error(e, opts[:verbose])
33
38
  end
34
39
  end
35
40
 
36
- desc 'scip', 'Generate SCIP index'
37
- method_option :directory, type: :string, aliases: '-d', default: '.', desc: 'Root directory to process'
38
- method_option :output, type: :string, aliases: '-o', default: 'index.scip', desc: 'Output file path'
39
- method_option :glob, type: :string, aliases: '-g', default: '**/*.rb', desc: 'File pattern to match'
40
- method_option :force, type: :boolean, aliases: '-f', desc: 'Overwrite output file if it exists'
41
- method_option :exclude, type: :array, aliases: '-e', desc: 'Patterns to exclude'
42
- def scip
43
- validate_directory(options[:directory])
44
- 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])
45
47
 
46
- files = find_files(options[:directory], options[:glob], options[:exclude])
47
- 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
48
51
 
49
- 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
50
57
 
51
- begin
52
- generate_scip(files, options)
53
- say "\nSCIP data has been written to: #{options[:output]}", :green if options[:verbose]
54
- rescue => e
55
- abort "Error generating SCIP: #{e.message}"
58
+ exit(1) if !results[:valid] || (options[:strict] && results[:warnings].any?)
59
+ rescue StandardError => e
60
+ handle_error(e, options[:verbose])
56
61
  end
57
62
  end
58
63
 
@@ -61,14 +66,81 @@ module Hind
61
66
  say "Hind version #{Hind::VERSION}"
62
67
  end
63
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
+
64
81
  private
65
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
+
66
136
  def validate_directory(directory)
67
137
  abort "Error: Directory '#{directory}' does not exist" unless Dir.exist?(directory)
68
138
  end
69
139
 
70
140
  def validate_output_file(output, force)
71
- abort "Error: Output file '#{output}' already exists. Use --force to overwrite." if File.exist?(output) && !force
141
+ if File.exist?(output) && !force
142
+ abort "Error: Output file '#{output}' already exists. Use --force to overwrite."
143
+ end
72
144
 
73
145
  # Ensure output directory exists
74
146
  FileUtils.mkdir_p(File.dirname(output))
@@ -78,60 +150,109 @@ module Hind
78
150
  pattern = File.join(directory, glob)
79
151
  files = Dir.glob(pattern)
80
152
 
81
- exclude_patterns&.each do |exclude|
82
- files.reject! { |f| File.fnmatch?(exclude, f) }
153
+ if exclude_patterns
154
+ exclude_patterns.each do |exclude|
155
+ files.reject! { |f| File.fnmatch?(exclude, f) }
156
+ end
83
157
  end
84
158
 
85
159
  files
86
160
  end
87
161
 
88
- def generate_lsif(files, options)
89
- global_state = Hind::LSIF::GlobalState.new
90
- vertex_id = 1
91
- initial = true
162
+ def load_config(config_path)
163
+ return {} unless config_path && File.exist?(config_path)
92
164
 
93
- File.open(options[:output], 'w') do |output_file|
94
- files.each do |file|
95
- say "Processing file: #{file}", :cyan if options[:verbose]
96
-
97
- relative_path = Pathname.new(file).relative_path_from(Pathname.new(options[:directory])).to_s
98
-
99
- begin
100
- generator = Hind::LSIF::Generator.new(
101
- {
102
- uri: relative_path,
103
- vertex_id: vertex_id,
104
- initial: initial,
105
- projectRoot: options[:directory]
106
- },
107
- global_state
108
- )
109
-
110
- output = generator.generate(File.read(file))
111
- vertex_id = output.last[:id].to_i + 1
112
- output_file.puts(output.map(&:to_json).join("\n"))
113
- initial = false
114
- rescue => e
115
- warn "Warning: Failed to process file '#{file}': #{e.message}"
116
- next
117
- end
118
- end
165
+ begin
166
+ YAML.load_file(config_path) || {}
167
+ rescue StandardError => e
168
+ abort "Error loading config file: #{e.message}"
119
169
  end
120
170
  end
121
171
 
122
- def generate_scip(files, options)
123
- raise NotImplementedError, 'SCIP generation not yet implemented'
124
- # Similar to generate_lsif but using SCIP generator
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])
125
193
  end
126
194
 
127
- def load_config(config_path)
128
- return {} unless config_path && File.exist?(config_path)
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
129
200
 
130
- begin
131
- YAML.load_file(config_path) || {}
132
- rescue => e
133
- abort "Error loading config file: #{e.message}"
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
207
+ end
208
+ puts
209
+ end
210
+
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
219
+ end
220
+
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
228
+
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}"
134
245
  end
135
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
136
257
  end
137
258
  end