forked from lua-stdlib/lua-stdlib
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathio.lua
More file actions
313 lines (251 loc) · 8.72 KB
/
io.lua
File metadata and controls
313 lines (251 loc) · 8.72 KB
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
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
--[[--
Additions to the core io module.
The module table returned by `std.io` also contains all of the entries from
the core `io` module table. An hygienic way to import this module, then,
is simply to override core `io` locally:
local io = require "std.io"
@corelibrary std.io
]]
local std = require "std.base"
local debug = require "std.debug"
local argerror, setmetatable = debug.argerror, debug.setmetatable
local ipairs, pairs = std.ipairs, std.pairs
local catfile = std.io.catfile
local len = std.operator.len
local dirsep = std.package.dirsep
local split = std.string.split
local insert = std.table.insert
local leaves = std.tree.leaves
local M, monkeys
local function input_handle (h)
if h == nil then
return io.input ()
elseif type (h) == "string" then
return io.open (h)
end
return h
end
local function slurp (file)
local h, err = input_handle (file)
if h == nil then argerror ("std.io.slurp", 1, err, 2) end
if h then
local s = h:read ("*a")
h:close ()
return s
end
end
local function readlines (file)
local h, err = input_handle (file)
if h == nil then argerror ("std.io.readlines", 1, err, 2) end
local l = {}
for line in h:lines () do
l[#l + 1] = line
end
h:close ()
return l
end
local function writelines (h, ...)
if io.type (h) ~= "file" then
io.write (h, "\n")
h = io.output ()
end
for v in leaves (ipairs, {...}) do
h:write (v, "\n")
end
end
local function monkey_patch (namespace)
namespace = namespace or _G
namespace.io = std.base.copy (namespace.io or {}, monkeys)
if namespace.io.stdin then
local mt = getmetatable (namespace.io.stdin) or {}
mt.readlines = M.readlines
mt.writelines = M.writelines
setmetatable (namespace.io.stdin, mt)
end
return M
end
local function process_files (fn)
-- N.B. "arg" below refers to the global array of command-line args
if len (arg) == 0 then
insert (arg, "-")
end
for i, v in ipairs (arg) do
if v == "-" then
io.input (io.stdin)
else
io.input (v)
end
fn (v, i)
end
end
local function warnfmt (msg, ...)
local prefix = ""
if (prog or {}).name then
prefix = prog.name .. ":"
if prog.line then
prefix = prefix .. tostring (prog.line) .. ":"
end
elseif (prog or {}).file then
prefix = prog.file .. ":"
if prog.line then
prefix = prefix .. tostring (prog.line) .. ":"
end
elseif (opts or {}).program then
prefix = opts.program .. ":"
if opts.line then
prefix = prefix .. tostring (opts.line) .. ":"
end
end
if #prefix > 0 then prefix = prefix .. " " end
return prefix .. string.format (msg, ...)
end
local function warn (msg, ...)
writelines (io.stderr, warnfmt (msg, ...))
end
--[[ ================= ]]--
--[[ Public Interface. ]]--
--[[ ================= ]]--
local function X (decl, fn)
return debug.argscheck ("std.io." .. decl, fn)
end
M = {
--- Diagnostic functions
-- @section diagnosticfuncs
--- Die with error.
-- This function uses the same rules to build a message prefix
-- as @{warn}.
-- @function die
-- @string msg format string
-- @param ... additional arguments to plug format string specifiers
-- @see warn
-- @usage die ("oh noes! (%s)", tostring (obj))
die = X ("die (string, [any...])", function (...)
error (warnfmt (...), 0)
end),
--- Give warning with the name of program and file (if any).
-- If there is a global `prog` table, prefix the message with
-- `prog.name` or `prog.file`, and `prog.line` if any. Otherwise
-- if there is a global `opts` table, prefix the message with
-- `opts.program` and `opts.line` if any. @{std.optparse:parse}
-- returns an `opts` table that provides the required `program`
-- field, as long as you assign it back to `_G.opts`.
-- @function warn
-- @string msg format string
-- @param ... additional arguments to plug format string specifiers
-- @see std.optparse:parse
-- @see die
-- @usage
-- local OptionParser = require "std.optparse"
-- local parser = OptionParser "eg 0\nUsage: eg\n"
-- _G.arg, _G.opts = parser:parse (_G.arg)
-- if not _G.opts.keep_going then
-- require "std.io".warn "oh noes!"
-- end
warn = X ("warn (string, [any...])", warn),
--- Path Functions
-- @section pathfuncs
--- Concatenate directory names into a path.
-- @function catdir
-- @string ... path components
-- @return path without trailing separator
-- @see catfile
-- @usage dirpath = catdir ("", "absolute", "directory")
catdir = X ("catdir (string...)", function (...)
return (table.concat ({...}, dirsep):gsub("^$", dirsep))
end),
--- Concatenate one or more directories and a filename into a path.
-- @function catfile
-- @string ... path components
-- @treturn string path
-- @see catdir
-- @see splitdir
-- @usage filepath = catfile ("relative", "path", "filename")
catfile = X ("catfile (string...)", catfile),
--- Remove the last dirsep delimited element from a path.
-- @function dirname
-- @string path file path
-- @treturn string a new path with the last dirsep and following
-- truncated
-- @usage dir = dirname "/base/subdir/filename"
dirname = X ("dirname (string)", function (path)
return (path:gsub (catfile ("", "[^", "]*$"), ""))
end),
--- Split a directory path into components.
-- Empty components are retained: the root directory becomes `{"", ""}`.
-- @function splitdir
-- @param path path
-- @return list of path components
-- @see catdir
-- @usage dir_components = splitdir (filepath)
splitdir = X ("splitdir (string)",
function (path) return split (path, dirsep) end),
--- Module Functions
-- @section modulefuncs
--- Overwrite core `io` methods with `std` enhanced versions.
--
-- Also adds @{readlines} and @{writelines} metamethods to core file objects.
-- @function monkey_patch
-- @tparam[opt=_G] table namespace where to install global functions
-- @treturn table the `std.io` module table
-- @usage local io = require "std.io".monkey_patch ()
monkey_patch = X ("monkey_patch (?table)", monkey_patch),
--- IO Functions
-- @section iofuncs
--- Process files specified on the command-line.
-- Each filename is made the default input source with `io.input`, and
-- then the filename and argument number are passed to the callback
-- function. In list of filenames, `-` means `io.stdin`. If no
-- filenames were given, behave as if a single `-` was passed.
-- @todo Make the file list an argument to the function.
-- @function process_files
-- @tparam fileprocessor fn function called for each file argument
-- @usage
-- #! /usr/bin/env lua
-- -- minimal cat command
-- local io = require "std.io"
-- io.process_files (function () io.write (io.slurp ()) end)
process_files = X ("process_files (function)", process_files),
--- Read a file or file handle into a list of lines.
-- The lines in the returned list are not `\n` terminated.
-- @function readlines
-- @tparam[opt=io.input()] file|string file file handle or name;
-- if file is a file handle, that file is closed after reading
-- @treturn list lines
-- @usage list = readlines "/etc/passwd"
readlines = X ("readlines (?file|string)", readlines),
--- Perform a shell command and return its output.
-- @function shell
-- @string c command
-- @treturn string output, or nil if error
-- @see os.execute
-- @usage users = shell [[cat /etc/passwd | awk -F: '{print $1;}']]
shell = X ("shell (string)", function (c) return slurp (io.popen (c)) end),
--- Slurp a file handle.
-- @function slurp
-- @tparam[opt=io.input()] file|string file file handle or name;
-- if file is a file handle, that file is closed after reading
-- @return contents of file or handle, or nil if error
-- @see process_files
-- @usage contents = slurp (filename)
slurp = X ("slurp (?file|string)", slurp),
--- Write values adding a newline after each.
-- @function writelines
-- @tparam[opt=io.output()] file h open writable file handle;
-- the file is **not** closed after writing
-- @tparam string|number ... values to write (as for write)
-- @usage writelines (io.stdout, "first line", "next line")
writelines = X ("writelines (?file|string|number, [string|number...])", writelines),
}
monkeys = std.base.copy ({}, M) -- before deprecations and core merge
return std.base.merge (M, io)
--- Types
-- @section Types
--- Signature of @{process_files} callback function.
-- @function fileprocessor
-- @string filename filename
-- @int i argument number of *filename*
-- @usage
-- local fileprocessor = function (filename, i)
-- io.write (tostring (i) .. ":\n===\n" .. io.slurp (filename) .. "\n")
-- end
-- io.process_files (fileprocessor)