Tamar 0.7.2 → 0.7.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,132 @@
1
+ --- LuaDist simple persistance functions
2
+ -- Peter Drahoš, LuaDist Project, 2010
3
+ -- Original Code borrowed from LuaRocks Project
4
+
5
+ --- Persistency table serializarion.
6
+ --- 2DO: If a better persistency dist with good readability becomes available change this code
7
+ -- This module contains functions that deal with serialization and loading of tables.
8
+ -- loadText - text 2 table
9
+ -- load - file 2 table
10
+ -- saveText - table 2 text
11
+ -- save - table 2 file
12
+ -- saveManifest - variant of save for manifests
13
+
14
+ module ("dist.persist", package.seeall)
15
+
16
+ local sys = require "dist.sys"
17
+
18
+ --- Serialize a table into text.
19
+ -- @param tbl table: Table to serialize.
20
+ -- @param d number: Intendation lenght.
21
+ -- @return out string: Serialized text.
22
+ local function serialize(o, d)
23
+ local out = ""
24
+ if not d then d = 0 end
25
+
26
+ if type(o) == "number" then
27
+ out = out .. tostring(o)
28
+ elseif type(o) == "table" then
29
+ out = out .."{\n"
30
+ for k,v in pairs(o) do
31
+ for f = 1,d do out = out .."\t" end
32
+
33
+ if type(k) ~="number" then
34
+ out = out .."\t['" ..tostring(k) .."'] = "
35
+ end
36
+
37
+ for f = 1,d do out = out .."\t" end
38
+ out = out .. serialize(v, d + 1)
39
+ if type(v) ~= "table" then out = out ..",\n" end
40
+ end
41
+ for f = 1,d do out = out .."\t" end
42
+ out = out .."},\n"
43
+ else
44
+ out = out .. '[[' .. tostring(o) .. ']]'
45
+ end
46
+ return out
47
+ end
48
+
49
+ --- Load table from text.
50
+ -- @param text string: Text to load table from.
51
+ -- @return table, log: Returns table on success, nil on failure and log message.
52
+ function loadText(text)
53
+ assert(type(text) == "string", "persist.loadText: Argument 'text' is not a string.")
54
+
55
+ local chunk, err = loadstring(text)
56
+ if not chunk then return false, "Failed to parse text " .. err end
57
+
58
+ local result = {}
59
+
60
+ setfenv(chunk, result)
61
+ local ok, ret, err = pcall(chunk)
62
+ if not ok then return false, ret end
63
+ return ret or result, "Sucessfully loaded table from text"
64
+ end
65
+
66
+ --- Load table from file.
67
+ -- @param filename string: File to load table from
68
+ -- @return table, log: Returns table on success, nil on failure and log message.
69
+ function load(filename)
70
+ assert(type(filename) == "string", "persist.load: Argument 'filename' is not a string.")
71
+
72
+ local chunk, err = loadfile(filename)
73
+ if not chunk then return false, "Cannot load from file " .. filename end
74
+
75
+ local result = {}
76
+
77
+ setfenv(chunk, result)
78
+ local ok, ret, err = pcall(chunk)
79
+ if not ok then return false, ret end
80
+ return ret or result, "Sucessfully loaded table from file " .. filename
81
+ end
82
+
83
+ --- Save table to string. Used for dist.info.
84
+ -- @param tbl table: Table to save.
85
+ -- @return out string: Serialized text.
86
+ function saveText(tbl)
87
+ assert(type(tbl) == "table", "persist.save: Argument 'tbl' is not a table.")
88
+
89
+ local out = ""
90
+ for k, v in pairs(tbl) do
91
+ -- Small fix for non alphanumeric strings (i know it looks ugly)
92
+ if not k:match("[^%w_]*$") then k = "_G['" .. k .. "']" end
93
+ if type(v) == 'table' then
94
+ -- little trick so top-level table won't have commas, but sub-tables will have
95
+ out = out .. k .. " = " .. serialize(v):gsub(',\n$', '\n') .."\n"
96
+ else
97
+ out = out .. k ..' = "' .. tostring(v):gsub('"','\\"') ..'"\n'
98
+ end
99
+ end
100
+ return out
101
+ end
102
+
103
+ --- Special serialization formating for manifests.
104
+ -- @param filename string: Path to save manifest to
105
+ -- @return ok, log: Returns true on success, nil on failure and log message.
106
+ function saveManifest(filename, dists)
107
+ assert(type(filename) == "string", "persist.saveManifest: Argument 'filename' is not a string.")
108
+ assert(type(dists) == "table", "persist.saveManifest: Argument 'dists' is not a table.")
109
+
110
+ local out = io.open(filename, "w")
111
+ if not out then return false, "Cannot write to file " .. filename end
112
+
113
+ out:write("return ");
114
+ out:write(serialize(dists).."true")
115
+ out:close()
116
+ return true, "Successfully saved manifest to " .. filename
117
+ end
118
+
119
+ --- Save table to file.
120
+ -- @param filename string: File to save table to.
121
+ -- @param tbl table: Table to save.
122
+ -- @return ok, log: Returns true on success, nil on failure and log message.
123
+ function save(filename, tbl)
124
+ assert(type(filename) == "string", "persist.save: Argument 'filename' is not a string.")
125
+ assert(type(tbl) == "table", "persist.save: Argument 'tbl' is not a table.")
126
+
127
+ local out = io.open(filename, "w")
128
+ if not out then return false, "Cannot write to file" .. filename end
129
+ out:write(saveText(tbl) or "")
130
+ out:close()
131
+ return true, "Successfully saved table to " .. filename
132
+ end
@@ -0,0 +1,436 @@
1
+ --- LuaDist UNIX and Windows system functions
2
+ -- Peter Drahoš, LuaDist Project, 2010
3
+ -- Original Code contributed to and then borrowed back from the LuaRocks Project
4
+
5
+ --- Host system dependent commands.
6
+ -- Override the default UNIX commands if needed for your platform.
7
+ -- Commands currently depend on popen and unzip being available. For future releases we would like
8
+ -- to use lua packages handling the compression issues. Additionally some filesystem functionality
9
+ -- not available in luafilesystem is emulated here.
10
+
11
+ module ("dist.sys", package.seeall)
12
+
13
+ local log = require "dist.log"
14
+ local config = require "dist.config"
15
+ local lfs = require "lfs"
16
+
17
+ --- Quote argument for shell processing.
18
+ -- Adds single quotes and escapes.
19
+ -- @param arg string: Unquoted argument.
20
+ -- @return string: Quoted argument.
21
+ function Q(arg)
22
+ assert(type(arg) == "string", "Q argument 'arg' is not a string.")
23
+
24
+ return "'" .. arg:gsub("\\", "\\\\"):gsub("'", "'\\''") .. "'"
25
+ end
26
+
27
+ --- Compose full system path or part of url
28
+ -- @args string: Path components
29
+ -- @return string: Path string
30
+ function path(...)
31
+ local args = ...
32
+ --if not args then return curDir() end
33
+ if type(...) == "string" then args = { ... } end
34
+ if type(args) ~= "table" then return nil end
35
+
36
+ if args[1]:match("^[%a:]*[/\\]") then
37
+ return table.concat(args,"/")
38
+ end
39
+ return curDir() .. "/" .. table.concat(args,"/")
40
+ end
41
+
42
+ --- Run the given system command.
43
+ -- The command is executed in the current directory in the dir stack.
44
+ -- @param cmd string: No quoting/escaping is applied to the command.
45
+ -- @return boolean: True if command succeeds, false otherwise.
46
+ function executeString(cmd)
47
+ assert(type(cmd) == "string", "sys.executeString: Argument 'cmd' is not a string.")
48
+
49
+ -- Command log
50
+ local execPath = path(config.temp, "execute.log")
51
+
52
+ -- Hande std output
53
+ local run = cmd
54
+ if not ( config.verbose or config.debug) then
55
+ run = run .. " >" .. execPath .. " 2>&1"
56
+ end
57
+
58
+ -- Run the command
59
+ local ok = os.execute( run )
60
+
61
+ -- Append the execution log to the log
62
+ local execFile, err = io.open( execPath, "r")
63
+ if execFile then
64
+ assert(execFile, "sys.executeString: Cannot create log.")
65
+ log.write("Command: " .. cmd .. "\n" .. execFile:read("*all") .. "\n")
66
+ execFile:close()
67
+ if not config.debug then os.remove(execPath) end
68
+ end
69
+
70
+ if ok~=0 then return nil, "Failed running command: " .. cmd end
71
+ return true, "Sucessfully executed command: " .. cmd
72
+ end
73
+
74
+ --- Run the given system command, quoting its arguments.
75
+ -- The command is executed in the current directory in the dir stack.
76
+ -- @param command string: The command to be executed. No quoting/escaping need to be applied.
77
+ -- @param ... strings: containing additional arguments, which are quoted.
78
+ -- @return ok, log: true on success, false on failure and log message.
79
+ function execute(command, ...)
80
+ assert(type(command) == "string", "execute argument 'command' is not a string.")
81
+
82
+ for k, arg in ipairs({...}) do
83
+ assert(type(arg) == "string", "execute argument #" .. tostring(k+1) .. "is not a string.")
84
+ command = command .. " " .. Q(arg)
85
+ end
86
+ return executeString(command)
87
+ end
88
+
89
+ --- Create a directory.
90
+ -- @param dir string: Path to create.
91
+ -- @return ok, log: true on success, false on failure and log message.
92
+ -- Succeeds if already exists.
93
+ function makeDir(dir)
94
+ assert(type(dir)=="string", "sys.makeDir: Argument 'dir' is not a string.")
95
+ dir = path(dir)
96
+
97
+ if isDir(dir) then return true end -- done
98
+
99
+ -- Find out if the base dir exists, if not make it
100
+ local base = dir:gsub("/[^/]*$","")
101
+ if base == "" then base = "/" end
102
+ -- Recursion!
103
+ if not isDir(base) then makeDir(base) end
104
+
105
+ -- Make directory
106
+ local ok = lfs.mkdir(dir)
107
+ if not ok then return nil, "Cannot create directory: " .. dir end
108
+ return true, "Created directory: " .. dir
109
+ end
110
+
111
+ --- Force delete a file, even if it is open.
112
+ -- If the file can't be deleted because it is currently open, this function
113
+ -- will try to rename the file to a temporary file in the same directory.
114
+ -- Windows in particular doesn't allow running executables to be deleted, but
115
+ -- it does allow them to be renamed. Cygwin 1.7 (unlike 1.5) does allow such
116
+ -- deletion but internally implements it via a mechanism like this. For
117
+ -- futher details, see
118
+ -- http://sourceforge.net/mailarchive/message.php?msg_name=bc4ed2190909261323i7c6280bfp6e7be6f70c713b0c%40mail.gmail.com
119
+ --
120
+ -- @param src - file name
121
+ function forceDelete(src)
122
+ os.remove(src) -- note: ignore fail
123
+
124
+ if exists(src) then -- still exists, try move instead
125
+ local tempfile
126
+ local MAX_TRIES = 10
127
+ for i=1,MAX_TRIES do
128
+ local test = src .. ".luadist-temporary-" .. i
129
+ os.remove(test) -- note: ignore fail
130
+ if not exists(test) then
131
+ tempfile = test
132
+ break
133
+ end
134
+ end
135
+ if not tempfile then
136
+ return nil, "Failed removing temporary files: " .. tempfile .. "*"
137
+ end
138
+ local ok, err = os.rename(src, tempfile)
139
+ if not ok then
140
+ return nil, "Failed renaming file: " .. err
141
+ end
142
+ end
143
+ return true
144
+ end
145
+
146
+ --- Move a file from one location to another.
147
+ -- @param src string: Pathname of source.
148
+ -- @param dest string: Pathname of destination.
149
+ -- @return ok, log: true on success, false on failure and log message.
150
+ function move(src, dest)
151
+ assert(type(src)=="string", "sys.move: Argument 'src' is not a string.")
152
+ assert(type(dest)=="string", "sys.move: Argument 'dest' is not a string.")
153
+
154
+ local ok, err = execute("mv -f", src, dest)
155
+ if not ok then return nil, "Failed moving source: " .. src .. " to: " .. dest .. " error: " .. err end
156
+ return true, "Moved source: " .. src .. " to: " .. dest
157
+ end
158
+
159
+ --- Recursive copy a file or directory.
160
+ -- @param src string: Pathname of source
161
+ -- @param dest string: Pathname of destination.
162
+ -- @param copy_contents boolean: if true, copies contents of
163
+ -- directory src into directory dest, creating dest
164
+ -- and parents of dest if they don't exist. (false if omitted)
165
+ -- @return ok, log: true on success, false on failure and log message.
166
+ function copy(src, dest, copy_contents)
167
+ assert(type(src)=="string", "copy argument 'src' is not a string.")
168
+ assert(type(dest)=="string", "copy argument 'dest' is not a string.")
169
+
170
+ if copy_contents and not isDir(dest) then
171
+ local ok, err = makeDir(dest)
172
+ if not ok then return nil, err end
173
+ end
174
+
175
+ local ok, err
176
+ if copy_contents then
177
+ ok, err = execute("cp -R -f -H " .. Q(src) .. [[/*]], dest)
178
+ else
179
+ ok, err = execute("cp -R -f -H ", src, dest)
180
+ end
181
+ if not ok then return nil, "Failed copying " .. src .. " to " .. dest .. ".\n" .. err end
182
+ return true
183
+ end
184
+
185
+ --- little helper function to get file depth (for directories its +1)
186
+ local function pathLen(dir)
187
+ local _, len = dir:gsub("[^\\/]+","")
188
+ return len - 1
189
+ end
190
+
191
+ --- Delete a file or a directory and all its contents.
192
+ -- For safety, this only accepts absolute paths.
193
+ -- @param dir string: Pathname of the file or directory to delete
194
+ -- @return ok, log: true on success, false on failure and log message. Returns success if already deleted.
195
+ function delete(dir)
196
+ assert(type(dir)=="string" and dir:match("^[%a:]*[/\\]"), "delete argument 'dir' is not a string or a full path.")
197
+ if not exists(dir) then return true end
198
+ return executeString("rm -rf " .. Q(dir))
199
+ end
200
+
201
+ --- List the contents of a directory.
202
+ -- @param path string: directory to list if not specified the current directory will be used.
203
+ -- @return table: an array of strings with the filenames representing the contents of a directory.
204
+ function dir(dir)
205
+ assert(not dir or type(dir) == "string", "dir argument 'dir' is not a string.")
206
+ dir = dir or curDir()
207
+ if not isDir(dir) then return nil, "Could not find directory " .. dir .. "." end
208
+ local files = {}
209
+ for file in lfs.dir(dir) do
210
+ if not file:match("^%.") then table.insert(files, file) end
211
+ end
212
+ return files
213
+ end
214
+
215
+ --- Get current directory.
216
+ -- @return string: current direcotry.
217
+ function curDir()
218
+ local dir, err = lfs.currentdir()
219
+ if not dir then return nil, err end
220
+ return dir:gsub("\\","/") -- Everyone loves win32
221
+ end
222
+
223
+ --- Test for existance of a file.
224
+ -- @param path string: filename to test
225
+ -- @return ok, log: true on success, false on failure and log message.
226
+ function exists(dir)
227
+ assert(type(dir)=="string", "exists argument 'dir' is not a string.")
228
+ return lfs.attributes(dir)
229
+ end
230
+
231
+ --- Test is pathname is a directory.
232
+ -- @param path string: pathname to test
233
+ -- @return ok, log: true on success, false on failure and log message.
234
+ function isDir(dir)
235
+ assert(type(dir)=="string", "isDir argument 'dir' is not a string.")
236
+ local attr, err = lfs.attributes(dir)
237
+ if not attr then return nil, "Failed to obtain attributes for " .. dir .. "." end
238
+ return attr.mode == "directory"
239
+ end
240
+
241
+ --- Test is pathname is a file.
242
+ -- @param path string: pathname to test
243
+ -- @return ok, log: true on success, false on failure and log message.
244
+ function isFile(dir)
245
+ assert(type(dir)=="string", "isFile argument 'dir' is not a string.")
246
+ local attr, err = lfs.attributes(dir)
247
+ if not attr then return nil, "Failed to obtain attributes for " .. dir .. "." end
248
+ return attr.mode == "file"
249
+ end
250
+
251
+ --- Recursively list the contents of a directory.
252
+ -- @param path string: directory to list if not specified the current directory will be used
253
+ -- @return table: an array of strings representing the contents of the directory structure
254
+ function list(dir)
255
+ assert(type(dir)=="string", "list argument 'path' is not a string.")
256
+ if not isDir(dir) then return nil, "Directory " .. dir .. " does not exist." end
257
+
258
+ local files = {}
259
+
260
+ local function collect (subdir)
261
+ subdir = subdir or ""
262
+ for file in lfs.dir(path(dir, subdir)) do
263
+ if not file:match("^%.") then
264
+ table.insert(files, subdir .. file)
265
+ if isDir(path(dir, subdir .. file)) then collect(subdir .. "/" .. file .. "/") end
266
+ end
267
+ end
268
+ end
269
+ collect()
270
+ return files
271
+ end
272
+
273
+ --- Compress files in a .zip archive.
274
+ -- @param zipfile string: pathname of .zip archive to be created.
275
+ -- @param ... Filenames to be stored in the archive are given as additional arguments.
276
+ -- @return ok, log: true on success, false on failure and log message.
277
+ function zip(workdir, zipfile, ...)
278
+ assert (type(workdir)=="string", "zip argument 'workdir' is not a string.")
279
+ assert (type(zipfile)=="string", "zip argument 'zipfile' is not a string.")
280
+ return execute("cd " .. Q(workdir) .. " && zip -r", zipfile, ...)
281
+ end
282
+
283
+ --- Unpack an archive.
284
+ -- Extract the contents of an archive, detecting its format by filename extension.
285
+ -- @param archive string: Filename of archive.
286
+ -- @return ok, log: true on success, false on failure and log message.
287
+ function unzip(archive, dest)
288
+ assert(type(archive) == "string", "unpack argument 'archive' is not a string.")
289
+ assert(type(dest) == "string", "unpack agrument 'dest' is not a string.")
290
+ local ok
291
+ if archive:match("%.zip$") or archive:match("%.dist$") then
292
+ ok = executeString("unzip " .. Q(archive) .. " -d " .. Q(dest))
293
+ end
294
+ if not ok then
295
+ return false, "Failed extracting."
296
+ end
297
+ return dest
298
+ end
299
+
300
+ --- Extract file contents of a file from archive
301
+ -- @param zipfile string: pathname of .zip/.dist archive to read from.
302
+ -- @param file string: file to get contents of.
303
+ -- @return contents, err: returns contents of file or false and error message.
304
+ -- Requires io.popen (2DO: work when io.popen not available?)
305
+ function getZipFile(zipfile, file)
306
+ assert(type(zipfile) == "string", "unpack argument 'zipfile' is not a string.")
307
+ assert(type(file) == "string", "unpack agrument 'file' is not a string.")
308
+
309
+ -- Try to get contents
310
+ local f, err = io.popen("unzip -cp " .. Q(zipfile) .. " " .. Q(file))
311
+ if not f then return false, "Failed to extract " .. file .. " from " .. zipfile end
312
+
313
+ -- Read result
314
+ local content = f:read("*a")
315
+ f:close()
316
+ if content == "" then return false, "Failed to extract " .. file .. " from " .. zipfile end
317
+ return content
318
+ end
319
+
320
+ --- Override different functions for Windows
321
+ if config.arch == "Windows" then
322
+
323
+ --- Quote argument for shell processing (Windows).
324
+ -- Adds single quotes and escapes.
325
+ -- @param arg string: Unquoted argument.
326
+ -- @return string: Quoted argument.
327
+ function Q(arg)
328
+ assert(type(arg) == "string", "Q argument 'arg' is not a string.")
329
+ -- Quote DIR for Windows
330
+ if arg:match("^[\.a-zA-Z]?:?[\\/]") then
331
+ return '"' .. arg:gsub("//","\\"):gsub("/", "\\"):gsub('"', '\\"') .. '"'
332
+ end
333
+ -- URLs and anything else
334
+ return '"' .. arg:gsub('"', '\\"') .. '"'
335
+ end
336
+
337
+ --- Move a file from one location to another (Windows).
338
+ -- @param src string: Pathname of source.
339
+ -- @param dest string: Pathname of destination.
340
+ -- @return ok, log: true on success, false on failure and log message.
341
+ function move(src, dest)
342
+ assert(type(src)=="string", "sys.move: Argument 'src' is not a string.")
343
+ assert(type(dest)=="string", "sys.move: Argument 'dest' is not a string.")
344
+
345
+ -- note: copy+delete may be more reliable than move (e.g. copy across drives).
346
+ -- [improve: cleanup on failure?]
347
+ local ok, err = copy(src, dest)
348
+ if ok then
349
+ ok, err = delete(src)
350
+ end
351
+ if not ok then return nil, "Failed moving source: " .. src .. " to: " .. dest .. " error: " .. err end
352
+ return true, "Moved source: " .. src .. " to: " .. dest
353
+ end
354
+
355
+ --- Recursive copy a file or directory (Windows).
356
+ -- @param src string: Pathname of source
357
+ -- @param dest string: Pathname of destination.
358
+ -- If `src` is a directory, copies contents of `src` into contents of
359
+ -- directory `dest`. Otherwise, both must represent files.
360
+ -- @return ok, log: true on success, false on failure and log message.
361
+ function copy(src, dest)
362
+ assert(type(src)=="string", "copy argument 'src' is not a string.")
363
+ assert(type(dest)=="string", "copy argument 'dest' is not a string.")
364
+
365
+ -- Avoid ambiguous behavior: file <-> directory
366
+ --2DO - Improve?
367
+ if isFile(src) then
368
+ if isDir(dest) then
369
+ return nil, "ambiguous request to copy file " .. src .. " to directory " .. dest
370
+ end
371
+ elseif isDir(src) then
372
+ if isFile(dest) then
373
+ return nil, "ambiguous request to copy directory " .. src .. " to file " .. dest
374
+ end
375
+ end
376
+
377
+ --2DO: The below will cause problems if src and dest are the same.
378
+
379
+ -- For any destination files that will be overwritten,
380
+ -- force delete them to avoid conflicts from open files
381
+ -- in subsequent copy.
382
+ if isDir(src) then
383
+ local srcfiles = list(src)
384
+ for i = 1, #srcfiles do
385
+ local destfile = path(dest, srcfiles[i])
386
+ if isFile(destfile) then
387
+ local ok, err = forceDelete(destfile)
388
+ if not ok then return nil, "Failed removing file: " .. destfile .. " " .. err end
389
+ end
390
+ end
391
+ elseif isFile(src) then
392
+ local ok, err = forceDelete(dest)
393
+ if not ok then return nil, "Failed removing file: " .. dest .. " " .. err end
394
+ end
395
+
396
+ local ok, err
397
+ if isDir(src) then
398
+ -- note: "xcopy /E /I /Y" copies contents of `src` into contents
399
+ -- of `dest` and creates all leading directories of `dest` if needed.
400
+ ok, err = execute("xcopy /I /E /Y " .. Q(src), dest)
401
+ else
402
+ ok, err = execute("copy /Y", src, dest)
403
+ end
404
+ if not ok then return nil, "Failed copying " .. src .. " to " .. dest .. ".\n" .. err end
405
+ -- note: The /Q flag on xcopy is not fully quiet; it prints
406
+ -- "1 File(s) copied". Copy lacks a /Q flag.
407
+
408
+ return true
409
+ end
410
+
411
+ --- Delete a file or a directory and all its contents (Windows).
412
+ -- For safety, this only accepts absolute paths.
413
+ -- @param dir string: Pathname of the file or directory to delete
414
+ -- @return ok, log: true on success, false on failure and log message.
415
+ -- Returns success if already deleted.
416
+ function delete(dir)
417
+ assert(type(dir)=="string" and dir:match("^[%a:]*[/\\]"), "delete argument 'dir' is not a string or a full path.")
418
+ -- Note: `del /S` recursively deletes files but not directories.
419
+ -- `rmdir /S` recursively deletes files and directories but does not
420
+ -- work if its parameter is a file.
421
+ if not exists(dir) then
422
+ return true
423
+ elseif isDir(dir) then
424
+ local ok, err = executeString("rmdir /S /Q " .. Q(dir))
425
+ if not ok then
426
+ return nil, "Could not recursively delete directory " .. dir .. " . " .. err
427
+ end
428
+ else
429
+ local ok, err = os.remove(dir)
430
+ if not ok then
431
+ return nil, "Could not delete file " .. dir .. " . " .. err
432
+ end
433
+ end
434
+ return true
435
+ end
436
+ end