Class: Compose::FileProcessor

Inherits:
Object
  • Object
show all
Defined in:
lib/compose/file_processor.rb

Constant Summary collapse

CACHE_DIR =
File.join(ENV['HOME'], '.cache', 'compose')

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(input_files, include_imports: true) ⇒ FileProcessor

Returns a new instance of FileProcessor.



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/compose/file_processor.rb', line 30

def initialize(input_files, include_imports: true)
  @files = {}

  input_files.each do |input|
    if input.start_with?('http://', 'https://')
      @files[input] = process_url(input)
    elsif File.directory?(input)
      find_files_in_directory(input).each do |file|
        @files[File.expand_path(file)] = nil
      end
    elsif File.file?(input)
      @files[File.expand_path(input)] = nil
    else
      puts "Skipping invalid input: #{input}".yellow
    end
  end

  @files.each do |path, _|
    @files[path] ||= FileProcessor.load_file(path, include_imports: include_imports)
  end

  self
end

Instance Attribute Details

#ai_clientObject (readonly)

Returns the value of attribute ai_client.



15
16
17
# File 'lib/compose/file_processor.rb', line 15

def ai_client
  @ai_client
end

#filesObject (readonly)

Returns the value of attribute files.



15
16
17
# File 'lib/compose/file_processor.rb', line 15

def files
  @files
end

#tokensObject (readonly)

Returns the value of attribute tokens.



15
16
17
# File 'lib/compose/file_processor.rb', line 15

def tokens
  @tokens
end

Class Method Details

.load_file(file_path, include_imports: true) ⇒ Object



17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/compose/file_processor.rb', line 17

def self.load_file(file_path, include_imports: true)
  content = File.read(file_path)
  processed_content = content.split("\n").map.with_index { |line, index| "L#{index + 1}: #{line}" }.join("\n")

  unless include_imports
    processed_content.gsub!(/L\d+:\s*require.*?\n/, '')
    processed_content.gsub!(/L\d+:\s*import.*?\n/, '')
  end

  relative_path = Pathname.new(file_path).relative_path_from(Pathname.new(Dir.pwd)).to_s
  "<#{relative_path}>\n#{processed_content}\n</#{relative_path}>"
end

Instance Method Details

#ask_files(question) ⇒ Object



58
59
60
61
62
63
64
# File 'lib/compose/file_processor.rb', line 58

def ask_files(question)
  prompt = File.read(File.expand_path("../../config/prompts/ask.txt", __dir__))
  prompt.gsub!('{{QUESTION}}', question)
  system_prompt = "CODE:\n#{content}\n"

  AIClient.new(Compose::Config.code_model).chat(system_prompt, prompt)
end

#contentObject



54
55
56
# File 'lib/compose/file_processor.rb', line 54

def content
  @files.values.join("\n\n")
end

#edit_files(task) ⇒ Object



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/compose/file_processor.rb', line 66

def edit_files(task)
  prompt = File.read(File.expand_path("../../config/prompts/task.txt", __dir__))
  prompt.gsub!('{{TASK}}', task)
  system_prompt = "CODE:\n#{content}\n"

  response = AIClient.new(Compose::Config.code_model).chat(system_prompt, prompt)

  begin
    edits = JSON.parse(response)['edits']
    edits.map do |edit|
      edit = edit.transform_keys(&:to_sym)
      edit[:change] = edit[:change].transform_keys(&:to_sym)
      {
        type: 'edit',
        edit: edit
      }
    end
  rescue JSON::ParserError => e
    [{ type: 'error', error: "Failed to parse AI response: #{e.message}" }]
  end
end