Class: Fontico::Lockfile

Inherits:
Object
  • Object
show all
Defined in:
lib/fontico/lockfile.rb

Overview

icons.lock pins two things that must never drift:

codepoints — append-only. Adding an icon must not renumber the others,
           or every glyph in the built font moves and the committed
           artifact churns whole-file on each addition. Codepoints of
           removed icons are retired, never reissued.

bodies     — the normalised SVG for each icon, so builds are
           reproducible and run offline. The Iconify API serves
           *latest*; without this an icon can change shape between
           two builds of the same manifest.

Constant Summary collapse

PUA_START =
0xE001
FORMAT =
1

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(path) ⇒ Lockfile

Returns a new instance of Lockfile.



24
25
26
27
28
29
30
# File 'lib/fontico/lockfile.rb', line 24

def initialize(path)
  @path = path
  data = File.exist?(path) ? (YAML.safe_load_file(path) || {}) : {}
  @codepoints = data["codepoints"] || {}
  @retired    = data["retired"]    || {}
  @entries    = data["icons"]      || {}
end

Instance Attribute Details

#pathObject (readonly)

Returns the value of attribute path.



22
23
24
# File 'lib/fontico/lockfile.rb', line 22

def path
  @path
end

Instance Method Details

#body(name) ⇒ Object



62
# File 'lib/fontico/lockfile.rb', line 62

def body(name) = entry(name)&.fetch("body", nil)

#codepoint_for(name) ⇒ Object



32
33
34
# File 'lib/fontico/lockfile.rb', line 32

def codepoint_for(name)
  @codepoints[name] ||= next_free
end

#entry(name) ⇒ Object



36
# File 'lib/fontico/lockfile.rb', line 36

def entry(name) = @entries[name]

#fresh?(name, source) ⇒ Boolean

Returns:

  • (Boolean)


58
59
60
# File 'lib/fontico/lockfile.rb', line 58

def fresh?(name, source)
  entry(name)&.fetch("source", nil) == source && entry(name)["body"]
end

#multicolor?(name) ⇒ Boolean

Returns:

  • (Boolean)


63
# File 'lib/fontico/lockfile.rb', line 63

def multicolor?(name) = !!entry(name)&.fetch("multicolor", false)

#retire_missing!(names) ⇒ Object

Names present in the lock but absent from the manifest keep their codepoint reserved so it is never handed to a different icon.



51
52
53
54
55
56
# File 'lib/fontico/lockfile.rb', line 51

def retire_missing!(names)
  (@codepoints.keys - names).each do |gone|
    @retired[gone] = @codepoints.delete(gone)
    @entries.delete(gone)
  end
end

#save!Object



69
70
71
72
73
74
75
76
# File 'lib/fontico/lockfile.rb', line 69

def save!
  File.write(@path, {
    "format"     => FORMAT,
    "codepoints" => @codepoints.sort.to_h,
    "retired"    => @retired.sort.to_h,
    "icons"      => @entries.sort.to_h
  }.to_yaml)
end

#store(name, source:, body:, multicolor: false, warnings: []) ⇒ Object



38
39
40
41
42
43
44
45
46
47
# File 'lib/fontico/lockfile.rb', line 38

def store(name, source:, body:, multicolor: false, warnings: [])
  @entries[name] = {
    "source"     => source,
    "digest"     => Digest::SHA256.hexdigest(body)[0, 16],
    "multicolor" => multicolor,
    "warnings"   => warnings,
    "body"       => body
  }
  codepoint_for(name)
end

#warnings(name) ⇒ Object

Replayed on cached builds so a hard failure keeps being reported until the source file is actually fixed.



67
# File 'lib/fontico/lockfile.rb', line 67

def warnings(name) = entry(name)&.fetch("warnings", nil) || []