aboutsummaryrefslogtreecommitdiffhomepage
path: root/core/lfs_ext.lua
blob: 786c54d8455e5ee9acf665c399d3683978156ca7 (plain)
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