howl.io.File
.basename returns the basename of the path
assert.equal 'base.ext', File('/foo/base.ext').basename
.extension returns the extension of the path
assert.equal File('/foo/base.ext').extension, 'ext'
assert.equal File('/foo/base.ex+').extension, 'ex+'
.path returns the path of the file
assert.equal '/foo/base.ext', File('/foo/base.ext').path
.uri returns an URI representing the path
assert.equal File('/foo.txt').uri, 'file:///foo.txt'
.exists returns true if the path exists
File.with_tmpfile (file) -> assert.is_true file.exists
.parent return the parent of the file
assert.equal File('/bin/ls').parent.path, '/bin'
.children returns a table of children
with_tmpdir (dir) ->
dir\join('child1')\mkdir!
dir\join('child2')\touch!
kids = dir.children
table.sort kids, (a,b) -> a.path < b.path
assert.same [v.basename for v in *kids], { 'child1', 'child2' }
it '.children_async returns a table of children', (done) ->
howl_async ->
with_tmpdir (dir) ->
dir\join('child1')\mkdir!
dir\join('child2')\touch!
kids = dir.children_async
table.sort kids, (a,b) -> a.path < b.path
assert.same [v.basename for v in *kids], { 'child1', 'child2' }
done!
.file_type is a string describing the file type
assert.equal 'directory', File('/bin').file_type
assert.equal 'regular', File('/bin/ls').file_type
assert.equal 'special', File('/dev/null').file_type
.writeable is true if the file represents a entry that can be written to
with_tmpdir (dir) ->
assert.is_true dir.writeable
file = dir / 'file.txt'
assert.is_true file.writeable
file\touch!
assert.is_true file.writeable
assert.is_false File('/no/such/directory/orfile.txt').writeable
.readable is true if the file represents a entry that can be read
with_tmpdir (dir) ->
assert.is_true dir.readable
file = dir / 'file.txt'
assert.is_false file.readable
file\touch!
assert.is_true file.readable
.etag is a string that can be used to check for modification
File.with_tmpfile (file) ->
assert.is.not_nil file.etag
assert.equal type(file.etag), 'string'
.modified_at is a the unix time when the file was last modified
File.with_tmpfile (file) ->
assert.is.not_nil file.modified_at
read(..) is a short hand for doing a read(..) on the Lua file handle
File.with_tmpfile (file) ->
file.contents = 'first line\n'
assert.same { 'first', ' line' }, { file\read 5, '*l' }
join() returns a new file representing the specified child
assert.equal File('/bin')\join('ls').path, '/bin/ls'
relative_to_parent() returns a path relative to the specified parent
parent = File '/bin'
file = File '/bin/ls'
assert.equal 'ls', file\relative_to_parent(parent)
is_below(dir) returns true if the file is located beneath <dir>
parent = File '/bin'
assert.is_true File('/bin/ls')\is_below parent
assert.is_true File('/bin/sub/ls')\is_below parent
assert.is_false File('/usr/bin/ls')\is_below parent
rm and unlink is an alias for delete
assert.equal File.rm, File.delete
assert.equal File.unlink, File.delete
rm_r is an alias for delete_all
assert.equal File.rm_r, File.delete_all
tmpfile()
returns a file instance pointing to an existing file
file = File.tmpfile!
assert.is_true file.exists
file\delete!
with_tmpfile(f)
invokes <f> with the file
f = spy.new (file) ->
assert.equals 'File', typeof(file)
File.with_tmpfile f
assert.spy(f).was_called(1)
removes the temporary file even if <f> raises an error
local tmpfile
f = (file) ->
tmpfile = file
error 'noo'
assert.raises 'noo', -> File.with_tmpfile f
assert.is_false tmpfile.exists
tmpdir()
returns a file instance pointing to an existing directory
file = File.tmpdir!
assert.is_true file.exists
assert.is_true file.is_directory
file\delete_all!
expand_path(path)
expands "~" into the full path of the home directory
assert.equals "#{os.getenv('HOME')}/foo.txt", (File.expand_path '~/foo.txt')
assert.equals "#{os.getenv('HOME')}/foo.txt", (File.expand_path '/blah/~/foo.txt')
handles multiple "~/" by replacing the deepest one
assert.equals "#{os.getenv('HOME')}/foo.txt", (File.expand_path '/a/b/~/c/~/foo.txt')
does not expand "~" when part of another word
assert.equals "/dir~/foo.txt", (File.expand_path '/dir~/foo.txt')
does not expand trailing "~" without "/" suffix
assert.equals "/dir/~", (File.expand_path '/dir/~')
new(p, cwd, opts = {})
accepts a string as denothing a path
File '/bin/ls'
accepts other files as well
f = File '/bin/ls'
f2 = File f
assert.equal f, f2
accepts an optional type specifying the file's type
f = File '/notherenothere', nil, type: File.TYPE_DIRECTORY
assert.is_true f.is_directory
(when <cwd> is specified)
resolves a string <p> relative to <cwd>
assert.equal '/bin/ls', File('ls', '/bin').path
resolves an absolute string <p> as the absolute path
assert.equal '/bin/ls', File('/bin/ls', '/home').path
accepts other Files as <cwd>
assert.equal '/bin/ls', File('ls', File('/bin')).path
.is_absolute
returns true if the given path is absolute
assert.is_true File.is_absolute '/bin/ls'
assert.is_true File.is_absolute 'c:\\\\bin\\ls'
returns false if the given path is absolute
assert.is_false File.is_absolute 'bin/ls'
assert.is_false File.is_absolute 'bin\\ls'
.display_name
is the same as the basename for files
assert.equal 'base.ext', File('/foo/base.ext').display_name
has a trailing separator for directories
assert.equal 'bin/', File('/usr/bin').display_name
.short_path
returns the path with the home directory replace by "~"
assert.equal '~', File(os.getenv('HOME')).short_path
file = File(os.getenv('HOME')) / 'foo.txt'
assert.equal '~/foo.txt', file.short_path
does not replace a directory the home directory is a prefix of directory
home_path = File(os.getenv('HOME')).path .. '-suffix'
home = File(home_path)
file = home / 'foo.txt'
assert.equal home.path, home.short_path
assert.equal file.path, file.short_path
contents
assigning a string writes the string to the file
File.with_tmpfile (file) ->
file.contents = 'hello world'
f = io.open file.path
read_back = f\read '*all'
f\close!
assert.equal read_back, 'hello world'
returns the contents of the file
File.with_tmpfile (file) ->
f = io.open file.path, 'wb'
f\write 'hello world'
f\close!
assert.equal file.contents, 'hello world'
open([mode, function])
(when <function> is nil)
returns a Lua file handle
File.with_tmpfile (file) ->
file.contents = 'first line\nsecond line\n'
fh = file\open!
assert.equal 'first line', fh\read!
assert.equal 'second line\n', fh\read '*L'
fh\close!
(when <function> is provided)
it is invoked with the file handle
File.with_tmpfile (file) ->
file.contents = 'first line\nsecond line\n'
local first_line
file\open 'r', (fh) ->
first_line = fh\read!
assert.equal 'first line', first_line
returns the returns values of the function
File.with_tmpfile (file) ->
assert.same { 'callback', nil, 'last' }, { file\open 'r', -> 'callback', nil, 'last' }
closes the file automatically after invoking <function>
File.with_tmpfile (file) ->
local handle
file\open 'r', (fh) -> handle = fh
assert.has_errors -> handle\read!
(.. when <function> raises an error)
propagates that error
File.with_tmpfile (file) ->
assert.raises 'kaboom', -> file\open 'r', -> error 'kaboom'
still closes the file
File.with_tmpfile (file) ->
local handle
pcall -> file\open 'r', (fh) ->
handle = fh
error 'kaboom'
assert.has_errors -> handle\read!
mkdir()
creates a directory for the path specified by the file
File.with_tmpfile (file) ->
file\delete!
file\mkdir!
assert.is_true file.exists and file.is_directory
raises an error if the directory could not be created
assert.has_error -> File('/aksdjskjdgudfkj')\mkdir!
mkdir_p()
creates a directory for the path specified by the file, including parents
File.with_tmpfile (file) ->
file\delete!
file = file\join 'sub/foo'
file\mkdir_p!
assert.is_true file.exists and file.is_directory
delete()
deletes the target file
File.with_tmpfile (file) ->
file\delete!
assert.is_false file.exists
raise an error if the file does not exist
file = File.tmpfile!
file\delete!
assert.error -> file\delete!
delete_all()
raise an error if the file does not exist
File.with_tmpfile (file) ->
file\delete!
assert.error -> file\delete!
(for a regular file)
deletes the target file
File.with_tmpfile (file) ->
file\delete_all!
assert.is_false file.exists
(for a directory)
deletes the directory and all sub entries
with_tmpdir (dir) ->
dir\join('child1')\mkdir!
dir\join('child1/sub_child')\touch!
dir\join('child2')\touch!
dir\delete_all!
assert.is_false dir.exists
touch()
creates the file if does not exist
File.with_tmpfile (file) ->
file\delete!
file\touch!
assert.is_true file.exists
raises an error if the file could not be created
file = File '/no/does/not/exist'
assert.error -> file\touch!
tostring()
returns a string containing the path
File.with_tmpfile (file) ->
to_s = file\tostring!
assert.equal 'string', typeof to_s
assert.equal to_s, file.path
find()
with_populated_dir = (f) ->
with_tmpdir (dir) ->
dir\join('child1')\mkdir!
dir\join('child1/sub_dir')\mkdir!
dir\join('child1/sub_dir/deep.lua')\touch!
dir\join('child1/sub_child.txt')\touch!
dir\join('child1/sandwich.lua')\touch!
dir\join('child2')\touch!
f dir
raises an error if the file is not a directory
file = File '/no/does/not/exist'
assert.error -> file\find!
(with no parameters given)
returns a list of all sub entries
with_populated_dir (dir) ->
files = dir\find!
table.sort files, (a,b) -> a.path < b.path
normalized = [f\relative_to_parent dir for f in *files]
assert.same {
'child1',
'child1/sandwich.lua',
'child1/sub_child.txt',
'child1/sub_dir',
'child1/sub_dir/deep.lua',
'child2'
}, normalized
(when the sort parameter is given)
returns a list of all sub entries in a pleasing order
with_populated_dir (dir) ->
files = dir\find sort: true
normalized = [f\relative_to_parent dir for f in *files]
assert.same normalized, {
'child2',
'child1',
'child1/sandwich.lua',
'child1/sub_child.txt',
'child1/sub_dir',
'child1/sub_dir/deep.lua',
}
(when filter: is passed as an option)
excludes files for which <filter(file)> returns true
with_populated_dir (dir) ->
files = dir\find filter: (file) ->
file.basename != 'sandwich.lua' and file.basename != 'child1'
assert.same { 'child1', 'sandwich.lua' }, [f.basename for f in *files]
(when the on_enter parameter is given)
is called once for each directory with the dir and files so far
with_populated_dir (dir) ->
dirs = {}
total_files = 0
dir\find on_enter: (enter_dir, files) ->
dirs[#dirs + 1] = enter_dir
total_files = files
assert.equal 3, #dirs
assert.equal 6, #total_files
table.sort dirs
assert.same {
dir,
dir\join('child1'),
dir\join('child1')\join('sub_dir'),
}, dirs
(.. and it returns "break")
causes an early return
with_populated_dir (dir) ->
files, cancelled = dir\find on_enter: (enter_dir, files) ->
if enter_dir.basename == 'child1'
return 'break'
assert.equal true, cancelled
table.sort files
assert.same {
dir\join('child1'),
dir\join('child2'),
}, files
find_paths(opts = {})
with_populated_dir = (f) ->
with_tmpdir (dir) ->
dir\join('child1')\mkdir!
dir\join('child1/sub_dir')\mkdir!
dir\join('child1/sub_dir/deep.lua')\touch!
dir\join('child1/sub_child.txt')\touch!
dir\join('child1/sandwich.lua')\touch!
dir\join('child2')\touch!
f dir
raises an error if the file is not a directory
file = File '/no/does/not/exist'
assert.error -> file\find_paths!
(with no option specified)
returns a list of all regular and directory sub paths
with_populated_dir (dir) ->
paths = dir\find_paths!
table.sort paths
assert.same {
'child1/',
'child1/sandwich.lua',
'child1/sub_child.txt',
'child1/sub_dir/',
'child1/sub_dir/deep.lua',
'child2'
}, paths
(with the exclude_directories option specified)
returns a list of all regular sub paths
with_populated_dir (dir) ->
paths = dir\find_paths exclude_directories: true
table.sort paths
assert.same {
'child1/sandwich.lua',
'child1/sub_child.txt',
'child1/sub_dir/deep.lua',
'child2'
}, paths
(with the exclude_non_directories option specified)
returns a list of all directory paths
with_populated_dir (dir) ->
paths = dir\find_paths exclude_non_directories: true
table.sort paths
assert.same {
'child1/',
'child1/sub_dir/',
}, paths
(when filter: is passed as an option)
excludes paths for which <filter(path)> returns true
with_populated_dir (dir) ->
paths = dir\find_paths filter: (path) ->
not (path\ends_with('sandwich.lua') or path == 'child1/')
assert.same { 'child1/', 'child1/sandwich.lua' }, paths
(when the on_enter parameter is given)
is called once for each directory with the dir and paths so far
with_populated_dir (dir) ->
dirs = {}
total_files = 0
dir\find_paths on_enter: (enter_dir, files) ->
dirs[#dirs + 1] = enter_dir
total_files = files
assert.equal 3, #dirs
assert.equal 6, #total_files
table.sort dirs
assert.same {
'./',
'child1/',
'child1/sub_dir/',
}, dirs
(.. and it returns "break")
causes an early return
with_populated_dir (dir) ->
local count
paths, cancelled = dir\find_paths on_enter: (enter_dir, cur_paths) ->
if enter_dir == 'child1/'
count = #cur_paths
return 'break'
assert.equal true, cancelled
assert.equal count, #paths
copy(dest)
copies the given file
with_tmpdir (dir) ->
a = dir/'a.txt'
b = dir/'b.txt'
a.contents = 'hello'
a\copy b
assert.same b.contents, 'hello'
a.contents = 'hello 2'
assert.has_errors -> a\copy b
assert.same b.contents, 'hello'
a\copy b, {'COPY_OVERWRITE'}
assert.same b.contents, 'hello 2'
/ and .. joins the file with the specified argument
file = File('/bin')
assert.equal (file / 'ls').path, '/bin/ls'
assert.equal (file .. 'ls').path, '/bin/ls'
tostring returns the result of File.tostring
file = File '/bin/ls'
assert.equal file\tostring!, tostring file
== returns true if the files point to the same path
assert.equal File('/bin/ls'), File('/bin/ls')