howl.Project
before_each ->
Project.roots = {}
after_each ->
Project.roots = {}
Project.open = {}
.roots contains all known roots
assert.same {}, Project.roots
with_tmpdir (dir) ->
Project.add_root dir
assert.same {dir}, Project.roots
.add_root adds the given root if not already present
with_tmpdir (dir) ->
Project.add_root dir
Project.add_root dir
assert.equal 1, #Project.roots
.remove_root removes the given root
with_tmpdir (dir) ->
Project.add_root dir
Project.remove_root dir
assert.equal 0, #Project.roots
.for_file(file)
raises an error if file is nil
assert.raises 'file', -> Project.for_file nil
returns nil by default
File.with_tmpfile (file) ->
assert.is_nil Project.for_file file
(when there is VC found for the file)
vc = name: 'p-vc', root: 'foo_root', paths: -> {}, files: -> {}
before_each -> VC.register 'pvc', find: -> vc
after_each -> VC.unregister 'pvc'
returns a project instantiated with the vc and vc root
p = Project.for_file 'file'
assert.not_nil p
assert.equal vc.root, p.root
assert.equal 'p-vc', p.vc.name
adds the new root to .roots
Project.for_file 'file'
assert.same Project.roots, {vc.root}
adds a new entry for the root and project to .open
p = Project.for_file 'file'
assert.same Project.open, { [vc.root]: p }
(when there is a known root containing the file)
returns a new project for the root
with_tmpdir (dir) ->
Project.add_root dir
file = dir / 'test.moon'
p = Project.for_file file
assert.not_nil p
assert.equal p.root, dir
automatically sets the matching VC if possible
with_tmpdir (dir) ->
Project.add_root dir
file = dir / 'test.moon'
vc = name: 'p2vc', root: dir, paths: -> {}
VC.register 'p2vc', find: (f) -> return vc if f == file
p = Project.for_file file
VC.unregister 'p2vc'
assert.equal 'p2vc', p.vc.name
(when there is an open project containing the file)
returns the existing project
with_tmpdir (dir) ->
Project.add_root dir
file = dir / 'test.moon'
file2 = dir / 'test2.moon'
p = Project.for_file file
p2 = Project.for_file file2
assert.not_nil p
assert.equal p2, p
for a given project instance
paths()
delegates to .vc.paths() if it is available
vc = paths: -> {'path'}
assert.same vc.paths!, Project('root', vc)\paths!
falls back to a FS scan, skipping directories, backup files and hidden exts
orig_exts = config.hidden_file_extensions
config.hidden_file_extensions = {'foo'}
with_tmpdir (dir) ->
regular = dir / 'regular.lua'
regular\touch!
sub_dir = dir / 'sub_dir'
sub_dir\mkdir!
hidden = dir / '.config'
hidden\touch!
backup = dir / 'config~'
backup\touch!
hidden_ext = dir / 'bar.foo'
hidden_ext\touch!
paths = Project(dir)\paths!
config.hidden_file_extensions = orig_exts
table.sort paths
assert.same { '.config', 'regular.lua' }, paths
files()
delegates to .vc.files() if it is available
vc = files: -> 'files'
assert.equal vc.files!, Project('root', vc)\files!
falls back to a FS scan, skipping directories and backup files
with_tmpdir (dir) ->
regular = dir / 'regular.lua'
regular\touch!
sub_dir = dir / 'sub_dir'
sub_dir\mkdir!
hidden = dir / '.config'
hidden\touch!
backup = dir / 'config~'
backup\touch!
assert.same { regular.path, hidden.path }, [f.path for f in *Project(dir)\files!]
environment
local dir, project
provide = (name, handler, files) ->
Project.register_environment_provider :name, :handler, :files
before_each ->
dir = File.tmpdir!
project = Project dir
after_each ->
Project.unregister_environment_provider 'test-a'
Project.unregister_environment_provider 'test-b'
dir\delete_all!
find_environment()
combines the providers' variables in order, giving each the project and those before it
local args
provide 'test-a', -> { A: '1', B: '1' }
provide 'test-b', (p, vars) ->
args = { p, moon.copy(vars) }
{ B: '2' }
assert.same { A: '1', B: '2' }, project\find_environment!
assert.equals project, args[1]
assert.same { A: '1', B: '1' }, args[2]
skips providers that fail, logging the error
provide 'test-a', -> error 'no environment here'
provide 'test-b', -> { B: '2' }
assert.same { B: '2' }, project\find_environment!
assert.includes log.last_error.message, 'no environment here'
adds the variables of project_env_file last, expanding references to earlier ones
provide 'test-a', -> { A: '1', B: '1' }
(dir / '.env').contents = 'B=2\nC=${A}${HOME}\n'
assert.same { A: '1', B: '2', C: "1#{sys.env.HOME}" }, project\find_environment!
uses the file named by project_env_file, and none when it is blank
(dir / '.env').contents = 'A=1'
(dir / 'dev.env').contents = 'B=2'
project.config.project_env_file = 'dev.env'
assert.same { B: '2' }, project\find_environment!
project = Project dir
project.config.project_env_file = ''
assert.same {}, project\find_environment!
keeps the variables in .environment, running the providers once
handler = spy.new -> { A: '1' }
provide 'test-a', handler
vars = project\find_environment!
assert.equals vars, project.environment
assert.equals vars, project\find_environment!
assert.spy(handler).was_called(1)
makes callers wait while the variables are being found
handle = dispatch.park 'test-provider'
provide 'test-a', ->
dispatch.wait handle
{ A: '1' }
local first, second
dispatch.launch -> first = project\find_environment!
dispatch.launch -> second = project\find_environment!
assert.is_nil first
dispatch.resume handle
assert.same { A: '1' }, first
assert.equals first, second
process_env()
returns nil when there are no variables
assert.is_nil project\process_env!
returns Howl's environment with the variables on top
provide 'test-a', -> { HOME: '/elsewhere', HOWL_PROJECT_VAR: '1' }
env = project\process_env!
assert.equals '/elsewhere', env.HOME
assert.equals '1', env.HOWL_PROJECT_VAR
assert.equals sys.env.PATH, env.PATH
(when a file is saved)
local value, buffer
save = (name) ->
buffer.file = dir / name
buffer\save!
before_each ->
value = '1'
provide 'test-a', (-> { A: value }), { 'deps.txt' }
Project.open[dir] = project
buffer = Buffer {}
finds the environment again when it depends on the file, signalling any change
project\find_environment!
with_signal_handler 'project-environment-changed', nil, (handler) ->
save 'deps.txt'
assert.spy(handler).was_not_called!
value = '2'
save 'deps.txt'
assert.spy(handler).was_called_with { :project }
(dir / '.env').contents = 'B=3'
save '.env'
assert.spy(handler).was_called(2)
assert.same { A: '2', B: '3' }, project.environment
leaves it alone for other files, or when it is not yet found
save 'deps.txt'
assert.is_nil project.environment
vars = project\find_environment!
value = '2'
save 'other.txt'
(dir / 'sub')\mkdir!
save 'sub/deps.txt'
assert.equals vars, project.environment