Class: Everywhere::Ignore

Inherits:
Object
  • Object
show all
Defined in:
lib/everywhere/ignore.rb

Overview

Shared source-exclusion rules: the built-in DEFAULTS, plus .everywhereignore (dockerignore style) appended when the app has one. ONE source of truth so the build receipt's source checksum (Receipt) and the Platform snapshot (every platform build, later) describe exactly the same set of files — a checksum that didn't match what actually ships would be worse than none.

Pattern semantics are a pragmatic subset of .dockerignore/.gitignore:

dist            → a path segment named "dist", anywhere
vendor/bundle   → that exact path (and everything under it)
*.log           → glob against the full path and the basename
!keep/this.log  → NEGATION: re-include a file an earlier pattern excluded

Rules are evaluated top to bottom and the LAST one that matches a path wins (dockerignore semantics), so ordering matters: put ! re-includes after the broad exclude they carve out of — and the app's file, coming last, can re-include anything DEFAULTS excluded.

Constant Summary collapse

FILE =
".everywhereignore"
FLAGS =
File::FNM_PATHNAME | File::FNM_DOTMATCH
DEFAULTS =

Build products, caches, VCS, local state, and secrets. Excluding .env* and the key/cert family keeps signing material out of both the checksum and any future snapshot.

%w[
  dist tmp log node_modules .git storage .bundle vendor/bundle
  coverage public/assets public/packs .DS_Store .env* *.log
  *.key *.pem *.crt *.p12 *.mobileprovision
].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(rules) ⇒ Ignore

Returns a new instance of Ignore.



61
62
63
# File 'lib/everywhere/ignore.rb', line 61

def initialize(rules)
  @rules = rules
end

Instance Attribute Details

#rulesObject (readonly)

Returns the value of attribute rules.



59
60
61
# File 'lib/everywhere/ignore.rb', line 59

def rules
  @rules
end

Class Method Details

.for(root) ⇒ Object

The app's file is layered ON TOP of DEFAULTS rather than replacing them: a project that adds one line to skip a fixture dir must not thereby start shipping .env and *.p12. Last match wins, so a user who really wants a defaulted-out file back can still ! it in.



37
38
39
40
41
42
43
44
# File 'lib/everywhere/ignore.rb', line 37

def self.for(root)
  path = File.join(root, FILE)
  return new(parse(DEFAULTS.join("\n"))) unless File.exist?(path)

  # UTF-8 explicitly (and scrubbed): under LANG=C the bytes come back
  # US-ASCII and a non-ASCII path pattern makes the matching raise.
  new(parse(DEFAULTS.join("\n")) + parse(File.read(path, encoding: "UTF-8").scrub))
end

.parse(text) ⇒ Object

→ [{ negated:, pattern: }, ...] in file order. Blank lines and whole-line # comments are dropped; a leading ! marks a re-include.



48
49
50
51
52
53
54
55
56
57
# File 'lib/everywhere/ignore.rb', line 48

def self.parse(text)
  text.lines.filter_map do |raw|
    line = raw.chomp
    next if line.strip.empty? || line.lstrip.start_with?("#")

    negated = line.start_with?("!")
    pattern = (negated ? line[1..] : line).strip.sub(%r{\A/}, "").chomp("/")
    { negated: negated, pattern: pattern } unless pattern.empty?
  end
end

Instance Method Details

#files(root) ⇒ Object

Every non-ignored regular file under root, as sorted root-relative paths.



66
67
68
69
70
71
# File 'lib/everywhere/ignore.rb', line 66

def files(root)
  Dir.glob("**/*", File::FNM_DOTMATCH, base: root)
    .reject { |rel| ignored?(rel) }
    .select { |rel| File.file?(File.join(root, rel)) }
    .sort
end

#ignored?(rel) ⇒ Boolean

Last matching rule wins; a negated match re-includes. Default: kept.

Returns:

  • (Boolean)


74
75
76
77
78
79
80
81
# File 'lib/everywhere/ignore.rb', line 74

def ignored?(rel)
  base = rel.split("/").last
  return true if [".", ".."].include?(base)

  @rules.reduce(false) do |ignored, rule|
    match?(rule[:pattern], rel, base) ? !rule[:negated] : ignored
  end
end