Merge branch 'master' of git://cams.pavlovian.net/questhelper
[QuestHelper.git] / Development / fileutil.lua
blobe556ad772291654ca387a1c75d66f4e30e3b623c
1 FileUtil = {}
3 --[[ Note:
5 fileHash and forEachFile will probably need replacements for other operating systems. ]]
7 --[[ Warning:
9 Pretty much all these functions can be made to do something malicious if given bad file names;
10 don't use input from untrusted sources. ]]
12 -- Our horrible test to check if you're using Windows or not.
13 local is_windows = os.getenv("HOMEDRIVE") ~= nil or
14 os.getenv("WINDIR") ~= nil or
15 os.getenv("OS") == "Windows_NT"
17 local home = os.getenv("HOME")
19 FileUtil.fileName = function(filename)
20 local home_path = select(3, string.find(filename, "^~(.*)$"))
22 if home_path then
23 return (is_windows and (os.getenv("HOMEDRIVE")..os.getenv("HOMEPATH")) or os.getenv("HOME"))..home_path
24 end
26 return filename
27 end
30 FileUtil.quoteFileWindows = function (filename)
31 -- Escapes filenames in Windows, and converts slashes to backslashes.
33 filename = FileUtil.fileName(filename)
35 if filename == "" then return "\"\"" end
37 local result = ""
38 for i=1,string.len(filename) do
39 local c = string.sub(filename, i, i)
40 if c == "/" then
41 c = "\\"
42 elseif string.find(c, "[^\\%.%a%d]") then
43 c = "^"..c
44 end
46 result = result .. c
47 end
48 return result
49 end
51 FileUtil.quoteFileNix = function (filename)
52 -- Escapes filenames in *nix, and converts backslashes to slashes.
53 -- Also used directly for URLs, which are always *nix style paths
55 filename = FileUtil.fileName(filename)
57 if filename == "" then return "\"\"" end
59 local result = ""
60 for i=1,string.len(filename) do
61 local c = string.sub(filename, i, i)
62 if c == "\\" then
63 c = "/"
64 elseif string.find(c, "[^/%.%-%a%d]") then
65 c = "\\"..c
66 end
68 result = result .. c
69 end
71 return result
72 end
74 FileUtil.quoteFile = is_windows and FileUtil.quoteFileWindows or FileUtil.quoteFileNix
76 local function escapeForPattern(text)
77 return string.gsub(text, "[%%%^%$%.%+%*%-%?%[%]]", function (x) return "%"..x end)
78 end
80 FileUtil.fileHash = function(filename)
81 print(string.format("Hashing " .. filename))
82 local stream = io.popen(string.format("sha1sum %s", FileUtil.quoteFile(filename)))
84 if not stream then
85 print("Failed to calculate hash: "..filename)
86 return nil
87 end
89 local line = stream:read()
90 io.close(stream)
91 if line then
92 return select(3, string.find(line, string.format("^([abcdef%%d]+) %s$", escapeForPattern(filename))))
93 end
94 end
96 FileUtil.fileExists = function(filename)
97 local stream = io.open(FileUtil.fileName(filename), "r")
98 if stream then
99 local exists = stream:read() ~= nil
100 io.close(stream)
101 return exists
103 return false
106 FileUtil.isDirectory = function(filename)
107 local stream = io.popen(string.format(is_windows and "DIR /B /AD %s" or "file -b %s", FileUtil.quoteFile(filename)), "r")
108 if stream then
109 local result = stream:read("*line")
110 io.close(stream)
111 return is_windows and (result ~= "File Not Found") or (result == "directory")
113 error("Failed to execute 'file' command.")
116 -- Extra strings passed to copyFile are pattern/replacement pairs, applied to
117 -- each line of the file being copied.
118 FileUtil.copyFile = function(in_name, out_name, ...)
119 local extra = select("#", ...)
121 if FileUtil.isDirectory(out_name) then
122 -- If out_name is a directory, change it to a filename.
123 out_name = string.format("%s/%s", out_name, select(3, string.find(in_name, "([^/\\]*)$")))
126 if extra > 0 then
127 assert(extra%2==0, "Odd number of arguments.")
128 local src = io.open(in_name, "rb")
129 if src then
130 local dest = io.open(out_name, "wb")
131 if dest then
132 while true do
133 local original = src:read("*line")
134 if not original then break end
135 local eol
136 original, eol = select(3, string.find(original, "^(.-)(\r?)$")) -- Try to keep the CR in CRLF codes intact.
137 local replacement = original
138 for i = 1,extra,2 do
139 local a, b = select(i, ...)
140 replacement = string.gsub(replacement, a, b)
143 -- If we make a line blank, and it wasn't blank before, we omit the line.
144 if original == replacement or replacement ~= "" then
145 dest:write(replacement, eol, "\n")
148 io.close(dest)
149 else
150 print("Failed to copy "..in_name.." to "..out_name.."; couldn't open "..out_name)
152 io.close(src)
153 else
154 print("Failed to copy "..in_name.." to "..out_name.."; couldn't open "..in_name)
156 else
157 local f = assert(io.open(in_name, "rb"))
158 local d = f:read("*all")
159 f:close()
160 f = assert(io.open(out_name, "wb"))
161 f:write(d)
162 f:close()
166 FileUtil.forEachFile = function(directory, func)
167 if directory == "" then
168 directory = "."
171 local stream = io.popen(string.format(is_windows and "DIR /B %s" or "ls -1 %s", FileUtil.quoteFile(directory)))
173 if not stream then
174 print("Failed to read directory contents: "..directory)
175 return
178 while true do
179 local filename = stream:read()
180 if not filename then break end
181 filename = directory.."/"..filename
183 if FileUtil.fileExists(filename) then
184 func(filename)
188 io.close(stream)
191 FileUtil.copyDirectoryRecursively = function(src, dest)
192 if os.execute(string.format("cp -r %s %s", src, dest)) ~= 0 then
193 print(string.format("Failed to copy %s to %s", src, dest))
194 assert(false)
197 os.execute(string.format("rm -rf %s/.*", dest))
200 FileUtil.extension = function(filename)
201 local ext = select(3, string.find(filename, "%.([^%s/\\]-)$"))
202 return ext and string.lower(ext) or ""
205 FileUtil.updateSVNRepo = function(url, directory)
206 -- Check for the SVN entries file, which should exist regardless of OS; fileExists doesn't work for directories under Windows.
207 if FileUtil.fileExists(directory.."/.svn/entries") then
208 if os.execute(string.format("svn up -q %s", FileUtil.quoteFile(directory))) ~= 0 then
209 print("Failed to update svn repository: "..directory.." ("..url..")")
211 else
212 -- quoteFile on Windows results in invalid URLs, so just wrap it in quotes and be done with it
213 if os.execute(string.format("svn co -q %s %s", is_windows and "\""..url.."\"" or FileUtil.quoteFile(url), FileUtil.quoteFile(directory))) ~= 0 then
214 print("Failed to up fetch svn repository: "..directory.." ("..url..")")
219 FileUtil.createDirectory = function(directory)
220 if os.execute(string.format(is_windows and "MD %s" or "mkdir -p %s", FileUtil.quoteFile(directory))) ~= 0 then
221 print("Failed to create directory: "..directory)
222 print(string.format(is_windows and "MD %s" or "mkdir -p %s", FileUtil.quoteFile(directory)))
223 os.execute("pwd")
224 os.exit(1)
228 FileUtil.unlinkDirectory = function(directory)
229 if os.execute(string.format(is_windows and "RMDIR /S /Q %s" or "rm -rf %s", FileUtil.quoteFile(directory))) ~= 0 then
230 print("Failed to unlink directory: "..directory)
234 FileUtil.unlinkFile = function(file)
235 if not os.remove(file) then
236 print("Couldn't remove file " .. file)
240 FileUtil.convertImage = function(source, dest)
241 if source ~= dest then
242 if FileUtil.extension(source) == "svg" then
243 -- Because convert doesn't properly render SVG files,
244 -- I'm going to instead use rsvg to render them to some temporary location,
245 -- and then use convert on the temporary file.
246 local temp = os.tmpname()..".png"
247 print(string.format("rsvg -fpng %s %s", FileUtil.quoteFile(source), FileUtil.quoteFile(temp)))
248 if os.execute(string.format("rsvg -fpng %s %s", FileUtil.quoteFile(source), FileUtil.quoteFile(temp))) ~= 0 then
249 print("Failed to convert: "..source)
250 print(tostring(os.execute(string.format("rsvg -fpng %s %s", FileUtil.quoteFile(source), FileUtil.quoteFile(temp))) ~= 0))
251 print(tostring(os.execute(string.format("rsvg -fpng %s %s", FileUtil.quoteFile(source), FileUtil.quoteFile(temp))) ~= 0))
252 print(tostring(os.execute(string.format("rsvg -fpng Development/%s %s", FileUtil.quoteFile(source), FileUtil.quoteFile(temp))) ~= 0))
253 print(tostring(os.execute(string.format("rsvg -fpng %s %s", FileUtil.quoteFile(source), FileUtil.quoteFile(temp))) ~= 0))
254 os.execute("pwd")
255 print("lulz")
256 os.exit(1)
257 else
258 FileUtil.convertImage(temp, dest)
259 FileUtil.unlinkFile(temp)
261 elseif os.execute(string.format("convert -background None %s %s", FileUtil.quoteFile(source), FileUtil.quoteFile(dest))) ~= 0 then
262 print(string.format("convert -background None %s %s", FileUtil.quoteFile(source), FileUtil.quoteFile(dest)))
263 print("Failed to convert: "..source)
264 os.exit(1)
269 FileUtil.createZipArchive = function(directory, archive)
270 if os.execute(string.format("zip -rq9 %s %s", FileUtil.quoteFile(archive), FileUtil.quoteFile(directory))) ~= 0 then
271 print("Failed to create zip archive: "..archive)
275 FileUtil.create7zArchive = function(directory, archive)
276 if os.execute(string.format("7z a -t7z -m0=lzma -mx=9 -mfb=64 -md=32m -ms=on %s %s", FileUtil.quoteFile(archive), FileUtil.quoteFile(directory))) ~= 0 then
277 print("Failed to create 7z archive: "..archive)
281 FileUtil.fileContains = function(filename, text)
282 local rv = os.execute(string.format("grep %s %s", text, filename))
283 return rv == 0