Class: Coat::Collector

Inherits:
Object
  • Object
show all
Defined in:
lib/coat/collector.rb

Overview

Turns work into patches. Provenance is recorded at the moment the scrap is made - what ran, where, how it exited, and what it touched - because none of that can be reconstructed honestly after the fact.

Defined Under Namespace

Classes: NoSuchFile

Constant Summary collapse

COMMAND_NOT_RUN =
127

Instance Method Summary collapse

Constructor Details

#initialize(ledger, patches_dir) ⇒ Collector

Returns a new instance of Collector.



19
20
21
22
23
24
# File 'lib/coat/collector.rb', line 19

def initialize(ledger, patches_dir)
  @ledger = ledger
  @patches_dir = patches_dir
  @state_dir = File.basename(File.dirname(patches_dir))
  @pins_dir = File.join(File.dirname(patches_dir), "pins")
end

Instance Method Details

#add_captured(body:, label:, agent:, cmd: nil, exit_code: nil, cwd: Dir.pwd, claims: nil) ⇒ Object

Output some other harness already produced. coat did not run this, so it knows only what it was handed - which is still capture rather than guess, as long as whoever handed it over was there when it ran. Recorded as its own kind so the ledger never claims coat watched it happen.

Nothing is claimed by default: the body arrived as text, so there is no honest way to know what it touched. Pass claims, or pin first.



55
56
57
58
59
60
# File 'lib/coat/collector.rb', line 55

def add_captured(body:, label:, agent:, cmd: nil, exit_code: nil, cwd: Dir.pwd, claims: nil)
  record(
    origin: origin(label, agent, "captured").merge("cmd" => cmd, "cwd" => cwd, "exit" => exit_code),
    body: store(label, body), started: Time.now, claims: claims || []
  )
end

#add_command(cmd:, label:, agent:, cwd: Dir.pwd, claims: nil) ⇒ Object



26
27
28
29
30
31
32
33
34
35
36
# File 'lib/coat/collector.rb', line 26

def add_command(cmd:, label:, agent:, cwd: Dir.pwd, claims: nil)
  before = working_tree(cwd)
  started = Time.now
  output, code = run(cmd, cwd)

  record(
    origin: origin(label, agent, "command").merge("cmd" => cmd, "cwd" => cwd, "exit" => code),
    body: store(label, output), started: started,
    claims: claims || touched(before, working_tree(cwd))
  )
end

#add_file(path:, label:, agent:, claims: nil) ⇒ Object

Raises:



38
39
40
41
42
43
44
45
46
# File 'lib/coat/collector.rb', line 38

def add_file(path:, label:, agent:, claims: nil)
  raise NoSuchFile, "no such file: #{path}" unless File.file?(path)

  record(
    origin: origin(label, agent, "file").merge("cwd" => File.dirname(File.expand_path(path))),
    body: store(label, File.read(path)), started: Time.now,
    claims: claims || [relative(path)]
  )
end

#pin(key, cwd: Dir.pwd) ⇒ Object

Marks where the working tree stands, so a patch added later can be asked what moved in between. This is how ambient capture keeps honest claims: something has to look before the work, and after the fact is too late.



65
66
67
68
69
70
71
72
# File 'lib/coat/collector.rb', line 65

def pin(key, cwd: Dir.pwd)
  tree = working_tree(cwd) or return nil

  path = pin_path(key)
  FileUtils.mkdir_p(@pins_dir)
  File.write(path, JSON.generate(tree))
  path
end

#since(key, cwd: Dir.pwd) ⇒ Object

What changed since that pin, and then the pin is spent - it described one boundary and that boundary has now been crossed.

ponytail: a pin nobody comes back for is left behind in .coat/pins. Sweep them if a long-lived checkout ever collects enough to notice.



79
80
81
82
83
84
85
86
# File 'lib/coat/collector.rb', line 79

def since(key, cwd: Dir.pwd)
  path = pin_path(key)
  return nil unless File.file?(path)

  before = JSON.parse(File.read(path))
  File.delete(path)
  touched(before, working_tree(cwd))
end