Class: Hecks::Adapters::Folder

Inherits:
Object
  • Object
show all
Defined in:
lib/hecks/adapters/driven/folder.rb

Constant Summary collapse

DOMAIN_ORDER =
Hecks::Vocabulary.fetch("LoadOrder")
PORTS =
"ports"
ADAPTERS =
"adapters"

Instance Method Summary collapse

Constructor Details

#initialize(settings: {}, root: nil) ⇒ Folder

Returns a new instance of Folder.



10
11
12
13
# File 'lib/hecks/adapters/driven/folder.rb', line 10

def initialize(settings: {}, root: nil)
  @settings = settings
  @root     = root
end

Instance Method Details

#bluebook_directory(path) ⇒ Object

Raises:

  • (Errno::ENOENT)


124
125
126
127
128
129
130
131
132
# File 'lib/hecks/adapters/driven/folder.rb', line 124

def bluebook_directory(path)
  expanded = File.expand_path(path)
  nested   = File.join(expanded, "bluebook")

  return nested   if File.directory?(nested)
  return expanded if File.directory?(expanded)

  raise Errno::ENOENT, "no such domain directory: #{path}"
end

#domain?(directory) ⇒ Boolean

Returns:

  • (Boolean)


174
175
176
177
# File 'lib/hecks/adapters/driven/folder.rb', line 174

def domain?(directory)
  !Dir[File.join(directory, "*.hecksagon")].empty? ||
    !Dir[File.join(directory, "bluebook", "*.hecksagon")].empty?
end

#domain_root(from = Dir.pwd) ⇒ Object

THE DOMAIN YOU ARE STANDING IN. Walks up from from — the way git finds .git — and answers the nearest directory a boot would accept, or nil if there is not one above you.

MARKED BY A .hecksagon, NOT BY A .bluebook. Chapters are everywhere: era translations, the language's own self-hosted grammar, and spec/fixtures, which holds a dozen unrelated ones in a single directory. A .hecksagon is the file that says "this is a domain, and here is how its aggregates are stored", which is exactly the claim a caller is relying on when they omit the path.

Both layouts, because bluebook_directory above accepts both: a domain directory holding a bluebook/ subdirectory (every example in this corpus), or one holding the files directly. NORMALISED TO THE OUTER DIRECTORY. Standing in examples/banking/bluebook, the .hecksagon is right there, so a plain walk stops on the bluebook/ directory itself. Both boot identically — bluebook_directory accepts either and Loader.boot takes File.dirname of what it gets, so the registry root comes out the same — but examples/banking is the directory a person names, and the one a .world's dir "data" reads as relative to.



155
156
157
158
159
160
161
# File 'lib/hecks/adapters/driven/folder.rb', line 155

def domain_root(from = Dir.pwd)
  found = nearest_domain(File.expand_path(from))
  return nil unless found

  parent = File.dirname(found)
  File.basename(found) == "bluebook" && domain?(parent) ? parent : found
end

#library(folder) ⇒ Object



194
195
196
# File 'lib/hecks/adapters/driven/folder.rb', line 194

def library(folder)
  File.expand_path("../../#{folder}", __dir__)
end

#load_bluebooks(directory, patterns = ["*.bluebook"]) ⇒ Object

EVERY BLUEBOOK IN A FOLDER IS ONE DECLARATION SET. Individual files remain organized in the domain expert's language; the folder is the unit callers load. Builders group declarations by the chapter name in each file, so a folder may hold more than one chapter without a catalog. Sorting makes source order deterministic while the deferred window keeps cross-file references from being judged against a partial chapter.



75
76
77
78
# File 'lib/hecks/adapters/driven/folder.rb', line 75

def load_bluebooks(directory, patterns = ["*.bluebook"])
  Bluebook::MetaValidator.defer { load_each(directory, patterns) }
  Bluebook::MetaValidator.judge_deferred!(Hecks.current_registry)
end

#load_domain(directory, environment: nil) ⇒ Object

CHAPTERS FIRST, JUDGED ONCE, THEN EVERYTHING THAT READS THEM. A chapter may be split across files (the language's own grammar is nine), and judging file one before files two-through-nine exist refuses references that are perfectly well declared a file later — see MetaValidator.defer for the whole reasoning. DOMAIN_ORDER already places every *.bluebook ahead of hecksagons and worlds, so the window ends at the last chapter pattern rather than at a hand-written list this would otherwise have to keep in step. environment: — ONE MORE PAIR OF FILES, LOADED LAST, NOT A GLOB. RECOVERED, not new — see Runtime::Loader.boot's own comment for the provenance. A caller passing Hecks.boot(path, environment: "production") gets exactly environments/production.hecksagon and environments/production.world loaded, whichever exist (a missing one is a silent no-op — not every environment overrides both; see docs/implemented/guides/wiring.md's "Swapping wiring per environment" for the motivating case: swapping a driven adapter — e.g. a real payment gateway for a mock one, or a hosting layer's tenancy settings — without an if/else anywhere in the domain's own wiring). Never matched by DOMAIN_ORDER's own globs (all non-recursive, none named environments/*), so this is the only thing that ever reaches them. Loaded as genuine Hecks.hecksagon "SameDomain" do ... end / Hecks.world "SameDomain" do ... end blocks — MERGED into the base file's own hecksagon/world (Registry#add_hecksagon / #add_world, concatenate/override rather than replace), so an overlay can rebind or add settings for anything the base file declared without needing to know what else the base file said.



54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/hecks/adapters/driven/folder.rb', line 54

def load_domain(directory, environment: nil)
  boundary = DOMAIN_ORDER.rindex { |pattern| pattern.end_with?(".bluebook") }
  if boundary
    load_bluebooks(directory, DOMAIN_ORDER[0..boundary])
    load_each(directory, DOMAIN_ORDER[(boundary + 1)..])
  else
    load_each(directory, DOMAIN_ORDER)
  end

  return unless environment

  load_each(directory, [File.join("environments", "#{environment}.hecksagon")])
  load_each(directory, [File.join("environments", "#{environment}.world")])
end

#load_each(directory, patterns) ⇒ Object



116
117
118
119
120
121
122
# File 'lib/hecks/adapters/driven/folder.rb', line 116

def load_each(directory, patterns)
  return unless File.directory?(directory)

  patterns.each do |pattern|
    Dir[File.join(directory, pattern)].sort.each { |file| Kernel.load(file) }
  end
end

#load_libraryObject



15
16
17
18
# File 'lib/hecks/adapters/driven/folder.rb', line 15

def load_library
  load_each(library(PORTS),    %w[*.port])
  load_each(library(ADAPTERS), %w[*/*.adapter */*/*.adapter])
end

#load_project(root) ⇒ Object



20
21
22
23
24
25
# File 'lib/hecks/adapters/driven/folder.rb', line 20

def load_project(root)
  return unless root

  load_each(File.join(root, PORTS),    %w[*.port */*.port])
  load_each(File.join(root, ADAPTERS), %w[*.adapter */*.adapter */*/*.adapter])
end

#load_selected(files, environment: nil) ⇒ Object

THE EXPLICIT-FILE SIBLING OF load_domain — for a caller that names its own exact files rather than a directory to glob (Loader.boot_files, behind Hecks.boot_files). No Dir.glob, no copying: every path here is a real file on disk, wherever it actually lives, loaded in place — a .behaviors file's loads scopes a boot this way specifically so a per-test boot never has to fake isolation by staging bluebooks into a tmpdir (see Loader.boot_files's own header for why that pattern is a hazard, not a convenience).

ORDERED BY CATEGORY, NOT BY THE CALLER'S OWN LIST ORDER — same four groups Vocabulary.fetch("LoadOrder") walks a directory in (bluebook chapters, translations, hecksagons, worlds), because a hecksagon can reference a bluebook's own constants and must not load first regardless of which order a caller happened to write loads "x.hecksagon", "x.bluebook" in. Bluebook chapters are judged as one deferred group exactly like load_domain does, for the identical forward-reference reason (MetaValidator.defer's own header).



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/hecks/adapters/driven/folder.rb', line 97

def load_selected(files, environment: nil)
  bluebooks, rest = files.partition { |f| f.end_with?(".bluebook") }

  if bluebooks.any?
    Bluebook::MetaValidator.defer { bluebooks.sort.each { |f| Kernel.load(f) } }
    Bluebook::MetaValidator.judge_deferred!(Hecks.current_registry)
  end

  %w[.hecksagon .world].each do |ext|
    rest.select { |f| f.end_with?(ext) }.sort.each { |f| Kernel.load(f) }
  end

  return unless environment

  directory = File.dirname(files.first)
  load_each(directory, [File.join("environments", "#{environment}.hecksagon")])
  load_each(directory, [File.join("environments", "#{environment}.world")])
end

#nearest_domain(current) ⇒ Object



163
164
165
166
167
168
169
170
171
172
# File 'lib/hecks/adapters/driven/folder.rb', line 163

def nearest_domain(current)
  loop do
    return current if domain?(current)

    parent = File.dirname(current)
    return nil if parent == current

    current = parent
  end
end

#shared_root(given, directory) ⇒ Object



179
180
181
182
183
184
185
186
187
188
189
190
191
192
# File 'lib/hecks/adapters/driven/folder.rb', line 179

def shared_root(given, directory)
  return File.expand_path(given) if given

  current = directory
  loop do
    return current if File.directory?(File.join(current, PORTS)) ||
                      File.directory?(File.join(current, ADAPTERS))

    parent = File.dirname(current)
    return nil if parent == current

    current = parent
  end
end