diff options
author | Hisham Muhammad <hisham@gobolinux.org> | 2016-05-22 19:51:45 -0300 |
---|---|---|
committer | Hisham Muhammad <hisham@gobolinux.org> | 2016-05-22 19:51:45 -0300 |
commit | a3650d51e96aef84657cb29e5f52e570fed828c9 (patch) | |
tree | 08a820f19862486b88f39b671803d87f42436abc /src | |
parent | 3f6eda258d5c6aee610b095a20beb12e026e6714 (diff) | |
parent | 69a6aff477fd794b3b594bc7b9f20775b8e07986 (diff) | |
download | luarocks-a3650d51e96aef84657cb29e5f52e570fed828c9.tar.gz luarocks-a3650d51e96aef84657cb29e5f52e570fed828c9.tar.bz2 luarocks-a3650d51e96aef84657cb29e5f52e570fed828c9.zip |
Merge pull request #561 from mpeterv/common-fs-tools-funcs
Move common `luarocks.fs.{unix,win32}.tools` functions into a new module
Diffstat (limited to 'src')
-rw-r--r-- | src/luarocks/fs.lua | 5 | ||||
-rw-r--r-- | src/luarocks/fs/tools.lua | 156 | ||||
-rw-r--r-- | src/luarocks/fs/unix.lua | 7 | ||||
-rw-r--r-- | src/luarocks/fs/unix/tools.lua | 151 | ||||
-rw-r--r-- | src/luarocks/fs/win32.lua | 7 | ||||
-rw-r--r-- | src/luarocks/fs/win32/tools.lua | 170 |
6 files changed, 194 insertions, 302 deletions
diff --git a/src/luarocks/fs.lua b/src/luarocks/fs.lua index 57302c7f..f3d86a13 100644 --- a/src/luarocks/fs.lua +++ b/src/luarocks/fs.lua | |||
@@ -68,7 +68,10 @@ load_fns(fs_lua) | |||
68 | 68 | ||
69 | -- Load platform-specific fallbacks for missing Lua modules | 69 | -- Load platform-specific fallbacks for missing Lua modules |
70 | local ok, fs_plat_tools = pcall(require, "luarocks.fs."..loaded_platform..".tools") | 70 | local ok, fs_plat_tools = pcall(require, "luarocks.fs."..loaded_platform..".tools") |
71 | if ok and fs_plat_tools then load_fns(fs_plat_tools) end | 71 | if ok and fs_plat_tools then |
72 | load_fns(fs_plat_tools) | ||
73 | load_fns(require("luarocks.fs.tools")) | ||
74 | end | ||
72 | 75 | ||
73 | 76 | ||
74 | return fs | 77 | return fs |
diff --git a/src/luarocks/fs/tools.lua b/src/luarocks/fs/tools.lua new file mode 100644 index 00000000..ed51b545 --- /dev/null +++ b/src/luarocks/fs/tools.lua | |||
@@ -0,0 +1,156 @@ | |||
1 | |||
2 | --- Common fs operations implemented with third-party tools. | ||
3 | local tools = {} | ||
4 | |||
5 | local fs = require("luarocks.fs") | ||
6 | local dir = require("luarocks.dir") | ||
7 | local cfg = require("luarocks.cfg") | ||
8 | |||
9 | local vars = cfg.variables | ||
10 | |||
11 | local dir_stack = {} | ||
12 | |||
13 | --- Obtain current directory. | ||
14 | -- Uses the module's internal directory stack. | ||
15 | -- @return string: the absolute pathname of the current directory. | ||
16 | function tools.current_dir() | ||
17 | local current = cfg.cache_pwd | ||
18 | if not current then | ||
19 | local pipe = io.popen(fs.quiet_stderr(fs.Q(vars.PWD))) | ||
20 | current = pipe:read("*l") | ||
21 | pipe:close() | ||
22 | cfg.cache_pwd = current | ||
23 | end | ||
24 | for _, directory in ipairs(dir_stack) do | ||
25 | current = fs.absolute_name(directory, current) | ||
26 | end | ||
27 | return current | ||
28 | end | ||
29 | |||
30 | --- Change the current directory. | ||
31 | -- Uses the module's internal directory stack. This does not have exact | ||
32 | -- semantics of chdir, as it does not handle errors the same way, | ||
33 | -- but works well for our purposes for now. | ||
34 | -- @param directory string: The directory to switch to. | ||
35 | -- @return boolean or (nil, string): true if successful, (nil, error message) if failed. | ||
36 | function tools.change_dir(directory) | ||
37 | assert(type(directory) == "string") | ||
38 | if fs.is_dir(directory) then | ||
39 | table.insert(dir_stack, directory) | ||
40 | return true | ||
41 | end | ||
42 | return nil, "directory not found: "..directory | ||
43 | end | ||
44 | |||
45 | --- Change directory to root. | ||
46 | -- Allows leaving a directory (e.g. for deleting it) in | ||
47 | -- a crossplatform way. | ||
48 | function tools.change_dir_to_root() | ||
49 | table.insert(dir_stack, "/") | ||
50 | end | ||
51 | |||
52 | --- Change working directory to the previous in the directory stack. | ||
53 | function tools.pop_dir() | ||
54 | local directory = table.remove(dir_stack) | ||
55 | return directory ~= nil | ||
56 | end | ||
57 | |||
58 | --- Run the given command. | ||
59 | -- The command is executed in the current directory in the directory stack. | ||
60 | -- @param cmd string: No quoting/escaping is applied to the command. | ||
61 | -- @return boolean: true if command succeeds (status code 0), false | ||
62 | -- otherwise. | ||
63 | function tools.execute_string(cmd) | ||
64 | local current = fs.current_dir() | ||
65 | if not current then return false end | ||
66 | cmd = fs.command_at(current, cmd) | ||
67 | local code = os.execute(cmd) | ||
68 | if code == 0 or code == true then | ||
69 | return true | ||
70 | else | ||
71 | return false | ||
72 | end | ||
73 | end | ||
74 | |||
75 | --- Internal implementation function for fs.dir. | ||
76 | -- Yields a filename on each iteration. | ||
77 | -- @param at string: directory to list | ||
78 | -- @return nil | ||
79 | function tools.dir_iterator(at) | ||
80 | local pipe = io.popen(fs.command_at(at, fs.Q(vars.LS))) | ||
81 | for file in pipe:lines() do | ||
82 | if file ~= "." and file ~= ".." then | ||
83 | coroutine.yield(file) | ||
84 | end | ||
85 | end | ||
86 | pipe:close() | ||
87 | end | ||
88 | |||
89 | --- Download a remote file. | ||
90 | -- @param url string: URL to be fetched. | ||
91 | -- @param filename string or nil: this function attempts to detect the | ||
92 | -- resulting local filename of the remote file as the basename of the URL; | ||
93 | -- if that is not correct (due to a redirection, for example), the local | ||
94 | -- filename can be given explicitly as this second argument. | ||
95 | -- @return (boolean, string): true and the filename on success, | ||
96 | -- false and the error message on failure. | ||
97 | function tools.use_downloader(url, filename, cache) | ||
98 | assert(type(url) == "string") | ||
99 | assert(type(filename) == "string" or not filename) | ||
100 | |||
101 | filename = fs.absolute_name(filename or dir.base_name(url)) | ||
102 | |||
103 | local ok | ||
104 | if cfg.downloader == "wget" then | ||
105 | local wget_cmd = fs.Q(vars.WGET).." "..vars.WGETNOCERTFLAG.." --no-cache --user-agent=\""..cfg.user_agent.." via wget\" --quiet " | ||
106 | if cfg.connection_timeout and cfg.connection_timeout > 0 then | ||
107 | wget_cmd = wget_cmd .. "--timeout="..tonumber(cfg.connection_timeout).." --tries=1 " | ||
108 | end | ||
109 | if cache then | ||
110 | -- --timestamping is incompatible with --output-document, | ||
111 | -- but that's not a problem for our use cases. | ||
112 | fs.change_dir(dir.dir_name(filename)) | ||
113 | ok = fs.execute_quiet(wget_cmd.." --timestamping ", url) | ||
114 | fs.pop_dir() | ||
115 | elseif filename then | ||
116 | ok = fs.execute_quiet(wget_cmd.." --output-document ", filename, url) | ||
117 | else | ||
118 | ok = fs.execute_quiet(wget_cmd, url) | ||
119 | end | ||
120 | elseif cfg.downloader == "curl" then | ||
121 | local curl_cmd = fs.Q(vars.CURL).." "..vars.CURLNOCERTFLAG.." -f -L --user-agent \""..cfg.user_agent.." via curl\" " | ||
122 | if cfg.connection_timeout and cfg.connection_timeout > 0 then | ||
123 | curl_cmd = curl_cmd .. "--connect-timeout "..tonumber(cfg.connection_timeout).." " | ||
124 | end | ||
125 | ok = fs.execute_string(fs.quiet_stderr(curl_cmd..fs.Q(url).." > "..fs.Q(filename))) | ||
126 | end | ||
127 | if ok then | ||
128 | return true, filename | ||
129 | else | ||
130 | return false | ||
131 | end | ||
132 | end | ||
133 | |||
134 | local md5_cmd = { | ||
135 | md5sum = fs.Q(vars.MD5SUM), | ||
136 | openssl = fs.Q(vars.OPENSSL).." md5", | ||
137 | md5 = fs.Q(vars.MD5), | ||
138 | } | ||
139 | |||
140 | --- Get the MD5 checksum for a file. | ||
141 | -- @param file string: The file to be computed. | ||
142 | -- @return string: The MD5 checksum or nil + message | ||
143 | function tools.get_md5(file) | ||
144 | local cmd = md5_cmd[cfg.md5checker] | ||
145 | if not cmd then return nil, "no MD5 checker command configured" end | ||
146 | local pipe = io.popen(cmd.." "..fs.Q(fs.absolute_name(file))) | ||
147 | local computed = pipe:read("*a") | ||
148 | pipe:close() | ||
149 | if computed then | ||
150 | computed = computed:match("("..("%x"):rep(32)..")") | ||
151 | end | ||
152 | if computed then return computed end | ||
153 | return nil, "Failed to compute MD5 hash for file "..tostring(fs.absolute_name(file)) | ||
154 | end | ||
155 | |||
156 | return tools | ||
diff --git a/src/luarocks/fs/unix.lua b/src/luarocks/fs/unix.lua index e2cc825b..520b3e99 100644 --- a/src/luarocks/fs/unix.lua +++ b/src/luarocks/fs/unix.lua | |||
@@ -16,6 +16,13 @@ function unix.quiet(cmd) | |||
16 | return cmd.." 1> /dev/null 2> /dev/null" | 16 | return cmd.." 1> /dev/null 2> /dev/null" |
17 | end | 17 | end |
18 | 18 | ||
19 | --- Annotate command string for execution with quiet stderr. | ||
20 | -- @param cmd string: A command-line string. | ||
21 | -- @return string: The command-line, with stderr silencing annotation. | ||
22 | function unix.quiet_stderr(cmd) | ||
23 | return cmd.." 2> /dev/null" | ||
24 | end | ||
25 | |||
19 | --- Return an absolute pathname from a potentially relative one. | 26 | --- Return an absolute pathname from a potentially relative one. |
20 | -- @param pathname string: pathname to convert. | 27 | -- @param pathname string: pathname to convert. |
21 | -- @param relative_to string or nil: path to prepend when making | 28 | -- @param relative_to string or nil: path to prepend when making |
diff --git a/src/luarocks/fs/unix/tools.lua b/src/luarocks/fs/unix/tools.lua index ab55897e..84bd53fd 100644 --- a/src/luarocks/fs/unix/tools.lua +++ b/src/luarocks/fs/unix/tools.lua | |||
@@ -7,74 +7,16 @@ local fs = require("luarocks.fs") | |||
7 | local dir = require("luarocks.dir") | 7 | local dir = require("luarocks.dir") |
8 | local cfg = require("luarocks.cfg") | 8 | local cfg = require("luarocks.cfg") |
9 | 9 | ||
10 | local dir_stack = {} | ||
11 | |||
12 | local vars = cfg.variables | 10 | local vars = cfg.variables |
13 | 11 | ||
14 | local function command_at(directory, cmd) | 12 | --- Adds prefix to command to make it run from a directory. |
13 | -- @param directory string: Path to a directory. | ||
14 | -- @param cmd string: A command-line string. | ||
15 | -- @return string: The command-line with prefix. | ||
16 | function tools.command_at(directory, cmd) | ||
15 | return "cd " .. fs.Q(fs.absolute_name(directory)) .. " && " .. cmd | 17 | return "cd " .. fs.Q(fs.absolute_name(directory)) .. " && " .. cmd |
16 | end | 18 | end |
17 | 19 | ||
18 | --- Obtain current directory. | ||
19 | -- Uses the module's internal directory stack. | ||
20 | -- @return string: the absolute pathname of the current directory. | ||
21 | function tools.current_dir() | ||
22 | local current = cfg.cache_pwd | ||
23 | if not current then | ||
24 | local pipe = io.popen(fs.Q(vars.PWD).." 2> /dev/null") | ||
25 | current = pipe:read("*l") | ||
26 | pipe:close() | ||
27 | cfg.cache_pwd = current | ||
28 | end | ||
29 | for _, directory in ipairs(dir_stack) do | ||
30 | current = fs.absolute_name(directory, current) | ||
31 | end | ||
32 | return current | ||
33 | end | ||
34 | |||
35 | --- Run the given command. | ||
36 | -- The command is executed in the current directory in the directory stack. | ||
37 | -- @param cmd string: No quoting/escaping is applied to the command. | ||
38 | -- @return boolean: true if command succeeds (status code 0), false | ||
39 | -- otherwise. | ||
40 | function tools.execute_string(cmd) | ||
41 | local current = fs.current_dir() | ||
42 | if not current then return false end | ||
43 | local code, err = os.execute(command_at(current, cmd)) | ||
44 | if code == 0 or code == true then | ||
45 | return true | ||
46 | else | ||
47 | return false | ||
48 | end | ||
49 | end | ||
50 | |||
51 | --- Change the current directory. | ||
52 | -- Uses the module's internal directory stack. This does not have exact | ||
53 | -- semantics of chdir, as it does not handle errors the same way, | ||
54 | -- but works well for our purposes for now. | ||
55 | -- @param directory string: The directory to switch to. | ||
56 | function tools.change_dir(directory) | ||
57 | assert(type(directory) == "string") | ||
58 | if fs.is_dir(directory) then | ||
59 | table.insert(dir_stack, directory) | ||
60 | return true | ||
61 | end | ||
62 | return nil, "directory not found: "..directory | ||
63 | end | ||
64 | |||
65 | --- Change directory to root. | ||
66 | -- Allows leaving a directory (e.g. for deleting it) in | ||
67 | -- a crossplatform way. | ||
68 | function tools.change_dir_to_root() | ||
69 | table.insert(dir_stack, "/") | ||
70 | end | ||
71 | |||
72 | --- Change working directory to the previous in the directory stack. | ||
73 | function tools.pop_dir() | ||
74 | local directory = table.remove(dir_stack) | ||
75 | return directory ~= nil | ||
76 | end | ||
77 | |||
78 | --- Create a directory if it does not already exist. | 20 | --- Create a directory if it does not already exist. |
79 | -- If any of the higher levels in the path name does not exist | 21 | -- If any of the higher levels in the path name does not exist |
80 | -- too, they are created as well. | 22 | -- too, they are created as well. |
@@ -155,20 +97,6 @@ function tools.delete(arg) | |||
155 | fs.execute_quiet(vars.RM, "-rf", arg) | 97 | fs.execute_quiet(vars.RM, "-rf", arg) |
156 | end | 98 | end |
157 | 99 | ||
158 | --- Internal implementation function for fs.dir. | ||
159 | -- Yields a filename on each iteration. | ||
160 | -- @param at string: directory to list | ||
161 | -- @return nil | ||
162 | function tools.dir_iterator(at) | ||
163 | local pipe = io.popen(command_at(at, vars.LS)) | ||
164 | for file in pipe:lines() do | ||
165 | if file ~= "." and file ~= ".." then | ||
166 | coroutine.yield(file) | ||
167 | end | ||
168 | end | ||
169 | pipe:close() | ||
170 | end | ||
171 | |||
172 | --- Recursively scan the contents of a directory. | 100 | --- Recursively scan the contents of a directory. |
173 | -- @param at string or nil: directory to scan (will be the current | 101 | -- @param at string or nil: directory to scan (will be the current |
174 | -- directory if none is given). | 102 | -- directory if none is given). |
@@ -183,7 +111,7 @@ function tools.find(at) | |||
183 | return {} | 111 | return {} |
184 | end | 112 | end |
185 | local result = {} | 113 | local result = {} |
186 | local pipe = io.popen(command_at(at, vars.FIND.." * 2>/dev/null")) | 114 | local pipe = io.popen(fs.command_at(at, fs.quiet_stderr(vars.FIND.." *"))) |
187 | for file in pipe:lines() do | 115 | for file in pipe:lines() do |
188 | table.insert(result, file) | 116 | table.insert(result, file) |
189 | end | 117 | end |
@@ -232,51 +160,6 @@ function tools.is_file(file) | |||
232 | return fs.execute(vars.TEST, "-f", file) | 160 | return fs.execute(vars.TEST, "-f", file) |
233 | end | 161 | end |
234 | 162 | ||
235 | --- Download a remote file. | ||
236 | -- @param url string: URL to be fetched. | ||
237 | -- @param filename string or nil: this function attempts to detect the | ||
238 | -- resulting local filename of the remote file as the basename of the URL; | ||
239 | -- if that is not correct (due to a redirection, for example), the local | ||
240 | -- filename can be given explicitly as this second argument. | ||
241 | -- @return (boolean, string): true and the filename on success, | ||
242 | -- false and the error message on failure. | ||
243 | function tools.use_downloader(url, filename, cache) | ||
244 | assert(type(url) == "string") | ||
245 | assert(type(filename) == "string" or not filename) | ||
246 | |||
247 | filename = fs.absolute_name(filename or dir.base_name(url)) | ||
248 | |||
249 | local ok | ||
250 | if cfg.downloader == "wget" then | ||
251 | local wget_cmd = fs.Q(vars.WGET).." "..vars.WGETNOCERTFLAG.." --no-cache --user-agent='"..cfg.user_agent.." via wget' --quiet " | ||
252 | if cfg.connection_timeout and cfg.connection_timeout > 0 then | ||
253 | wget_cmd = wget_cmd .. "--timeout="..tonumber(cfg.connection_timeout).." --tries=1 " | ||
254 | end | ||
255 | if cache then | ||
256 | -- --timestamping is incompatible with --output-document, | ||
257 | -- but that's not a problem for our use cases. | ||
258 | fs.change_dir(dir.dir_name(filename)) | ||
259 | ok = fs.execute_quiet(wget_cmd.." --timestamping ", url) | ||
260 | fs.pop_dir() | ||
261 | elseif filename then | ||
262 | ok = fs.execute_quiet(wget_cmd.." --output-document ", filename, url) | ||
263 | else | ||
264 | ok = fs.execute_quiet(wget_cmd, url) | ||
265 | end | ||
266 | elseif cfg.downloader == "curl" then | ||
267 | local curl_cmd = fs.Q(vars.CURL).." "..vars.CURLNOCERTFLAG.." -f -L --user-agent '"..cfg.user_agent.." via curl' " | ||
268 | if cfg.connection_timeout and cfg.connection_timeout > 0 then | ||
269 | curl_cmd = curl_cmd .. "--connect-timeout "..tonumber(cfg.connection_timeout).." " | ||
270 | end | ||
271 | ok = fs.execute_string(curl_cmd..fs.Q(url).." 2> /dev/null 1> "..fs.Q(filename)) | ||
272 | end | ||
273 | if ok then | ||
274 | return true, filename | ||
275 | else | ||
276 | return false | ||
277 | end | ||
278 | end | ||
279 | |||
280 | function tools.chmod(pathname, mode) | 163 | function tools.chmod(pathname, mode) |
281 | if mode then | 164 | if mode then |
282 | return fs.execute(vars.CHMOD, mode, pathname) | 165 | return fs.execute(vars.CHMOD, mode, pathname) |
@@ -319,28 +202,6 @@ function tools.unpack_archive(archive) | |||
319 | return true | 202 | return true |
320 | end | 203 | end |
321 | 204 | ||
322 | local md5_cmd = { | ||
323 | md5sum = vars.MD5SUM, | ||
324 | openssl = vars.OPENSSL.." md5", | ||
325 | md5 = vars.MD5, | ||
326 | } | ||
327 | |||
328 | --- Get the MD5 checksum for a file. | ||
329 | -- @param file string: The file to be computed. | ||
330 | -- @return string: The MD5 checksum | ||
331 | function tools.get_md5(file) | ||
332 | local cmd = md5_cmd[cfg.md5checker] | ||
333 | if not cmd then return nil, "no MD5 checker command configured" end | ||
334 | local pipe = io.popen(cmd.." "..fs.Q(fs.absolute_name(file))) | ||
335 | local computed = pipe:read("*a") | ||
336 | pipe:close() | ||
337 | if computed then | ||
338 | computed = computed:match("("..("%x"):rep(32)..")") | ||
339 | end | ||
340 | if computed then return computed end | ||
341 | return nil, "Failed to compute MD5 hash for file "..tostring(fs.absolute_name(file)) | ||
342 | end | ||
343 | |||
344 | function tools.get_permissions(filename) | 205 | function tools.get_permissions(filename) |
345 | local pipe = io.popen(vars.STAT.." "..vars.STATFLAG.." "..fs.Q(filename)) | 206 | local pipe = io.popen(vars.STAT.." "..vars.STATFLAG.." "..fs.Q(filename)) |
346 | local ret = pipe:read("*l") | 207 | local ret = pipe:read("*l") |
diff --git a/src/luarocks/fs/win32.lua b/src/luarocks/fs/win32.lua index c14c421b..74f3ed69 100644 --- a/src/luarocks/fs/win32.lua +++ b/src/luarocks/fs/win32.lua | |||
@@ -27,6 +27,13 @@ function win32.quiet(cmd) | |||
27 | return cmd.." 2> NUL 1> NUL" | 27 | return cmd.." 2> NUL 1> NUL" |
28 | end | 28 | end |
29 | 29 | ||
30 | --- Annotate command string for execution with quiet stderr. | ||
31 | -- @param cmd string: A command-line string. | ||
32 | -- @return string: The command-line, with stderr silencing annotation. | ||
33 | function win32.quiet_stderr(cmd) | ||
34 | return cmd.." 2> NUL" | ||
35 | end | ||
36 | |||
30 | local drive_letter = "[%.a-zA-Z]?:?[\\/]" | 37 | local drive_letter = "[%.a-zA-Z]?:?[\\/]" |
31 | 38 | ||
32 | local win_escape_chars = { | 39 | local win_escape_chars = { |
diff --git a/src/luarocks/fs/win32/tools.lua b/src/luarocks/fs/win32/tools.lua index b9dce85c..9cb6d47a 100644 --- a/src/luarocks/fs/win32/tools.lua +++ b/src/luarocks/fs/win32/tools.lua | |||
@@ -9,22 +9,13 @@ local fs = require("luarocks.fs") | |||
9 | local dir = require("luarocks.dir") | 9 | local dir = require("luarocks.dir") |
10 | local cfg = require("luarocks.cfg") | 10 | local cfg = require("luarocks.cfg") |
11 | 11 | ||
12 | local dir_stack = {} | ||
13 | |||
14 | local vars = cfg.variables | 12 | local vars = cfg.variables |
15 | 13 | ||
16 | --- Strip the last extension of a filename. | 14 | --- Adds prefix to command to make it run from a directory. |
17 | -- Example: "foo.tar.gz" becomes "foo.tar". | 15 | -- @param directory string: Path to a directory. |
18 | -- If filename has no dots, returns it unchanged. | 16 | -- @param cmd string: A command-line string. |
19 | -- @param filename string: The file name to strip. | 17 | -- @return string: The command-line with prefix. |
20 | -- @return string: The stripped name. | 18 | function tools.command_at(directory, cmd) |
21 | local function strip_extension(filename) | ||
22 | assert(type(filename) == "string") | ||
23 | |||
24 | return (filename:gsub("%.[^.]+$", "")) or filename | ||
25 | end | ||
26 | |||
27 | local function command_at(directory, cmd) | ||
28 | local drive = directory:match("^([A-Za-z]:)") | 19 | local drive = directory:match("^([A-Za-z]:)") |
29 | cmd = "cd " .. fs.Q(directory) .. " & " .. cmd | 20 | cmd = "cd " .. fs.Q(directory) .. " & " .. cmd |
30 | if drive then | 21 | if drive then |
@@ -33,68 +24,6 @@ local function command_at(directory, cmd) | |||
33 | return cmd | 24 | return cmd |
34 | end | 25 | end |
35 | 26 | ||
36 | --- Obtain current directory. | ||
37 | -- Uses the module's internal directory stack. | ||
38 | -- @return string: the absolute pathname of the current directory. | ||
39 | function tools.current_dir() | ||
40 | local current = cfg.cache_pwd | ||
41 | if not current then | ||
42 | local pipe = io.popen(fs.Q(vars.PWD).. " 2> NUL") | ||
43 | current = pipe:read("*l") | ||
44 | pipe:close() | ||
45 | cfg.cache_pwd = current | ||
46 | end | ||
47 | for _, directory in ipairs(dir_stack) do | ||
48 | current = fs.absolute_name(directory, current) | ||
49 | end | ||
50 | return current | ||
51 | end | ||
52 | |||
53 | --- Run the given command. | ||
54 | -- The command is executed in the current directory in the directory stack. | ||
55 | -- @param cmd string: No quoting/escaping is applied to the command. | ||
56 | -- @return boolean: true if command succeeds (status code 0), false | ||
57 | -- otherwise. | ||
58 | function tools.execute_string(cmd) | ||
59 | local current = fs.current_dir() | ||
60 | if not current then return false end | ||
61 | cmd = command_at(current, cmd) | ||
62 | local code = os.execute(cmd) | ||
63 | if code == 0 or code == true then | ||
64 | return true | ||
65 | else | ||
66 | return false | ||
67 | end | ||
68 | end | ||
69 | |||
70 | --- Change the current directory. | ||
71 | -- Uses the module's internal directory stack. This does not have exact | ||
72 | -- semantics of chdir, as it does not handle errors the same way, | ||
73 | -- but works well for our purposes for now. | ||
74 | -- @param directory string: The directory to switch to. | ||
75 | -- @return boolean or (nil, string): true if successful, (nil, error message) if failed. | ||
76 | function tools.change_dir(directory) | ||
77 | assert(type(directory) == "string") | ||
78 | if fs.is_dir(directory) then | ||
79 | table.insert(dir_stack, directory) | ||
80 | return true | ||
81 | end | ||
82 | return nil, "directory not found: "..directory | ||
83 | end | ||
84 | |||
85 | --- Change directory to root. | ||
86 | -- Allows leaving a directory (e.g. for deleting it) in | ||
87 | -- a crossplatform way. | ||
88 | function tools.change_dir_to_root() | ||
89 | table.insert(dir_stack, "/") | ||
90 | end | ||
91 | |||
92 | --- Change working directory to the previous in the directory stack. | ||
93 | function tools.pop_dir() | ||
94 | local directory = table.remove(dir_stack) | ||
95 | return directory ~= nil | ||
96 | end | ||
97 | |||
98 | --- Create a directory if it does not already exist. | 27 | --- Create a directory if it does not already exist. |
99 | -- If any of the higher levels in the path name does not exist | 28 | -- If any of the higher levels in the path name does not exist |
100 | -- too, they are created as well. | 29 | -- too, they are created as well. |
@@ -168,20 +97,6 @@ function tools.delete(arg) | |||
168 | fs.execute_quiet("if exist "..fs.Q(arg.."\\").." ( RMDIR /S /Q "..fs.Q(arg).." ) else ( DEL /Q /F "..fs.Q(arg).." )") | 97 | fs.execute_quiet("if exist "..fs.Q(arg.."\\").." ( RMDIR /S /Q "..fs.Q(arg).." ) else ( DEL /Q /F "..fs.Q(arg).." )") |
169 | end | 98 | end |
170 | 99 | ||
171 | --- Internal implementation function for fs.dir. | ||
172 | -- Yields a filename on each iteration. | ||
173 | -- @param at string: directory to list | ||
174 | -- @return nil | ||
175 | function tools.dir_iterator(at) | ||
176 | local pipe = io.popen(command_at(at, fs.Q(vars.LS))) | ||
177 | for file in pipe:lines() do | ||
178 | if file ~= "." and file ~= ".." then | ||
179 | coroutine.yield(file) | ||
180 | end | ||
181 | end | ||
182 | pipe:close() | ||
183 | end | ||
184 | |||
185 | --- Recursively scan the contents of a directory. | 100 | --- Recursively scan the contents of a directory. |
186 | -- @param at string or nil: directory to scan (will be the current | 101 | -- @param at string or nil: directory to scan (will be the current |
187 | -- directory if none is given). | 102 | -- directory if none is given). |
@@ -196,7 +111,7 @@ function tools.find(at) | |||
196 | return {} | 111 | return {} |
197 | end | 112 | end |
198 | local result = {} | 113 | local result = {} |
199 | local pipe = io.popen(command_at(at, fs.Q(vars.FIND).." 2> NUL")) | 114 | local pipe = io.popen(fs.command_at(at, fs.quiet_stderr(fs.Q(vars.FIND)))) |
200 | for file in pipe:lines() do | 115 | for file in pipe:lines() do |
201 | -- Windows find is a bit different | 116 | -- Windows find is a bit different |
202 | local first_two = file:sub(1,2) | 117 | local first_two = file:sub(1,2) |
@@ -242,49 +157,14 @@ function tools.is_file(file) | |||
242 | return fs.execute(fs.Q(vars.TEST).." -f", file) | 157 | return fs.execute(fs.Q(vars.TEST).." -f", file) |
243 | end | 158 | end |
244 | 159 | ||
245 | --- Download a remote file. | 160 | --- Strip the last extension of a filename. |
246 | -- @param url string: URL to be fetched. | 161 | -- Example: "foo.tar.gz" becomes "foo.tar". |
247 | -- @param filename string or nil: this function attempts to detect the | 162 | -- If filename has no dots, returns it unchanged. |
248 | -- resulting local filename of the remote file as the basename of the URL; | 163 | -- @param filename string: The file name to strip. |
249 | -- if that is not correct (due to a redirection, for example), the local | 164 | -- @return string: The stripped name. |
250 | -- filename can be given explicitly as this second argument. | 165 | local function strip_extension(filename) |
251 | -- @return (boolean, string): true and the filename on success, | 166 | assert(type(filename) == "string") |
252 | -- false and the error message on failure. | 167 | return (filename:gsub("%.[^.]+$", "")) or filename |
253 | function tools.use_downloader(url, filename, cache) | ||
254 | assert(type(url) == "string") | ||
255 | assert(type(filename) == "string" or not filename) | ||
256 | |||
257 | filename = fs.absolute_name(filename or dir.base_name(url)) | ||
258 | |||
259 | local ok | ||
260 | if cfg.downloader == "wget" then | ||
261 | local wget_cmd = fs.Q(vars.WGET).." "..vars.WGETNOCERTFLAG.." --no-cache --user-agent=\""..cfg.user_agent.." via wget\" --quiet " | ||
262 | if cfg.connection_timeout and cfg.connection_timeout > 0 then | ||
263 | wget_cmd = wget_cmd .. "--timeout="..tonumber(cfg.connection_timeout).." --tries=1 " | ||
264 | end | ||
265 | if cache then | ||
266 | -- --timestamping is incompatible with --output-document, | ||
267 | -- but that's not a problem for our use cases. | ||
268 | fs.change_dir(dir.dir_name(filename)) | ||
269 | ok = fs.execute_quiet(wget_cmd.." --timestamping ", url) | ||
270 | fs.pop_dir() | ||
271 | elseif filename then | ||
272 | ok = fs.execute_quiet(wget_cmd.." --output-document ", filename, url) | ||
273 | else | ||
274 | ok = fs.execute_quiet(wget_cmd, url) | ||
275 | end | ||
276 | elseif cfg.downloader == "curl" then | ||
277 | local curl_cmd = fs.Q(vars.CURL).." "..vars.CURLNOCERTFLAG.." -f -L --user-agent \""..cfg.user_agent.." via curl\" " | ||
278 | if cfg.connection_timeout and cfg.connection_timeout > 0 then | ||
279 | curl_cmd = curl_cmd .. "--connect-timeout "..tonumber(cfg.connection_timeout).." " | ||
280 | end | ||
281 | ok = fs.execute_string(curl_cmd..fs.Q(url).." 2> NUL 1> "..fs.Q(filename)) | ||
282 | end | ||
283 | if ok then | ||
284 | return true, filename | ||
285 | else | ||
286 | return false | ||
287 | end | ||
288 | end | 168 | end |
289 | 169 | ||
290 | --- Uncompress gzip file. | 170 | --- Uncompress gzip file. |
@@ -334,28 +214,6 @@ function tools.unpack_archive(archive) | |||
334 | return true | 214 | return true |
335 | end | 215 | end |
336 | 216 | ||
337 | local md5_cmd = { | ||
338 | md5sum = fs.Q(vars.MD5SUM), | ||
339 | openssl = fs.Q(vars.OPENSSL).." md5", | ||
340 | md5 = fs.Q(vars.MD5), | ||
341 | } | ||
342 | |||
343 | --- Get the MD5 checksum for a file. | ||
344 | -- @param file string: The file to be computed. | ||
345 | -- @return string: The MD5 checksum or nil + message | ||
346 | function tools.get_md5(file) | ||
347 | local cmd = md5_cmd[cfg.md5checker] | ||
348 | if not cmd then return nil, "no MD5 checker command configured" end | ||
349 | local pipe = io.popen(cmd.." "..fs.Q(fs.absolute_name(file))) | ||
350 | local computed = pipe:read("*a") | ||
351 | pipe:close() | ||
352 | if computed then | ||
353 | computed = computed:match("("..("%x"):rep(32)..")") | ||
354 | end | ||
355 | if computed then return computed end | ||
356 | return nil, "Failed to compute MD5 hash for file "..tostring(fs.absolute_name(file)) | ||
357 | end | ||
358 | |||
359 | --- Test for existance of a file. | 217 | --- Test for existance of a file. |
360 | -- @param file string: filename to test | 218 | -- @param file string: filename to test |
361 | -- @return boolean: true if file exists, false otherwise. | 219 | -- @return boolean: true if file exists, false otherwise. |