1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
|
-- Copyright 2007-2015 Mitchell mitchell.att.foicica.com. See LICENSE.
--[[ This comment is for LuaDoc.
---
-- Extends the `lfs` library to find files in directories and determine absolute
-- file paths.
module('lfs')]]
---
-- The filter table containing common binary file extensions and version control
-- directories to exclude when iterating over files and directories using
-- `dir_foreach` when its `exclude_FILTER` argument is `false`.
-- @see dir_foreach
-- @class table
-- @name FILTER
lfs.FILTER = {
extensions = {
'a', 'bmp', 'bz2', 'class', 'dll', 'exe', 'gif', 'gz', 'jar', 'jpeg', 'jpg',
'o', 'png', 'so', 'tar', 'tgz', 'tif', 'tiff', 'zip'
},
folders = {'%.bzr$', '%.git$', '%.hg$', '%.svn$', 'CVS$'}
}
local lfs_symlinkattributes = lfs.symlinkattributes
-- Determines whether or not the given file matches the given filter.
-- @param file The filename.
-- @param filter The filter table.
-- @return boolean `true` or `false`.
local function exclude(file, filter)
if not filter then return false end
local ext = filter.extensions
if ext and ext[file:match('[^%.]+$')] then return true end
for i = 1, #filter do
local patt = filter[i]
if patt:sub(1, 1) ~= '!' then
if file:find(patt) then return true end
else
if not file:find(patt:sub(2)) then return true end
end
end
return filter.symlink and lfs_symlinkattributes(file, 'mode') == 'link'
end
---
-- Iterates over all files and sub-directories (up to *n* levels deep) in
-- directory *dir*, calling function *f* with each file found.
-- Files passed to *f* do not match any pattern in string or table *filter*,
-- and, unless *exclude_FILTER* is `true`, `lfs.FILTER` as well. A filter table
-- contains:
--
-- + Lua patterns that match filenames to exclude.
-- + Optional `folders` sub-table that contains patterns matching directories
-- to exclude.
-- + Optional `extensions` sub-table that contains raw file extensions to
-- exclude.
-- + Optional `symlink` flag that when `true`, excludes symlinked files (but
-- not symlinked directories).
-- + Optional `folders.symlink` flag that when `true`, excludes symlinked
-- directories.
--
-- Any filter patterns starting with '!' exclude files and directories that do
-- not match the pattern that follows.
-- @param dir The directory path to iterate over.
-- @param f Function to call with each full file path found. If *f* returns
-- `false` explicitly, iteration ceases.
-- @param filter Optional filter for files and directories to exclude.
-- @param exclude_FILTER Optional flag indicating whether or not to exclude the
-- default filter `lfs.FILTER` in the search. If `false`, adds `lfs.FILTER` to
-- *filter*.
-- The default value is `false` to include the default filter.
-- @param n Optional maximum number of directory levels to descend into.
-- The default value is `nil`, which indicates no limit.
-- @param include_dirs Optional flag indicating whether or not to call *f* with
-- directory names too. Directory names are passed with a trailing '/' or '\',
-- depending on the current platform.
-- The default value is `false`.
-- @param level Utility value indicating the directory level this function is
-- at. This value is used and set internally, and should not be set otherwise.
-- @see FILTER
-- @name dir_foreach
function lfs.dir_foreach(dir, f, filter, exclude_FILTER, n, include_dirs, level)
if not level then level = 0 end
if level == 0 then
-- Convert filter to a table from nil or string arguments.
if not filter then filter = {} end
if type(filter) == 'string' then filter = {filter} end
-- Add FILTER to filter unless specified otherwise.
if not exclude_FILTER then
for k, v in pairs(lfs.FILTER) do
if type(v) == 'table' then
if not filter[k] then filter[k] = {} end
local filter_k = filter[k]
for k2, v2 in pairs(v) do
filter_k[tonumber(k2) and #filter_k + 1 or k2] = v2
end
else
filter[tonumber(k) and #filter + 1 or k] = v
end
end
end
-- Create file extension filter hash table for quick lookups.
local ext = filter.extensions
if ext then for i = 1, #ext do ext[ext[i]] = true end end
end
local dir_sep, lfs_attributes = not WIN32 and '/' or '\\', lfs.attributes
for file in lfs.dir(dir) do
if not file:find('^%.%.?$') then -- ignore . and ..
file = dir..(dir ~= '/' and dir_sep or '')..file
local type = lfs_attributes(file, 'mode')
if type == 'directory' and not exclude(file, filter.folders) then
if include_dirs and f(file..dir_sep) == false then return end
if not n or level < n then
lfs.dir_foreach(file, f, filter, nil, n, include_dirs, level + 1)
end
elseif type == 'file' and not exclude(file, filter) then
if f(file) == false then return end
end
end
end
end
---
-- Returns the absolute path to string *filename*.
-- *prefix* or `lfs.currentdir()` is prepended to a relative filename. The
-- returned path is not guaranteed to exist.
-- @param filename The relative or absolute path to a file.
-- @param prefix Optional prefix path prepended to a relative filename.
-- @return string absolute path
-- @name abspath
function lfs.abspath(filename, prefix)
if WIN32 then filename = filename:gsub('/', '\\') end
if not filename:find(not WIN32 and '^/' or '^%a:[/\\]') and
not (WIN32 and filename:find('^\\\\')) then
prefix = prefix or lfs.currentdir()
filename = prefix..(not WIN32 and '/' or '\\')..filename
end
filename = filename:gsub('%f[^/\\]%.[/\\]', '') -- clean up './'
while filename:find('[^/\\]+[/\\]%.%.[/\\]') do
filename = filename:gsub('[^/\\]+[/\\]%.%.[/\\]', '') -- clean up '../'
end
return filename
end
|