summaryrefslogtreecommitdiff
path: root/Data/BuiltIn/Libraries/lua-stdlib/io.lua
blob: 1a2b79f864e1332463523a25ebe325a437c5359c (plain)
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
314
315
316
317
318
319
320
321
322
--[[
 General Lua Libraries for Lua 5.1, 5.2 & 5.3
 Copyright (C) 2002-2018 stdlib authors
]]
--[[--
 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 _ = require 'std._base'

local argscheck = _.typecheck and _.typecheck.argscheck
local catfile = _.io.catfile
local leaves = _.tree.leaves
local split = _.string.split

_ = nil


local _ENV = require 'std.normalize' {
   'io',
   _G = _G,  -- FIXME: don't use the host _G as an API!
   concat = 'table.concat',
   dirsep = 'package.dirsep',
   format = 'string.format',
   gsub = 'string.gsub',
   input = 'io.input',
   insert = 'table.insert',
   io_type = 'io.type',
   merge = 'table.merge',
   open = 'io.open',
   output = 'io.output',
   popen = 'io.popen',
   stderr = 'io.stderr',
   stdin = 'io.stdin',
   write = 'io.write',
}


--[[ =============== ]]--
--[[ Implementation. ]]--
--[[ =============== ]]--


local M


local function input_handle(h)
   if h == nil then
      return input()
   elseif type(h) == 'string' then
      return 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
      write(h, '\n')
      h = output()
   end
   for v in leaves(ipairs, {...}) do
      h:write(v, '\n')
   end
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
         input(stdin)
      else
         input(v)
      end
      fn(v, i)
   end
end


local function warnfmt(msg, ...)
   local prefix = ''
   local prog = rawget(_G, 'prog') or {}
   local opts = rawget(_G, 'opts') or {}
   if prog.name then
      prefix = prog.name .. ':'
      if prog.line then
         prefix = prefix .. str(prog.line) .. ':'
      end
   elseif prog.file then
      prefix = prog.file .. ':'
      if prog.line then
         prefix = prefix .. str(prog.line) .. ':'
      end
   elseif opts.program then
      prefix = opts.program .. ':'
      if opts.line then
         prefix = prefix .. str(opts.line) .. ':'
      end
   end
   if #prefix > 0 then
      prefix = prefix .. ' '
   end
   return prefix .. format(msg, ...)
end


local function warn(msg, ...)
   writelines(stderr, warnfmt(msg, ...))
end



--[[ ================= ]]--
--[[ Public Interface. ]]--
--[[ ================= ]]--


local function X(decl, fn)
   return argscheck and argscheck('std.io.' .. decl, fn) or 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.
   -- @function warn
   -- @string msg format string
   -- @param ... additional arguments to plug format string specifiers
   -- @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(gsub(concat({...}, dirsep), '^$', 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(gsub(path, 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),


   --- 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(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),
}


return merge(io, M)



--- 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)