Class: Lemans::Stores::FS

Inherits:
Lemans::Store show all
Defined in:
lib/lemans/stores/fs.rb

Overview

A file-system store (default)

Constant Summary collapse

FILENAME =
"result.json"
RESULT_ID =
/\A(?<task>.+)__[A-Za-z0-9]{7}\z/

Instance Method Summary collapse

Constructor Details

#initialize(root, filterer: nil) ⇒ FS

Returns a new instance of FS.



16
17
18
19
20
# File 'lib/lemans/stores/fs.rb', line 16

def initialize(root, filterer: nil)
  super()
  @root = Pathname(root)
  @filterer = filterer
end

Instance Method Details

#delete(result) ⇒ Object



53
54
55
56
57
58
59
60
61
62
63
# File 'lib/lemans/stores/fs.rb', line 53

def delete(result)
  dir = root.glob("**/#{result.id}").find(&:directory?)
  return unless dir

  FileUtils.remove_entry(dir.to_s)
  prune_empty_parents(dir.parent)
  dir
rescue SystemCallError => e
  warn "lemans: could not delete #{result.id}: #{e.message}"
  nil
end

#fetchObject



28
29
30
# File 'lib/lemans/stores/fs.rb', line 28

def fetch
  root.glob("**/#{FILENAME}").filter_map { to_record(it) }
end

#query(task: nil, agent: nil, model: nil, tags: nil) ⇒ Object

The file system keeps no index, so filtering happens in memory.



33
34
35
36
37
38
39
40
# File 'lib/lemans/stores/fs.rb', line 33

def query(task: nil, agent: nil, model: nil, tags: nil)
  results = fetch
  results.select! { Array(task).include?(it.task) } if task
  results.select! { it.agent == agent } if agent
  results.select! { it.model == model } if model
  results.select! { Array(tags).intersect?(it.tags) } if tags
  results
end

#save(result) ⇒ Object



65
66
67
68
69
70
# File 'lib/lemans/stores/fs.rb', line 65

def save(result)
  atomic_write(result_dir(result).join(FILENAME), filtered("#{JSON.pretty_generate(result.as_json)}\n"))
rescue SystemCallError, JSON::GeneratorError => e
  # runs_dir unwritable, disk full
  raise ConfigError, "cannot record trial #{result.id}: #{e.message}"
end

#save_artifact(result, contents, path:) ⇒ Object



72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/lemans/stores/fs.rb', line 72

def save_artifact(result, contents, path:)
  destination = result_dir(result).join(path)
  if destination.exist?
    warn "lemans: artifact #{path} collides with an existing file and was dropped"
    return
  end

  destination.dirname.mkpath
  contents.is_a?(String) ? destination.write(filtered(contents)) : copy_filtered(contents, destination)
  destination
rescue SystemCallError => e
  warn "lemans: could not save artifact #{path} for #{result.id}: #{e.message}"
  nil
end

#setupObject



22
23
24
25
26
# File 'lib/lemans/stores/fs.rb', line 22

def setup
  root.mkpath
rescue SystemCallError => e
  raise ConfigError, "cannot use runs directory #{root}: #{e.message}"
end

#unreadableObject



44
45
46
47
48
49
50
51
# File 'lib/lemans/stores/fs.rb', line 44

def unreadable
  root.glob("**/#{FILENAME}").filter_map do |path|
    next if to_record(path)

    id = path.dirname.basename.to_s
    Result.new(task: RESULT_ID.match(id)&.[](:task), agent: nil, model: nil, id:)
  end
end