X-Git-Url: http://nitlanguage.org diff --git a/lib/core/file.nit b/lib/core/file.nit index def1cd2..c78f6d4 100644 --- a/lib/core/file.nit +++ b/lib/core/file.nit @@ -28,8 +28,16 @@ in "C Header" `{ #include #include #include - #include #include +#ifndef _WIN32 + #include +#endif +`} + +in "C" `{ +#ifdef _WIN32 + #include +#endif `} # `Stream` used to interact with a File or FileDescriptor @@ -49,23 +57,24 @@ abstract class FileStream # Return null in case of error fun file_stat: nullable FileStat do - var stat = _file.file_stat + var stat = _file.as(not null).file_stat if stat.address_is_null then return null return new FileStat(stat) end # File descriptor of this file - fun fd: Int do return _file.fileno + fun fd: Int do return _file.as(not null).fileno redef fun close do - if _file == null then return - if _file.address_is_null then + var file = _file + if file == null then return + if file.address_is_null then if last_error != null then return last_error = new IOError("Cannot close unopened file") return end - var i = _file.io_close + var i = file.io_close if i != 0 then last_error = new IOError("Close failed due to error {sys.errno.strerror}") end @@ -83,7 +92,7 @@ abstract class FileStream # * `buffer_mode_none` fun set_buffering_mode(buf_size, mode: Int) do if buf_size <= 0 then buf_size = 512 - if _file.set_buffering_type(buf_size, mode) != 0 then + if _file.as(not null).set_buffering_type(buf_size, mode) != 0 then last_error = new IOError("Error while changing buffering type for FileStream, returned error {sys.errno.strerror}") end end @@ -105,10 +114,10 @@ class FileReader # assert l == f.read_line fun reopen do - if not eof and not _file.address_is_null then close + if not eof and not _file.as(not null).address_is_null then close last_error = null - _file = new NativeFile.io_open_read(path.to_cstring) - if _file.address_is_null then + _file = new NativeFile.io_open_read(path.as(not null).to_cstring) + if _file.as(not null).address_is_null then last_error = new IOError("Cannot open `{path.as(not null)}`: {sys.errno.strerror}") end_reached = true return @@ -126,8 +135,8 @@ class FileReader redef fun fill_buffer do - var nb = _file.io_read(_buffer, _buffer_capacity) - if last_error == null and _file.ferror then + var nb = _file.as(not null).io_read(_buffer, _buffer_capacity) + if last_error == null and _file.as(not null).ferror then last_error = new IOError("Cannot read `{path.as(not null)}`: {sys.errno.strerror}") end_reached = true end @@ -158,7 +167,7 @@ class FileReader self.path = path prepare_buffer(100) _file = new NativeFile.io_open_read(path.to_cstring) - if _file.address_is_null then + if _file.as(not null).address_is_null then last_error = new IOError("Cannot open `{path}`: {sys.errno.strerror}") end_reached = true end @@ -171,7 +180,7 @@ class FileReader self.path = "" prepare_buffer(1) _file = fd.fd_to_stream(read_only) - if _file.address_is_null then + if _file.as(not null).address_is_null then last_error = new IOError("Error: Converting fd {fd} to stream failed with '{sys.errno.strerror}'") end_reached = true end @@ -187,8 +196,12 @@ class FileReader end private fun native_poll_in(fd: Int): Int `{ +#ifndef _WIN32 struct pollfd fds = {(int)fd, POLLIN, 0}; return poll(&fds, 1, 0); +#else + return 0; +#endif `} end @@ -223,13 +236,13 @@ class FileWriter last_error = new IOError("Cannot write to non-writable stream") return end - if _file.address_is_null then + if _file.as(not null).address_is_null then last_error = new IOError("Writing on a null stream") _is_writable = false return end - var err = _file.write_byte(value) + var err = _file.as(not null).write_byte(value) if err != 1 then # Big problem last_error = new IOError("Problem writing a byte: {err}") @@ -244,19 +257,19 @@ class FileWriter redef var is_writable = false # Write `len` bytes from `native`. - private fun write_native(native: NativeString, from, len: Int) + private fun write_native(native: CString, from, len: Int) do if last_error != null then return if not _is_writable then last_error = new IOError("Cannot write to non-writable stream") return end - if _file.address_is_null then + if _file.as(not null).address_is_null then last_error = new IOError("Writing on a null stream") _is_writable = false return end - var err = _file.io_write(native, from, len) + var err = _file.as(not null).io_write(native, from, len) if err != len then # Big problem last_error = new IOError("Problem in writing : {err} {len} \n") @@ -269,7 +282,7 @@ class FileWriter _file = new NativeFile.io_open_write(path.to_cstring) self.path = path _is_writable = true - if _file.address_is_null then + if _file.as(not null).address_is_null then last_error = new IOError("Cannot open `{path}`: {sys.errno.strerror}") is_writable = false end @@ -280,7 +293,7 @@ class FileWriter self.path = "" _file = fd.fd_to_stream(wipe_write) _is_writable = true - if _file.address_is_null then + if _file.as(not null).address_is_null then last_error = new IOError("Error: Opening stream from file descriptor {fd} failed with '{sys.errno.strerror}'") _is_writable = false end @@ -291,20 +304,20 @@ redef class Int # Creates a file stream from a file descriptor `fd` using the file access `mode`. # # NOTE: The `mode` specified must be compatible with the one used in the file descriptor. - private fun fd_to_stream(mode: NativeString): NativeFile `{ + private fun fd_to_stream(mode: CString): NativeFile `{ return fdopen((int)self, mode); `} end # Constant for read-only file streams -private fun read_only: NativeString do return once "r".to_cstring +private fun read_only: CString do return once "r".to_cstring # Constant for write-only file streams # # If a stream is opened on a file with this method, # it will wipe the previous file if any. # Else, it will create the file. -private fun wipe_write: NativeString do return once "w".to_cstring +private fun wipe_write: CString do return once "w".to_cstring ############################################################################### @@ -625,6 +638,22 @@ class Path return res end + # Correctly join `self` with `subpath` using the directory separator. + # + # Using a standard "{self}/{path}" does not work in the following cases: + # + # * `self` is empty. + # * `path` starts with `'/'`. + # + # This method ensures that the join is valid. + # + # var hello = "hello".to_path + # assert (hello/"world").to_s == "hello/world" + # assert ("hel/lo".to_path / "wor/ld").to_s == "hel/lo/wor/ld" + # assert ("".to_path / "world").to_s == "world" + # assert (hello / "/world").to_s == "/world" + # assert ("hello/".to_path / "world").to_s == "hello/world" + fun /(subpath: String): Path do return new Path(path / subpath) # Lists the files contained within the directory at `path`. # @@ -654,35 +683,54 @@ class Path # readdir cannot fail, so null means end of list break end - var name = de.to_s_with_copy + var name = de.to_s if name == "." or name == ".." then continue - res.add new Path(path / name) + res.add self / name end d.closedir return res end - # Delete a directory and all of its content + # Is `self` the path to an existing directory ? + # + # ~~~nit + # assert ".".to_path.is_dir + # assert not "/etc/issue".to_path.is_dir + # assert not "/should/not/exist".to_path.is_dir + # ~~~ + fun is_dir: Bool do + var st = stat + if st == null then return false + return st.is_dir + end + + # Recursively delete a directory and all of its content # # Does not go through symbolic links and may get stuck in a cycle if there # is a cycle in the file system. # - # `last_error` is updated to contains the error information on error, and null on success. - # The method does not stop on the first error and try to remove most file and directories. + # `last_error` is updated with the first encountered error, or null on success. + # The method does not stop on the first error and tries to remove the most files and directories. # # ~~~ # var path = "/does/not/exists/".to_path # path.rmdir # assert path.last_error != null + # + # path = "/tmp/path/to/create".to_path + # path.to_s.mkdir + # assert path.exists + # path.rmdir + # assert path.last_error == null # ~~~ fun rmdir do - last_error = null + var first_error = null for file in self.files do var stat = file.link_stat if stat == null then - last_error = file.last_error + if first_error == null then first_error = file.last_error continue end if stat.is_dir then @@ -691,15 +739,16 @@ class Path else file.delete end - if last_error == null then last_error = file.last_error + if first_error == null then first_error = file.last_error end # Delete the directory itself if things are fine - if last_error == null then - if path.to_cstring.rmdir then - last_error = new IOError("Cannot remove `{self}`: {sys.errno.strerror}") + if first_error == null then + if not path.to_cstring.rmdir then + first_error = new IOError("Cannot remove `{self}`: {sys.errno.strerror}") end end + self.last_error = first_error end redef fun ==(other) do return other isa Path and simplified.path == other.simplified.path @@ -844,14 +893,14 @@ redef class Text private fun write_native_to(s: FileWriter) do - for i in substrings do s.write_native(i.to_cstring, 0, i.bytelen) + for i in substrings do s.write_native(i.to_cstring, 0, i.byte_length) end -end -redef class String # return true if a file with this names exists fun file_exists: Bool do return to_cstring.file_exists +end +redef class String # The status of a file. see POSIX stat(2). fun file_stat: nullable FileStat do @@ -920,13 +969,24 @@ redef class String # assert "".basename("") == "" fun basename(extension: nullable String): String do - var l = length - 1 # Index of the last char - while l > 0 and self.chars[l] == '/' do l -= 1 # remove all trailing `/` - if l == 0 then return "/" - var pos = chars.last_index_of_from('/', l) var n = self - if pos >= 0 then - n = substring(pos+1, l-pos) + if is_windows then + var l = length - 1 # Index of the last char + while l > 0 and (self.chars[l] == '/' or chars[l] == '\\') do l -= 1 # remove all trailing `/` + if l == 0 then return "/" + var pos = chars.last_index_of_from('/', l) + pos = pos.max(last_index_of_from('\\', l)) + if pos >= 0 then + n = substring(pos+1, l-pos) + end + else + var l = length - 1 # Index of the last char + while l > 0 and self.chars[l] == '/' do l -= 1 # remove all trailing `/` + if l == 0 then return "/" + var pos = chars.last_index_of_from('/', l) + if pos >= 0 then + n = substring(pos+1, l-pos) + end end if extension != null then @@ -964,7 +1024,7 @@ redef class String fun realpath: String do var cs = to_cstring.file_realpath assert file_exists - var res = cs.to_s_with_copy + var res = cs.to_s cs.free return res end @@ -1163,15 +1223,21 @@ redef class String path.add('/') end var error: nullable Error = null - for d in dirs do + for i in [0 .. dirs.length - 1[ do + var d = dirs[i] if d.is_empty then continue path.append(d) path.add('/') - var res = path.to_s.to_cstring.file_mkdir(mode) + if path.file_exists then continue + var res = path.to_cstring.file_mkdir(mode) if not res and error == null then error = new IOError("Cannot create directory `{path}`: {sys.errno.strerror}") end end + var res = self.to_cstring.file_mkdir(mode) + if not res and error == null then + error = new IOError("Cannot create directory `{path}`: {sys.errno.strerror}") + end return error end @@ -1258,7 +1324,7 @@ redef class String loop var de = d.readdir if de.address_is_null then break - var name = de.to_s_with_copy + var name = de.to_s if name == "." or name == ".." then continue res.add name end @@ -1271,7 +1337,7 @@ end redef class FlatString redef fun write_native_to(s) do - s.write_native(items, first_byte, bytelen) + s.write_native(items, first_byte, byte_length) end redef fun file_extension do @@ -1289,27 +1355,46 @@ redef class FlatString end redef fun basename(extension) do - var l = last_byte - var its = _items - var min = _first_byte - var sl = '/'.ascii - while l > min and its[l] == sl do l -= 1 - if l == min then return "/" - var ns = l - while ns >= min and its[ns] != sl do ns -= 1 - var bname = new FlatString.with_infos(its, l - ns, ns + 1) + var bname + if is_windows then + var l = last_byte + var its = _items + var min = _first_byte + var sl = '/'.ascii + var ls = '\\'.ascii + while l > min and (its[l] == sl or its[l] == ls) do l -= 1 + if l == min then return "\\" + var ns = l + while ns >= min and its[ns] != sl and its[ns] != ls do ns -= 1 + bname = new FlatString.with_infos(its, l - ns, ns + 1) + else + var l = last_byte + var its = _items + var min = _first_byte + var sl = '/'.ascii + while l > min and its[l] == sl do l -= 1 + if l == min then return "/" + var ns = l + while ns >= min and its[ns] != sl do ns -= 1 + bname = new FlatString.with_infos(its, l - ns, ns + 1) + end return if extension != null then bname.strip_extension(extension) else bname end end -redef class NativeString +redef class CString private fun file_exists: Bool `{ +#ifdef _WIN32 + DWORD attribs = GetFileAttributesA(self); + return attribs != INVALID_FILE_ATTRIBUTES; +#else FILE *hdl = fopen(self,"r"); if(hdl != NULL){ fclose(hdl); } return hdl != NULL; +#endif `} private fun file_stat: NativeFileStat `{ @@ -1323,15 +1408,26 @@ redef class NativeString `} private fun file_lstat: NativeFileStat `{ +#ifdef _WIN32 + // FIXME use a higher level abstraction to support WIN32 + return NULL; +#else struct stat* stat_element; int res; stat_element = malloc(sizeof(struct stat)); res = lstat(self, stat_element); if (res == -1) return NULL; return stat_element; +#endif `} - private fun file_mkdir(mode: Int): Bool `{ return !mkdir(self, mode); `} + private fun file_mkdir(mode: Int): Bool `{ +#ifdef _WIN32 + return !mkdir(self); +#else + return !mkdir(self, mode); +#endif + `} private fun rmdir: Bool `{ return !rmdir(self); `} @@ -1341,7 +1437,16 @@ redef class NativeString private fun file_chdir: Bool `{ return !chdir(self); `} - private fun file_realpath: NativeString `{ return realpath(self, NULL); `} + private fun file_realpath: CString `{ +#ifdef _WIN32 + DWORD len = GetFullPathName(self, 0, NULL, NULL); + char *buf = malloc(len+1); // FIXME don't leak memory + len = GetFullPathName(self, len+1, buf, NULL); + return buf; +#else + return realpath(self, NULL); +#endif + `} end # This class is system dependent ... must reify the vfs @@ -1378,19 +1483,31 @@ private extern class NativeFileStat `{ struct stat * `} fun is_fifo: Bool `{ return S_ISFIFO(self->st_mode); `} # Returns true if the type is a link - fun is_lnk: Bool `{ return S_ISLNK(self->st_mode); `} + fun is_lnk: Bool `{ +#ifdef _WIN32 + return 0; +#else + return S_ISLNK(self->st_mode); +#endif + `} # Returns true if the type is a socket - fun is_sock: Bool `{ return S_ISSOCK(self->st_mode); `} + fun is_sock: Bool `{ +#ifdef _WIN32 + return 0; +#else + return S_ISSOCK(self->st_mode); +#endif + `} end # Instance of this class are standard FILE * pointers private extern class NativeFile `{ FILE* `} - fun io_read(buf: NativeString, len: Int): Int `{ + fun io_read(buf: CString, len: Int): Int `{ return fread(buf, 1, len, self); `} - fun io_write(buf: NativeString, from, len: Int): Int `{ + fun io_write(buf: CString, from, len: Int): Int `{ return fwrite(buf+from, 1, len, self); `} @@ -1423,9 +1540,9 @@ private extern class NativeFile `{ FILE* `} return setvbuf(self, NULL, (int)mode, buf_length); `} - new io_open_read(path: NativeString) `{ return fopen(path, "r"); `} + new io_open_read(path: CString) `{ return fopen(path, "r"); `} - new io_open_write(path: NativeString) `{ return fopen(path, "w"); `} + new io_open_write(path: CString) `{ return fopen(path, "w"); `} new native_stdin `{ return stdin; `} @@ -1438,13 +1555,13 @@ end private extern class NativeDir `{ DIR* `} # Open a directory - new opendir(path: NativeString) `{ return opendir(path); `} + new opendir(path: CString) `{ return opendir(path); `} # Close a directory fun closedir `{ closedir(self); `} # Read the next directory entry - fun readdir: NativeString `{ + fun readdir: CString `{ struct dirent *de; de = readdir(self); if (!de) return NULL; @@ -1498,6 +1615,9 @@ redef class Sys private fun intern_poll(in_fds: Array[Int], out_fds: Array[Int]): nullable Int import Array[Int].length, Array[Int].[], Int.as(nullable Int) `{ +#ifndef _WIN32 + // FIXME use a higher level abstraction to support WIN32 + int in_len, out_len, total_len; struct pollfd *c_fds; int i; @@ -1542,6 +1662,7 @@ redef class Sys } else if ( result < 0 ) fprintf( stderr, "Error in Stream:poll: %s\n", strerror( errno ) ); +#endif return null_Int(); `} @@ -1585,4 +1706,4 @@ end # Return the working (current) directory fun getcwd: String do return native_getcwd.to_s -private fun native_getcwd: NativeString `{ return getcwd(NULL, 0); `} +private fun native_getcwd: CString `{ return getcwd(NULL, 0); `}