Added rightclick to map button, desaturated it when QuestHelper is hidden, and change...
[QuestHelper.git] / Development / fileutil.lua
blob0fb00b57710aead17236c4eba9ec11fdfd857102
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 local stream = io.popen(string.format("sha1sum %s", FileUtil.quoteFile(filename)))
83 if not stream then
84 print("Failed to calculate hash: "..filename)
85 return nil
86 end
88 local line = stream:read()
89 io.close(stream)
90 if line then
91 return select(3, string.find(line, string.format("^([abcdef%%d]+) %s$", escapeForPattern(filename))))
92 end
93 end
95 FileUtil.fileExists = function(filename)
96 local stream = io.open(FileUtil.fileName(filename), "r")
97 if stream then
98 local exists = stream:read() ~= nil
99 io.close(stream)
100 return exists
102 return false
105 FileUtil.isDirectory = function(filename)
106 local stream = io.popen(string.format(is_windows and "DIR /B /AD %s" or "file -b %s", FileUtil.quoteFile(filename)), "r")
107 if stream then
108 local result = stream:read("*line")
109 io.close(stream)
110 return is_windows and (result ~= "File Not Found") or (result == "directory")
112 error("Failed to execute 'file' command.")
115 -- Extra strings passed to copyFile are pattern/replacement pairs, applied to
116 -- each line of the file being copied.
117 FileUtil.copyFile = function(in_name, out_name, ...)
118 local extra = select("#", ...)
120 if FileUtil.isDirectory(out_name) then
121 -- If out_name is a directory, change it to a filename.
122 out_name = string.format("%s/%s", out_name, select(3, string.find(in_name, "([^/\\]*)$")))
125 if extra > 0 then
126 assert(extra%2==0, "Odd number of arguments.")
127 local src = io.open(in_name, "rb")
128 if src then
129 local dest = io.open(out_name, "wb")
130 if dest then
131 while true do
132 local original = src:read("*line")
133 if not original then break end
134 local eol
135 original, eol = select(3, string.find(original, "^(.-)(\r?)$")) -- Try to keep the CR in CRLF codes intact.
136 local replacement = original
137 for i = 1,extra,2 do
138 local a, b = select(i, ...)
139 replacement = string.gsub(replacement, a, b)
142 -- If we make a line blank, and it wasn't blank before, we omit the line.
143 if original == replacement or replacement ~= "" then
144 dest:write(replacement, eol, "\n")
147 io.close(dest)
148 else
149 print("Failed to copy "..in_name.." to "..out_name.."; couldn't open "..out_name)
151 io.close(src)
152 else
153 print("Failed to copy "..in_name.." to "..out_name.."; couldn't open "..in_name)
155 elseif os.execute(string.format(is_windows and "COPY %s %s" or "cp %s %s", FileUtil.quoteFile(in_name), FileUtil.quoteFile(out_name))) ~= 0 then
156 print("Failed to copy "..in_name.." to "..out_name)
160 FileUtil.forEachFile = function(directory, func)
161 if directory == "" then
162 directory = "."
165 local stream = io.popen(string.format(is_windows and "DIR /B %s" or "ls -1 %s", FileUtil.quoteFile(directory)))
167 if not stream then
168 print("Failed to read directory contents: "..directory)
169 return
172 while true do
173 local filename = stream:read()
174 if not filename then break end
175 filename = directory.."/"..filename
177 if FileUtil.fileExists(filename) then
178 func(filename)
182 io.close(stream)
185 FileUtil.extension = function(filename)
186 local ext = select(3, string.find(filename, "%.([^%s/\\]-)$"))
187 return ext and string.lower(ext) or ""
190 FileUtil.updateSVNRepo = function(url, directory)
191 -- Check for the SVN entries file, which should exist regardless of OS; fileExists doesn't work for directories under Windows.
192 if FileUtil.fileExists(directory.."/.svn/entries") then
193 if os.execute(string.format("svn up -q %s", FileUtil.quoteFile(directory))) ~= 0 then
194 print("Failed to update svn repository: "..directory.." ("..url..")")
196 else
197 -- quoteFile on Windows results in invalid URLs, so just wrap it in quotes and be done with it
198 if os.execute(string.format("svn co -q %s %s", is_windows and "\""..url.."\"" or FileUtil.quoteFile(url), FileUtil.quoteFile(directory))) ~= 0 then
199 print("Failed to up fetch svn repository: "..directory.." ("..url..")")
204 FileUtil.createDirectory = function(directory)
205 if os.execute(string.format(is_windows and "MD %s" or "mkdir -p %s", FileUtil.quoteFile(directory))) ~= 0 then
206 print("Failed to create directory: "..directory)
210 FileUtil.unlinkDirectory = function(directory)
211 if os.execute(string.format(is_windows and "RMDIR /S /Q %s" or "rm -rf %s", FileUtil.quoteFile(directory))) ~= 0 then
212 print("Failed to unlink directory: "..directory)
216 FileUtil.unlinkFile = function(filename)
217 if os.execute(string.format(is_windows and "DEL /Q %s" or "rm -rf %s", FileUtil.quoteFile(filename))) ~= 0 then
218 print("Failed to unlink file: "..filename)
222 FileUtil.convertImage = function(source, dest)
223 if source ~= dest then
224 if FileUtil.extension(source) == "svg" then
225 -- Because convert doesn't properly render SVG files,
226 -- I'm going to instead use rsvg to render them to some temporary location,
227 -- and then use convert on the temporary file.
228 local temp = os.tmpname()..".png"
229 if os.execute(string.format("rsvg -fpng %s %s", FileUtil.quoteFile(source), FileUtil.quoteFile(temp))) ~= 0 then
230 print("Failed to convert: "..source)
231 else
232 FileUtil.convertImage(temp, dest)
233 FileUtil.unlinkFile(temp)
235 elseif os.execute(string.format("convert -background None %s %s", FileUtil.quoteFile(source), FileUtil.quoteFile(dest))) ~= 0 then
236 print("Failed to convert: "..source)
241 FileUtil.createZipArchive = function(directory, archive)
242 if os.execute(string.format("zip -rq9 %s %s", FileUtil.quoteFile(archive), FileUtil.quoteFile(directory))) ~= 0 then
243 print("Failed to create zip archive: "..archive)
247 FileUtil.create7zArchive = function(directory, archive)
248 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
249 print("Failed to create 7z archive: "..archive)