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