rename 'package' to 'module'
[nit.git] / lib / standard / file.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2004-2008 Jean Privat <jean@pryen.org>
4 # Copyright 2008 Floréal Morandat <morandat@lirmm.fr>
5 # Copyright 2008 Jean-Sébastien Gélinas <calestar@gmail.com>
6 #
7 # This file is free software, which comes along with NIT. This software is
8 # distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
9 # without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
10 # PARTICULAR PURPOSE. You can modify it is you want, provided this header
11 # is kept unaltered, and a notification of the changes is added.
12 # You are allowed to redistribute it and sell it, alone or is a part of
13 # another product.
14
15 # File manipulations (create, read, write, etc.)
16 module file
17
18 intrude import stream
19 intrude import string
20 import string_search
21 import time
22
23 redef class Object
24 # Simple I/O
25
26 # Print `objects` on the standard output (`stdout`).
27 protected fun printn(objects: Object...)
28 do
29 stdout.write(objects.to_s)
30 end
31
32 # Print an `object` on the standard output (`stdout`) and add a newline.
33 protected fun print(object: Object)
34 do
35 stdout.write(object.to_s)
36 stdout.write("\n")
37 end
38
39 # Read a character from the standard input (`stdin`).
40 protected fun getc: Char
41 do
42 return stdin.read_char.ascii
43 end
44
45 # Read a line from the standard input (`stdin`).
46 protected fun gets: String
47 do
48 return stdin.read_line
49 end
50 end
51
52 # File Abstract Stream
53 abstract class FStream
54 super IOS
55 # The path of the file.
56 readable var _path: nullable String = null
57
58 # The FILE *.
59 var _file: nullable NativeFile = null
60
61 fun file_stat: FileStat
62 do return _file.file_stat end
63 end
64
65 # File input stream
66 class IFStream
67 super FStream
68 super BufferedIStream
69 # Misc
70
71 # Open the same file again.
72 # The original path is reused, therefore the reopened file can be a different file.
73 fun reopen
74 do
75 if not eof then close
76 _file = new NativeFile.io_open_read(_path.to_cstring)
77 _end_reached = false
78 _buffer_pos = 0
79 _buffer.clear
80 end
81
82 redef fun close
83 do
84 var i = _file.io_close
85 _end_reached = true
86 end
87
88 redef fun fill_buffer
89 do
90 var nb = _file.io_read(_buffer._items, _buffer._capacity)
91 if nb <= 0 then
92 _end_reached = true
93 nb = 0
94 end
95 _buffer._length = nb
96 _buffer_pos = 0
97 end
98
99 # End of file?
100 redef readable var _end_reached: Bool = false
101
102 # Open the file at `path` for reading.
103 init open(path: String)
104 do
105 _path = path
106 prepare_buffer(10)
107 _file = new NativeFile.io_open_read(_path.to_cstring)
108 assert cant_open_file: _file != null
109 end
110
111 private init do end
112 private init without_file do end
113 end
114
115 # File output stream
116 class OFStream
117 super FStream
118 super OStream
119
120 redef fun write(s)
121 do
122 assert _writable
123 write_native(s.to_cstring, s.length)
124 end
125
126 redef fun is_writable do return _writable
127
128 redef fun close
129 do
130 var i = _file.io_close
131 _writable = false
132 end
133
134 # Is the file open in write mode
135 var _writable: Bool
136
137 # Write `len` bytes from `native`.
138 private fun write_native(native: NativeString, len: Int)
139 do
140 assert _writable
141 var err = _file.io_write(native, len)
142 if err != len then
143 # Big problem
144 printn("Problem in writing : ", err, " ", len, "\n")
145 end
146 end
147
148 # Open the file at `path` for writing.
149 init open(path: String)
150 do
151 _file = new NativeFile.io_open_write(path.to_cstring)
152 assert cant_open_file: _file != null
153 _path = path
154 _writable = true
155 end
156
157 private init do end
158 private init without_file do end
159 end
160
161 ###############################################################################
162
163 class Stdin
164 super IFStream
165 private init do
166 _file = new NativeFile.native_stdin
167 _path = "/dev/stdin"
168 prepare_buffer(1)
169 end
170
171 # Is these something to read? (non blocking)
172 # FIXME: should be generalized
173 fun poll_in: Bool is extern "file_stdin_poll_in"
174 end
175
176 class Stdout
177 super OFStream
178 private init do
179 _file = new NativeFile.native_stdout
180 _path = "/dev/stdout"
181 _writable = true
182 end
183 end
184
185 class Stderr
186 super OFStream
187 private init do
188 _file = new NativeFile.native_stderr
189 _path = "/dev/stderr"
190 _writable = true
191 end
192 end
193
194 ###############################################################################
195
196 redef class String
197 # return true if a file with this names exists
198 fun file_exists: Bool do return to_cstring.file_exists
199
200 fun file_stat: FileStat do return to_cstring.file_stat
201 fun file_lstat: FileStat do return to_cstring.file_lstat
202
203 # Remove a file, return true if success
204 fun file_delete: Bool do return to_cstring.file_delete
205
206 # remove the trailing extension "ext"
207 fun strip_extension(ext: String): String
208 do
209 if has_suffix(ext) then
210 return substring(0, length - ext.length)
211 end
212 return self
213 end
214
215 # Extract the basename of a path and remove the extension
216 fun basename(ext: String): String
217 do
218 var pos = last_index_of_from('/', _length - 1)
219 var n = self
220 if pos >= 0 then
221 n = substring_from(pos+1)
222 end
223 return n.strip_extension(ext)
224 end
225
226 # Extract the dirname of a path
227 fun dirname: String
228 do
229 var pos = last_index_of_from('/', _length - 1)
230 if pos >= 0 then
231 return substring(0, pos)
232 else
233 return "."
234 end
235 end
236
237 # Simplify a file path by remove useless ".", removing "//", and resolving ".."
238 # ".." are not resolved if they start the path
239 # starting "/" is not removed
240 # trainling "/" is removed
241 #
242 # Note that the method only wonrk on the string:
243 # * no I/O access is performed
244 # * the validity of the path is not checked
245 #
246 # assert "some/./complex/../../path/from/../to/a////file//".simplify_path == "path/to/a/file"
247 # assert "../dir/file".simplify_path == "../dir/file"
248 # assert "dir/../../".simplify_path == ".."
249 # assert "//absolute//path/".simplify_path == "/absolute/path"
250 fun simplify_path: String
251 do
252 var a = self.split_with("/")
253 var a2 = new Array[String]
254 for x in a do
255 if x == "." then continue
256 if x == "" and not a2.is_empty then continue
257 if x == ".." and not a2.is_empty and a2.last != ".." then
258 a2.pop
259 continue
260 end
261 a2.push(x)
262 end
263 return a2.join("/")
264 end
265
266 # Correctly join two path using the directory separator.
267 #
268 # Using a standard "{self}/{path}" does not work when `self` is the empty string.
269 # This method ensure that the join is valid.
270 #
271 # assert "hello".join_path("world") == "hello/world"
272 # assert "hel/lo".join_path("wor/ld") == "hel/lo/wor/ld"
273 # assert "".join_path("world") == "world"
274 # assert "/hello".join_path("/world") == "/world"
275 #
276 # Note: you may want to use `simplify_path` on the result
277 #
278 # Note: I you want to join a great number of path, you can write
279 #
280 # [p1, p2, p3, p4].join("/")
281 fun join_path(path: String): String
282 do
283 if path.is_empty then return self
284 if self.is_empty then return path
285 if path[0] == '/' then return path
286 return "{self}/{path}"
287 end
288
289 # Create a directory (and all intermediate directories if needed)
290 fun mkdir
291 do
292 var dirs = self.split_with("/")
293 var path = new Buffer
294 if dirs.is_empty then return
295 if dirs[0].is_empty then
296 # it was a starting /
297 path.add('/')
298 end
299 for d in dirs do
300 if d.is_empty then continue
301 path.append(d)
302 path.add('/')
303 path.to_s.to_cstring.file_mkdir
304 end
305 end
306
307 # Return right-most extension (without the dot)
308 fun file_extension : nullable String
309 do
310 var last_slash = last_index_of('.')
311 if last_slash >= 0 then
312 return substring( last_slash+1, length )
313 else
314 return null
315 end
316 end
317
318 # returns files contained within the directory represented by self
319 fun files : Set[ String ] is extern import HashSet, HashSet::add, NativeString::to_s, String::to_cstring, HashSet[String] as( Set[String] ), String as( Object )
320 end
321
322 redef class NativeString
323 private fun file_exists: Bool is extern "string_NativeString_NativeString_file_exists_0"
324 private fun file_stat: FileStat is extern "string_NativeString_NativeString_file_stat_0"
325 private fun file_lstat: FileStat `{
326 struct stat* stat_element;
327 int res;
328 stat_element = malloc(sizeof(struct stat));
329 res = lstat(recv, stat_element);
330 if (res == -1) return NULL;
331 return stat_element;
332 `}
333 private fun file_mkdir: Bool is extern "string_NativeString_NativeString_file_mkdir_0"
334 private fun file_delete: Bool is extern "string_NativeString_NativeString_file_delete_0"
335 end
336
337 extern FileStat `{ struct stat * `}
338 # This class is system dependent ... must reify the vfs
339 fun mode: Int is extern "file_FileStat_FileStat_mode_0"
340 fun atime: Int is extern "file_FileStat_FileStat_atime_0"
341 fun ctime: Int is extern "file_FileStat_FileStat_ctime_0"
342 fun mtime: Int is extern "file_FileStat_FileStat_mtime_0"
343 fun size: Int is extern "file_FileStat_FileStat_size_0"
344
345 fun is_reg: Bool `{ return S_ISREG(recv->st_mode); `}
346 fun is_dir: Bool `{ return S_ISDIR(recv->st_mode); `}
347 fun is_chr: Bool `{ return S_ISCHR(recv->st_mode); `}
348 fun is_blk: Bool `{ return S_ISBLK(recv->st_mode); `}
349 fun is_fifo: Bool `{ return S_ISFIFO(recv->st_mode); `}
350 fun is_lnk: Bool `{ return S_ISLNK(recv->st_mode); `}
351 fun is_sock: Bool `{ return S_ISSOCK(recv->st_mode); `}
352 end
353
354 # Instance of this class are standard FILE * pointers
355 private extern NativeFile
356 fun io_read(buf: NativeString, len: Int): Int is extern "file_NativeFile_NativeFile_io_read_2"
357 fun io_write(buf: NativeString, len: Int): Int is extern "file_NativeFile_NativeFile_io_write_2"
358 fun io_close: Int is extern "file_NativeFile_NativeFile_io_close_0"
359 fun file_stat: FileStat is extern "file_NativeFile_NativeFile_file_stat_0"
360
361 new io_open_read(path: NativeString) is extern "file_NativeFileCapable_NativeFileCapable_io_open_read_1"
362 new io_open_write(path: NativeString) is extern "file_NativeFileCapable_NativeFileCapable_io_open_write_1"
363 new native_stdin is extern "file_NativeFileCapable_NativeFileCapable_native_stdin_0"
364 new native_stdout is extern "file_NativeFileCapable_NativeFileCapable_native_stdout_0"
365 new native_stderr is extern "file_NativeFileCapable_NativeFileCapable_native_stderr_0"
366 end
367
368 # Standard input.
369 fun stdin: Stdin do return once new Stdin
370
371 # Standard output.
372 fun stdout: OFStream do return once new Stdout
373
374 # Standard output for error.
375 fun stderr: OFStream do return once new Stderr