Tamar 0.7.2 → 0.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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