1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
|
--- Application support functions.
-- See @{01-introduction.md.Application_Support|the Guide}
--
-- Dependencies: `pl.utils`, `pl.path`
-- @module pl.app
local io,package,require = _G.io, _G.package, _G.require
local utils = require 'pl.utils'
local path = require 'pl.path'
local app = {}
--- return the name of the current script running.
-- The name will be the name as passed on the command line
-- @return string filename
function app.script_name()
if _G.arg and _G.arg[0] then
return _G.arg[0]
end
return utils.raise("No script name found")
end
--- prefixes the current script's path to the Lua module path.
-- Applies to both the source and the binary module paths. It makes it easy for
-- the main file of a multi-file program to access its modules in the same directory.
-- `base` allows these modules to be put in a specified subdirectory, to allow for
-- cleaner deployment and resolve potential conflicts between a script name and its
-- library directory.
--
-- Note: the path is prefixed, so it is searched first when requiring modules.
-- @string base optional base directory (absolute, or relative path).
-- @treturn string the current script's path with a trailing slash
function app.require_here (base)
local p = path.dirname(app.script_name())
if not path.isabs(p) then
p = path.join(path.currentdir(),p)
end
if p:sub(-1,-1) ~= path.sep then
p = p..path.sep
end
if base then
if path.is_windows then
base = base:gsub('/','\\')
end
if path.isabs(base) then
p = base .. path.sep
else
p = p..base..path.sep
end
end
local so_ext = path.is_windows and 'dll' or 'so'
local lsep = package.path:find '^;' and '' or ';'
local csep = package.cpath:find '^;' and '' or ';'
package.path = ('%s?.lua;%s?%sinit.lua%s%s'):format(p,p,path.sep,lsep,package.path)
package.cpath = ('%s?.%s%s%s'):format(p,so_ext,csep,package.cpath)
return p
end
--- return a suitable path for files private to this application.
-- These will look like '~/.SNAME/file', with '~' as with expanduser and
-- SNAME is the name of the script without .lua extension.
-- If the directory does not exist, it will be created.
-- @string file a filename (w/out path)
-- @return a full pathname, or nil
-- @return cannot create directory error
-- @usage
-- -- when run from a script called 'testapp' (on Windows):
-- local app = require 'pl.app'
-- print(app.appfile 'test.txt')
-- -- C:\Documents and Settings\steve\.testapp\test.txt
function app.appfile(file)
local sfullname, err = app.script_name()
if not sfullname then return utils.raise(err) end
local sname = path.basename(sfullname)
local name = path.splitext(sname)
local dir = path.join(path.expanduser('~'),'.'..name)
if not path.isdir(dir) then
local ret = path.mkdir(dir)
if not ret then return utils.raise('cannot create '..dir) end
end
return path.join(dir,file)
end
--- return string indicating operating system.
-- @return 'Windows','OSX' or whatever uname returns (e.g. 'Linux')
function app.platform()
if path.is_windows then
return 'Windows'
else
local f = io.popen('uname')
local res = f:read()
if res == 'Darwin' then res = 'OSX' end
f:close()
return res
end
end
--- return the full command-line used to invoke this script.
-- It will not include the scriptname itself, see `app.script_name`.
-- @return command-line
-- @return name of Lua program used
-- @usage
-- -- execute: lua -lluacov -e 'print(_VERSION)' myscript.lua
--
-- -- myscript.lua
-- print(require("pl.app").lua()) --> "lua -lluacov -e 'print(_VERSION)'", "lua"
function app.lua()
local args = _G.arg
if not args then
return utils.raise "not in a main program"
end
local cmd = {}
local i = -1
while true do
table.insert(cmd, 1, args[i])
if not args[i-1] then
return utils.quote_arg(cmd), args[i]
end
i = i - 1
end
end
--- parse command-line arguments into flags and parameters.
-- Understands GNU-style command-line flags; short (`-f`) and long (`--flag`).
--
-- These may be given a value with either '=' or ':' (`-k:2`,`--alpha=3.2`,`-n2`),
-- a number value can be given without a space. If the flag is marked
-- as having a value, then a space-separated value is also accepted (`-i hello`),
-- see the `flags_with_values` argument).
--
-- Multiple short args can be combined like so: ( `-abcd`).
--
-- When specifying the `flags_valid` parameter, its contents can also contain
-- aliasses, to convert short/long flags to the same output name. See the
-- example below.
--
-- Note: if a flag is repeated, the last value wins.
-- @tparam {string} args an array of strings (default is the global `arg`)
-- @tab flags_with_values any flags that take values, either list or hash
-- table e.g. `{ out=true }` or `{ "out" }`.
-- @tab flags_valid (optional) flags that are valid, either list or hashtable.
-- If not given, everything
-- will be accepted(everything in `flags_with_values` will automatically be allowed)
-- @return a table of flags (flag=value pairs)
-- @return an array of parameters
-- @raise if args is nil, then the global `args` must be available!
-- @usage
-- -- Simple form:
-- local flags, params = app.parse_args(nil,
-- { "hello", "world" }, -- list of flags taking values
-- { "l", "a", "b"}) -- list of allowed flags (value ones will be added)
--
-- -- More complex example using aliasses:
-- local valid = {
-- long = "l", -- if 'l' is specified, it is reported as 'long'
-- new = { "n", "old" }, -- here both 'n' and 'old' will go into 'new'
-- }
-- local values = {
-- "value", -- will automatically be added to the allowed set of flags
-- "new", -- will mark 'n' and 'old' as requiring a value as well
-- }
-- local flags, params = app.parse_args(nil, values, valid)
--
-- -- command: myapp.lua -l --old:hello --value world param1 param2
-- -- will yield:
-- flags = {
-- long = true, -- input from 'l'
-- new = "hello", -- input from 'old'
-- value = "world", -- allowed because it was in 'values', note: space separated!
-- }
-- params = {
-- [1] = "param1"
-- [2] = "param2"
-- }
function app.parse_args (args,flags_with_values, flags_valid)
if not args then
args = _G.arg
if not args then utils.raise "Not in a main program: 'arg' not found" end
end
local with_values = {}
for k,v in pairs(flags_with_values or {}) do
if type(k) == "number" then
k = v
end
with_values[k] = true
end
local valid
if not flags_valid then
-- if no allowed flags provided, we create a table that always returns
-- the keyname, no matter what you look up
valid = setmetatable({},{ __index = function(_, key) return key end })
else
valid = {}
for k,aliasses in pairs(flags_valid) do
if type(k) == "number" then -- array/list entry
k = aliasses
end
if type(aliasses) == "string" then -- single alias
aliasses = { aliasses }
end
if type(aliasses) == "table" then -- list of aliasses
-- it's the alternate name, so add the proper mappings
for i, alias in ipairs(aliasses) do
valid[alias] = k
end
end
valid[k] = k
end
do
local new_with_values = {} -- needed to prevent "invalid key to 'next'" error
for k,v in pairs(with_values) do
if not valid[k] then
valid[k] = k -- add the with_value entry as a valid one
new_with_values[k] = true
else
new_with_values[valid[k]] = true --set, but by its alias
end
end
with_values = new_with_values
end
end
-- now check that all flags with values are reported as such under all
-- of their aliasses
for k, main_alias in pairs(valid) do
if with_values[main_alias] then
with_values[k] = true
end
end
local _args = {}
local flags = {}
local i = 1
while i <= #args do
local a = args[i]
local v = a:match('^-(.+)')
local is_long
if not v then
-- we have a parameter
_args[#_args+1] = a
else
-- it's a flag
if v:find '^-' then
is_long = true
v = v:sub(2)
end
if with_values[v] then
if i == #args or args[i+1]:find '^-' then
return utils.raise ("no value for '"..v.."'")
end
flags[valid[v]] = args[i+1]
i = i + 1
else
-- a value can also be indicated with = or :
local var,val = utils.splitv (v,'[=:]', false, 2)
var = var or v
val = val or true
if not is_long then
if #var > 1 then
if var:find '.%d+' then -- short flag, number value
val = var:sub(2)
var = var:sub(1,1)
else -- multiple short flags
for i = 1,#var do
local f = var:sub(i,i)
if not valid[f] then
return utils.raise("unknown flag '"..f.."'")
else
f = valid[f]
end
flags[f] = true
end
val = nil -- prevents use of var as a flag below
end
else -- single short flag (can have value, defaults to true)
val = val or true
end
end
if val then
if not valid[var] then
return utils.raise("unknown flag '"..var.."'")
else
var = valid[var]
end
flags[var] = val
end
end
end
i = i + 1
end
return flags,_args
end
return app
|