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