aboutsummaryrefslogtreecommitdiff
path: root/files.lua
blob: d9686d9053cb89f957c77f40a408ae5005a07e73 (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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
local lpeg = require('lpeg')
local re = require('re')
local files = { }
local _FILE_CACHE = { }
files.spoof = function(filename, contents)
  _FILE_CACHE[filename] = contents
end
files.read = function(filename)
  do
    local file_contents = _FILE_CACHE[filename]
    if file_contents then
      return file_contents
    end
  end
  if filename == 'stdin' then
    local contents = io.read('*a')
    _FILE_CACHE['stdin'] = contents
    return contents
  end
  local file = io.open(filename)
  if package.nomsupath and not file then
    for nomsupath in package.nomsupath:gmatch("[^;]+") do
      file = io.open(nomsupath .. "/" .. filename)
      if file then
        break
      end
    end
  end
  if not (file) then
    return nil
  end
  local contents = file:read("*a")
  file:close()
  _FILE_CACHE[filename] = contents
  return contents
end
local iterate_single
iterate_single = function(item, prev)
  if item == prev then
    return nil
  else
    return item
  end
end
local match, gsub
do
  local _obj_0 = string
  match, gsub = _obj_0.match, _obj_0.gsub
end
iterate_single = function(item, prev)
  if item == prev then
    return nil
  else
    return item
  end
end
local ok, lfs = pcall(require, "lfs")
if ok then
  local raw_file_exists
  raw_file_exists = function(filename)
    local mode = lfs.attributes(filename, 'mode')
    if mode == 'file' or mode == 'directory' then
      return true
    else
      return false
    end
  end
  files.exists = function(path)
    if path == 'stdin' or raw_file_exists(path) then
      return true
    end
    if package.nomsupath then
      for nomsupath in package.nomsupath:gmatch("[^;]+") do
        if raw_file_exists(nomsupath .. "/" .. path) then
          return true
        end
      end
    end
    return false
  end
  local browse
  browse = function(filename)
    local file_type = lfs.attributes(filename, 'mode')
    if file_type == 'file' then
      if match(filename, "%.nom$") or match(filename, "%.lua$") then
        coroutine.yield(filename)
        return true
      end
    elseif file_type == 'directory' then
      for subfile in lfs.dir(filename) do
        if not (subfile == "." or subfile == ".." or not subfile:match("%.nom$")) then
          browse(filename .. "/" .. subfile)
        end
      end
      return true
    elseif file_type == 'char device' then
      coroutine.yield(filename)
      return true
    end
    return false
  end
  files.walk = function(path)
    if match(path, "%.nom$") or match(path, "%.lua$") or path == 'stdin' then
      return iterate_single, path
    end
    return coroutine.wrap(function()
      if not browse(path) and package.nomsupath then
        for nomsupath in package.nomsupath:gmatch("[^;]+") do
          if browse(nomsupath .. "/" .. path) then
            break
          end
        end
      end
      return nil
    end)
  end
else
  if io.popen('find . -maxdepth 0'):close() then
    error("Could not find 'luafilesystem' module and couldn't run system command `find` (this might happen on Windows). Please install `luafilesystem` (which can be found at: http://keplerproject.github.io/luafilesystem/ or `luarocks install luafilesystem`)", 0)
  end
  local sanitize
  sanitize = function(path)
    path = gsub(path, "\\", "\\\\")
    path = gsub(path, "`", "")
    path = gsub(path, '"', '\\"')
    path = gsub(path, "$", "")
    return path
  end
  files.exists = function(path)
    if not (io.popen("ls " .. tostring(sanitize(path))):close()) then
      return true
    end
    if package.nomsupath then
      for nomsupath in package.nomsupath:gmatch("[^;]+") do
        if not (io.popen("ls " .. tostring(nomsupath) .. "/" .. tostring(sanitize(path))):close()) then
          return true
        end
      end
    end
    return false
  end
  files.walk = function(path)
    if match(path, "%.nom$") or match(path, "%.lua$") or path == 'stdin' then
      return iterate_single, path
    end
    path = sanitize(path)
    return coroutine.wrap(function()
      local f = io.popen('find -L "' .. path .. '" -not -path "*/\\.*" -type f -name "*.nom"')
      local found = false
      for line in f:lines() do
        found = true
        coroutine.yield(line)
      end
      if not found and package.nomsupath then
        f:close()
        for nomsupath in package.nomsupath:gmatch("[^;]+") do
          f = io.popen('find -L "' .. package.nomsupath .. '/' .. path .. '" -not -path "*/\\.*" -type f -name "*.nom"')
          for line in f:lines() do
            found = true
            coroutine.yield(line)
          end
          f:close()
          if found then
            break
          end
        end
      end
      if not (found) then
        return error("Invalid file path: " .. tostring(path))
      end
    end)
  end
end
local line_counter = re.compile([[    lines <- {| line (%nl line)* |}
    line <- {} (!%nl .)*
]], {
  nl = lpeg.P("\r") ^ -1 * lpeg.P("\n")
})
local get_lines = re.compile([[    lines <- {| line (%nl line)* |}
    line <- {[^%nl]*}
]], {
  nl = lpeg.P("\r") ^ -1 * lpeg.P("\n")
})
local _LINE_STARTS = { }
files.get_line_starts = function(str)
  if type(str) ~= 'string' then
    str = tostring(str)
  end
  do
    local starts = _LINE_STARTS[str]
    if starts then
      return starts
    end
  end
  local line_starts = line_counter:match(str)
  _LINE_STARTS[str] = line_starts
  return line_starts
end
files.get_line_number = function(str, pos)
  local line_starts = files.get_line_starts(str)
  local lo, hi = 1, #line_starts
  while lo <= hi do
    local mid = math.floor((lo + hi) / 2)
    if line_starts[mid] > pos then
      hi = mid - 1
    else
      lo = mid + 1
    end
  end
  return hi
end
files.get_line = function(str, line_no)
  local line_starts = files.get_line_starts(str)
  return str:sub(line_starts[line_no] or 1, (line_starts[line_no + 1] or 1) - 2)
end
files.get_lines = function(str)
  return get_lines:match(str)
end
return files