aboutsummaryrefslogtreecommitdiff
path: root/core/lfs_ext.lua
blob: d36753eb4cb52edf4a91c543d6084c6c8b2107a4 (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
-- Copyright 2007-2018 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$',
  --[[Temporary backwards-compatibility]]extensions={'a','bmp','bz2','class','dll','exe','gif','gz','jar','jpeg','jpg','o','pdf','png','so','tar','tgz','tif','tiff','xz','zip'},folders={'%.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
    --[[Temporary backwards-compatibility.]]if filter.extensions and filter~=lfs.default_filter then;local compat_filter={};ui.dialogs.textbox{title='Compatibility Notice',informative_text='Warning: use of deprecated filter format; please update your scripts',text=debug.traceback()};for i=1,#filter do if filter[i]:find('^!')then compat_filter[i]=filter[i]:sub(2)else compat_filter[i]='!'..filter[i]end end;for i=1,#filter.extensions do compat_filter[#compat_filter+1]='!.'..filter.extensions[i]end;if filter.folders then for i=1,#filter.folders do; compat_filter[#compat_filter+1]='!'..filter.folders[i]:gsub('^%%.[^.]+%$$','[/\\]%0')end end;filter = compat_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 = {}}
    for i = 1, #filter do
      local patt = filter[i]
      patt = patt:gsub('^(!?)%%?%.([^.]+)$', '%1%%.%2$') -- '.lua' to '%.lua$'
      patt = patt:gsub('/([^\\])', '[/\\]%1') -- '/' to '[/\\]'
      if not patt:find('^!') then processed_filter.consider_any = false end
      local ext = patt:match('^!?%%.([^.]+)%$$')
      if ext then
        processed_filter.exts[ext] = patt:find('^!') and 'exclude' or 'include'
      else
        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 not basename:find('^%.%.?$') then -- 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 skip end
      local include
      if mode == 'file' then
        local ext = filename:match('[^.]+$')
        if ext and filter.exts[ext] == 'exclude' then goto skip end
        include = filter.consider_any or ext and filter.exts[ext] == 'include'
      elseif mode == 'directory' then
        include = filter.consider_any or #filter == 0
      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 skip 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
      ::skip::
    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('[^/\\]+[/\\]%.%.[/\\]', '', 1) -- clean up '../'
  end
  return filename
end