Merge remote branch 'alexis/wip'
[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 # This module handle file input and output
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 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 end
170
171 class Stdout
172 super OFStream
173 private init do
174 _file = new NativeFile.native_stdout
175 _path = "/dev/stdout"
176 _writable = true
177 end
178 end
179
180 class Stderr
181 super OFStream
182 private init do
183 _file = new NativeFile.native_stderr
184 _path = "/dev/stderr"
185 _writable = true
186 end
187 end
188
189 ###############################################################################
190
191 redef class String
192 # return true if a file with this names exists
193 fun file_exists: Bool do return to_cstring.file_exists
194
195 fun file_stat: FileStat do return to_cstring.file_stat
196
197 # Remove a file, return true if success
198 fun file_delete: Bool do return to_cstring.file_delete
199
200 # remove the trailing extension "ext"
201 fun strip_extension(ext: String): String
202 do
203 if has_suffix(ext) then
204 return substring(0, length - ext.length)
205 end
206 return self
207 end
208
209 # Extract the basename of a path and remove the extension
210 fun basename(ext: String): String
211 do
212 var pos = last_index_of_from('/', _length - 1)
213 var n = self
214 if pos >= 0 then
215 n = substring_from(pos+1)
216 end
217 return n.strip_extension(ext)
218 end
219
220 # Extract the dirname of a path
221 fun dirname: String
222 do
223 var pos = last_index_of_from('/', _length - 1)
224 if pos >= 0 then
225 return substring(0, pos)
226 else
227 return "."
228 end
229 end
230
231 # Simplify a file path by remove useless ".", removing "//", and resolving ".."
232 # ".." are not resolved if they start the path
233 # starting "/" is not removed
234 # trainling "/" is removed
235 #
236 # Note that the method only wonrk on the string:
237 # * no I/O access is performed
238 # * the validity of the path is not checked
239 #
240 # "some/./complex/../../path/from/../to/a////file//".simplify_path # -> "path/to/a/file"
241 # "../dir/file" # -> "../dir/file"
242 # "dir/../../" # -> ".."
243 # "//absolute//path/" # -> "/absolute/path"
244 fun simplify_path: String
245 do
246 var a = self.split_with("/")
247 var a2 = new Array[String]
248 for x in a do
249 if x == "." then continue
250 if x == "" and not a2.is_empty then continue
251 if x == ".." and not a2.is_empty and a2.last != ".." then
252 a2.pop
253 continue
254 end
255 a2.push(x)
256 end
257 return a2.join("/")
258 end
259
260 # Correctly join two path using the directory separator.
261 #
262 # Using a standard "{self}/{path}" does not work when `self' is the empty string.
263 # This method ensure that the join is valid.
264 #
265 # "hello".join_path("world") # -> "hello/world"
266 # "hel/lo".join_path("wor/ld") # -> "hel/lo/wor/ld"
267 # "".join_path("world") # -> "world"
268 # "/hello".join_path("/world") # -> "/world"
269 #
270 # Note: you may want to use `simplify_path' on the result
271 #
272 # Note: I you want to join a great number of path, you can write
273 #
274 # [p1, p2, p3, p4].join("/")
275 fun join_path(path: String): String
276 do
277 if path.is_empty then return self
278 if self.is_empty then return path
279 if path[0] == '/' then return path
280 return "{self}/{path}"
281 end
282
283 # Create a directory (and all intermediate directories if needed)
284 fun mkdir
285 do
286 var dirs = self.split_with("/")
287 var path = new Buffer
288 if dirs.is_empty then return
289 if dirs[0].is_empty then
290 # it was a starting /
291 path.add('/')
292 end
293 for d in dirs do
294 if d.is_empty then continue
295 path.append(d)
296 path.add('/')
297 path.to_s.to_cstring.file_mkdir
298 end
299 end
300
301 # Return right-most extension (without the dot)
302 fun file_extension : nullable String
303 do
304 var last_slash = last_index_of('.')
305 if last_slash >= 0 then
306 return substring( last_slash+1, length )
307 else
308 return null
309 end
310 end
311
312 # returns files contained within the directory represented by self
313 fun files : Set[ String ] is extern import HashSet, HashSet::add, String::from_cstring, String::to_cstring, HashSet[String] as( Set[String] ), String as( Object )
314 end
315
316 redef class NativeString
317 private fun file_exists: Bool is extern "string_NativeString_NativeString_file_exists_0"
318 private fun file_stat: FileStat is extern "string_NativeString_NativeString_file_stat_0"
319 private fun file_mkdir: Bool is extern "string_NativeString_NativeString_file_mkdir_0"
320 private fun file_delete: Bool is extern "string_NativeString_NativeString_file_delete_0"
321 end
322
323 extern FileStat
324 # This class is system dependent ... must reify the vfs
325 fun mode: Int is extern "file_FileStat_FileStat_mode_0"
326 fun atime: Int is extern "file_FileStat_FileStat_atime_0"
327 fun ctime: Int is extern "file_FileStat_FileStat_ctime_0"
328 fun mtime: Int is extern "file_FileStat_FileStat_mtime_0"
329 fun size: Int is extern "file_FileStat_FileStat_size_0"
330 end
331
332 # Instance of this class are standard FILE * pointers
333 private extern NativeFile
334 fun io_read(buf: NativeString, len: Int): Int is extern "file_NativeFile_NativeFile_io_read_2"
335 fun io_write(buf: NativeString, len: Int): Int is extern "file_NativeFile_NativeFile_io_write_2"
336 fun io_close: Int is extern "file_NativeFile_NativeFile_io_close_0"
337 fun file_stat: FileStat is extern "file_NativeFile_NativeFile_file_stat_0"
338
339 new io_open_read(path: NativeString) is extern "file_NativeFileCapable_NativeFileCapable_io_open_read_1"
340 new io_open_write(path: NativeString) is extern "file_NativeFileCapable_NativeFileCapable_io_open_write_1"
341 new native_stdin is extern "file_NativeFileCapable_NativeFileCapable_native_stdin_0"
342 new native_stdout is extern "file_NativeFileCapable_NativeFileCapable_native_stdout_0"
343 new native_stderr is extern "file_NativeFileCapable_NativeFileCapable_native_stderr_0"
344 end
345
346 # Standard input.
347 fun stdin: IFStream do return once new Stdin
348
349 # Standard output.
350 fun stdout: OFStream do return once new Stdout
351
352 # Standard output for error.
353 fun stderr: OFStream do return once new Stderr