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
|
-- Copyright 2007-2019 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`.
-- @see dir_foreach
-- @class table
-- @name default_filter
lfs.default_filter = {
-- File extensions to exclude.
'!.a', '!.bmp', '!.bz2', '!.class', '!.dll', '!.exe', '!.gif', '!.gz',
'!.jar', '!.jpeg', '!.jpg', '!.o', '!.pdf', '!.png', '!.so', '!.tar', '!.tgz',
'!.tif', '!.tiff', '!.xz', '!.zip',
-- Directories to exclude.
'!/%.bzr$', '!/%.git$', '!/%.hg$', '!/%.svn$', '!/node_modules$',
}
---
-- Iterates over all files and sub-directories (up to *n* levels deep) in
-- directory *dir*, calling function *f* with each file found.
-- String or list *filter* determines which files to pass through to *f*, with
-- the default filter being `lfs.default_filter`. A filter consists of Lua
-- patterns that match file and directory paths to include or exclude. Exclusive
-- patterns begin with a '!'. If no inclusive patterns are given, any path is
-- initially considered. As a convenience, file extensions can be specified
-- literally instead of as a Lua pattern (e.g. '.lua' vs. '%.lua$'), and '/'
-- also matches the Windows directory separator ('[/\\]' is not needed).
-- @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 include and
-- exclude. The default value is `lfs.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, n, include_dirs, level)
if not level then
-- Convert filter to a table from nil or string arguments.
if not filter then filter = lfs.default_filter end
if type(filter) == 'string' then filter = {filter} end
-- Process the given filter into something that can match files more easily
-- and/or quickly. For example, convert '.ext' shorthand to '%.ext$',
-- substitute '/' with '[/\\]', and enable hash lookup for file extensions
-- to include or exclude.
local processed_filter = {
consider_any = true,
exts = setmetatable({}, {__index = function() return true end})
}
for i = 1, #filter do
local patt = filter[i]
patt = patt:gsub('^(!?)%%?%.([^.]+)$', '%1%%.%2$') -- '.lua' to '%.lua$'
patt = patt:gsub('/([^\\])', '[/\\]%1') -- '/' to '[/\\]'
local include = not patt:find('^!')
local ext = patt:match('^!?%%.([^.]+)%$$')
if ext then
processed_filter.exts[ext] = include
if include then setmetatable(processed_filter.exts, nil) end
else
if include then processed_filter.consider_any = false end
processed_filter[#processed_filter + 1] = patt
end
end
filter = processed_filter
end
local dir_sep, lfs_attributes = not WIN32 and '/' or '\\', lfs.attributes
for basename in lfs.dir(dir) do
if basename:find('^%.%.?$') then goto continue end -- ignore . and ..
local filename = dir..(dir ~= '/' and dir_sep or '')..basename
local mode = lfs_attributes(filename, 'mode')
if mode ~= 'directory' and mode ~= 'file' then goto continue end
local include
if mode == 'file' then
local ext = filename:match('[^.]+$')
if ext and not filter.exts[ext] then goto continue end
include = filter.consider_any or ext ~= nil
elseif mode == 'directory' then
include = filter.consider_any
end
for i = 1, #filter do
local patt = filter[i]
-- Treat exclusive patterns as logical AND.
if patt:find('^!') and filename:find(patt:sub(2)) then goto continue end
-- Treat inclusive patterns as logical OR.
include = include or (not patt:find('^!') and filename:find(patt))
end
if include and mode == 'directory' then
if include_dirs and f(filename..dir_sep) == false then return end
if not n or (level or 0) < n then
local halt = lfs.dir_foreach(filename, f, filter, n, include_dirs,
(level or 0) + 1) == false
if halt then return false end
end
elseif include and mode == 'file' then
if f(filename) == false then return false end
end
::continue::
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('[^/\\]+[/\\]%.%.[/\\]', '', 1) -- clean up '../'
end
return filename
end
|