hind 0.1.5 → 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: 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