Class: Jade::Project

Inherits:
Object
  • Object
show all
Defined in:
lib/jade/project.rb

Overview

A project's compiler settings, read from jade.json at its root.

Without this, project config only exists as Ruby executed at app boot (Jade.setup), so every tool that runs outside the app — the CLI, an editor's LSP, a codegen task — is blind to it and has to guess the source root and which extension gems to load.

Subclassed rather than Project = Data.define(...) do ... end because constants in that block land in Jade, not on the class — which would put Jade::DEFAULTS in the gem's top namespace.

Defined Under Namespace

Classes: NotFound

Constant Summary collapse

MANIFEST =
'jade.json'.freeze
DEFAULTS =
{
  source_roots: ['lib'],
  build_dir: '.jade/build',
  cache_dir: '.jade/cache',
  extensions: [],
  entries: [],
  map: {},
}.freeze

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.find(from = Dir.pwd) ⇒ Object

nil when there's no manifest — a project predating one is a legitimate state, not a failure. Callers that need it say so with find!.



45
46
47
48
49
50
51
52
# File 'lib/jade/project.rb', line 45

def self.find(from = Dir.pwd)
  Pathname
    .new(from)
    .expand_path
    .ascend
    .find { (it + MANIFEST).file? }
    &.then { load(it) }
end

.find!(from = Dir.pwd) ⇒ Object



54
55
56
# File 'lib/jade/project.rb', line 54

def self.find!(from = Dir.pwd)
  find(from) || fail(NotFound.new(from))
end

.load(root) ⇒ Object



58
59
60
61
62
63
64
# File 'lib/jade/project.rb', line 58

def self.load(root)
  (root + MANIFEST)
    .read
    .then { JSON.parse(it, symbolize_names: true) }
    .then { new(root: root.to_s, **DEFAULTS.merge(it)) }
    .tap { it.require_extensions }
end

Instance Method Details

#build_pathObject



70
71
72
# File 'lib/jade/project.rb', line 70

def build_path
  File.expand_path(build_dir, root)
end

#cache_pathObject



74
75
76
# File 'lib/jade/project.rb', line 74

def cache_path
  File.expand_path(cache_dir, root)
end

#require_extensionsObject

Extensions register their search root when required, so this is what lets a standalone tool resolve Sql.Uuid to the gem that ships it.

Plain require, not Kernel.require: RubyGems overrides the private instance method and leaves the module function alone, so Kernel.require finds only what is already on the load path — never an installed gem, which is the whole point here.



96
97
98
# File 'lib/jade/project.rb', line 96

def require_extensions
  extensions.each { require(it) }
end

#source_relative(path) ⇒ Object

Modules are addressed relative to the source root, but a path typed at a shell or sent by an editor is relative to the project root. Accept either, and say so when it's neither.



81
82
83
84
85
86
87
# File 'lib/jade/project.rb', line 81

def source_relative(path)
  [source_root, root]
    .map { File.expand_path(path, it) }
    .find { File.file?(it) }
    .then { it || fail("no such file: #{path}") }
    .then { Pathname.new(it).relative_path_from(source_root).to_s }
end

#source_rootObject



66
67
68
# File 'lib/jade/project.rb', line 66

def source_root
  File.expand_path(source_roots.first, root)
end