aboutsummaryrefslogtreecommitdiff
path: root/data/core/project.lua
blob: 68b93cadd5ff47db35dfac6ddc29c9be6dd23c67 (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
135
136
137
138
139
140
141
local Object = require "core.object"

local Project = Object:extend()

local core = require "core"
local common = require "core.common"
local config = require "core.config"

-- inspect config.ignore_files patterns and prepare ready to use entries.
local function compile_ignore_files()
  local ipatterns = config.ignore_files
  local compiled = {}
  -- config.ignore_files could be a simple string...
  if type(ipatterns) ~= "table" then ipatterns = {ipatterns} end
  for i, pattern in ipairs(ipatterns) do
    -- we ignore malformed pattern that raise an error
    if pcall(string.match, "a", pattern) then
      table.insert(compiled, {
        use_path = pattern:match("/[^/$]"), -- contains a slash but not at the end
        -- An '/' or '/$' at the end means we want to match a directory.
        match_dir = pattern:match(".+/%$?$"), -- to be used as a boolen value
        pattern = pattern -- get the actual pattern
      })
    end
  end
  return compiled
end


function Project:new(path)
  self.path = path
  self.name = common.basename(path)
  self.compiled = compile_ignore_files()
  return self
end


-- The function below works like system.absolute_path except it
-- doesn't fail if the file does not exist. We consider that the
-- current dir is core.project_dir so relative filename are considered
-- to be in core.project_dir.
-- Please note that .. or . in the filename are not taken into account.
-- This function should get only filenames normalized using
-- common.normalize_path function.
function Project:absolute_path(filename)
  if common.is_absolute_path(filename) then
    return common.normalize_path(filename)
  elseif not self or not self.path then
    local cwd = system.absolute_path(".")
    return cwd .. PATHSEP .. common.normalize_path(filename)
  else
    return self.path .. PATHSEP .. filename
  end
end


function Project:normalize_path(filename)
  filename = common.normalize_path(filename)
  if common.path_belongs_to(filename, self.path) then
    filename = common.relative_path(self.path, filename)
  end
  return filename
end



local function fileinfo_pass_filter(info, ignore_compiled)
  if info.size >= config.file_size_limit * 1e6 then return false end
  local basename = common.basename(info.filename)
  -- replace '\' with '/' for Windows where PATHSEP = '\'
  local fullname = "/" .. info.filename:gsub("\\", "/")
  for _, compiled in ipairs(ignore_compiled) do
    local test = compiled.use_path and fullname or basename
    if compiled.match_dir then
      if info.type == "dir" and string.match(test .. "/", compiled.pattern) then
        return false
      end
    else
      if string.match(test, compiled.pattern) then
        return false
      end
    end
  end
  return true
end



function Project:is_ignored(info, path)
  -- info can be not nil but info.type may be nil if is neither a file neither
  -- a directory, for example for /dev/* entries on linux.
  if info and info.type then
    if path then info.filename = path end
    return not fileinfo_pass_filter(info, self.compiled)
  end
  return false
end

-- compute a file's info entry completed with "filename" to be used
-- in project scan or falsy if it shouldn't appear in the list.
function Project:get_file_info(path)
  local info = system.get_file_info(path)
  if self:is_ignored(info, path) then return nil end
  return info
end

local function get_dir_content(project, path, entries)
  local all = system.list_dir(path) or {}
  for _, file in ipairs(all) do
    local file = path .. PATHSEP .. file
    local info = project:get_file_info(file)
    if info then
      info.filename = file
      table.insert(entries, info)
    end
  end
end

local function find_files_rec(project, path)
  local entries = {}
  get_dir_content(project, path, entries)

  for _, info in ipairs(entries) do
    if info.type == "file" then
      coroutine.yield(project, info)
    elseif not common.match_pattern(common.basename(info.filename), config.ignore_files) and info.type then
      get_dir_content(project, info.filename, entries)
    end
  end
end


function Project:files()
  return coroutine.wrap(function()
    find_files_rec(self, self.path)
  end)
end



return Project