diff --git a/src/file.cr b/src/file.cr index dada3e7dab4c..0a218bcf91ec 100644 --- a/src/file.cr +++ b/src/file.cr @@ -134,41 +134,41 @@ class File < IO::FileDescriptor set_encoding(encoding, invalid: invalid) if encoding end - # Opens the file named by *filename*. - # - # *mode* must be one of the following file open modes: - # - # ```text - # Mode | Description - # -----------+------------------------------------------------------ - # r rb | Read-only, starts at the beginning of the file. - # r+ r+b rb+ | Read-write, starts at the beginning of the file. - # w wb | Write-only, truncates existing file to zero length or - # | creates a new file if the file doesn't exist. - # w+ w+b wb+ | Read-write, truncates existing file to zero length or - # | creates a new file if the file doesn't exist. - # a ab | Write-only, all writes seek to the end of the file, - # | creates a new file if the file doesn't exist. - # a+ a+b ab+ | Read-write, all writes seek to the end of the file, - # | creates a new file if the file doesn't exist. - # ``` - # - # Line endings are preserved on all platforms. The `b` mode flag has no - # effect; it is provided only for POSIX compatibility. - # - # NOTE: The *blocking* arg is deprecated since Crystal 1.17. It used to be - # true by default to denote a regular disk file (always ready in system event - # loops) and could be set to false when the file was known to be a fifo, pipe, - # or character device (for example `/dev/tty`). The event loop now chooses - # the appropriate blocking mode automatically and there are no reasons to - # change it anymore. - # - # NOTE: On macOS files are always opened in blocking mode because non-blocking - # FIFO files don't work — the OS exhibits issues with readiness notifications. {% begin %} - def self.new(filename : Path | String, mode = "r", perm = DEFAULT_CREATE_PERMISSIONS, encoding = nil, invalid = nil, {% if compare_versions(Crystal::VERSION, "1.5.0") >= 0 %} @[Deprecated] {% end %} blocking = nil) - new_internal filename, mode, perm, encoding, invalid, blocking - end + # Opens the file named by *filename*. + # + # *mode* must be one of the following file open modes: + # + # ```text + # Mode | Description + # -----------+------------------------------------------------------ + # r rb | Read-only, starts at the beginning of the file. + # r+ r+b rb+ | Read-write, starts at the beginning of the file. + # w wb | Write-only, truncates existing file to zero length or + # | creates a new file if the file doesn't exist. + # w+ w+b wb+ | Read-write, truncates existing file to zero length or + # | creates a new file if the file doesn't exist. + # a ab | Write-only, all writes seek to the end of the file, + # | creates a new file if the file doesn't exist. + # a+ a+b ab+ | Read-write, all writes seek to the end of the file, + # | creates a new file if the file doesn't exist. + # ``` + # + # Line endings are preserved on all platforms. The `b` mode flag has no + # effect; it is provided only for POSIX compatibility. + # + # NOTE: The *blocking* arg is deprecated since Crystal 1.17. It used to be + # true by default to denote a regular disk file (always ready in system event + # loops) and could be set to false when the file was known to be a fifo, pipe, + # or character device (for example `/dev/tty`). The event loop now chooses + # the appropriate blocking mode automatically and there are no reasons to + # change it anymore. + # + # NOTE: On macOS files are always opened in blocking mode because non-blocking + # FIFO files don't work — the OS exhibits issues with readiness notifications. + def self.new(filename : Path | String, mode = "r", perm = DEFAULT_CREATE_PERMISSIONS, encoding = nil, invalid = nil, {% if compare_versions(Crystal::VERSION, "1.5.0") >= 0 %} @[Deprecated] {% end %} blocking = nil) + new_internal filename, mode, perm, encoding, invalid, blocking + end {% end %} protected def self.new_internal(filename, mode = "r", perm = DEFAULT_CREATE_PERMISSIONS, encoding = nil, invalid = nil, blocking = nil) @@ -511,25 +511,25 @@ class File < IO::FileDescriptor Crystal::System::File.readlink(path.to_s) { return nil } end - # Opens the file named by *filename*. If a file is being created, its initial - # permissions may be set using the *perm* parameter. - # - # See `self.new` for what *mode* can be. {% begin %} - def self.open(filename : Path | String, mode = "r", perm = DEFAULT_CREATE_PERMISSIONS, encoding = nil, invalid = nil, {% if compare_versions(Crystal::VERSION, "1.5.0") >= 0 %} @[Deprecated] {% end %} blocking = nil) : self - new_internal(filename.to_s, mode, perm, encoding, invalid, blocking) - end + # Opens the file named by *filename*. If a file is being created, its initial + # permissions may be set using the *perm* parameter. + # + # See `self.new` for what *mode* can be. + def self.open(filename : Path | String, mode = "r", perm = DEFAULT_CREATE_PERMISSIONS, encoding = nil, invalid = nil, {% if compare_versions(Crystal::VERSION, "1.5.0") >= 0 %} @[Deprecated] {% end %} blocking = nil) : self + new_internal(filename.to_s, mode, perm, encoding, invalid, blocking) + end {% end %} - # Opens the file named by *filename*. If a file is being created, its initial - # permissions may be set using the *perm* parameter. Then given block will be passed the opened - # file as an argument, the file will be automatically closed when the block returns. - # - # See `self.new` for what *mode* can be. {% begin %} - def self.open(filename : Path | String, mode = "r", perm = DEFAULT_CREATE_PERMISSIONS, encoding = nil, invalid = nil, {% if compare_versions(Crystal::VERSION, "1.5.0") >= 0 %} @[Deprecated] {% end %} blocking = nil, &) - open_internal(filename.to_s, mode, perm, encoding, invalid, blocking) { |file| yield file } - end + # Opens the file named by *filename*. If a file is being created, its initial + # permissions may be set using the *perm* parameter. Then given block will be passed the opened + # file as an argument, the file will be automatically closed when the block returns. + # + # See `self.new` for what *mode* can be. + def self.open(filename : Path | String, mode = "r", perm = DEFAULT_CREATE_PERMISSIONS, encoding = nil, invalid = nil, {% if compare_versions(Crystal::VERSION, "1.5.0") >= 0 %} @[Deprecated] {% end %} blocking = nil, &) + open_internal(filename.to_s, mode, perm, encoding, invalid, blocking) { |file| yield file } + end {% end %} protected def self.open_internal(filename, mode = "r", perm = DEFAULT_CREATE_PERMISSIONS, encoding = nil, invalid = nil, blocking = nil, &) @@ -541,103 +541,103 @@ class File < IO::FileDescriptor end end - # Returns the content of *filename* as a string. - # - # ``` - # File.write("bar", "foo") - # File.read("bar") # => "foo" - # ``` {% begin %} - def self.read(filename : Path | String, encoding = nil, invalid = nil, {% if compare_versions(Crystal::VERSION, "1.5.0") >= 0 %} @[Deprecated] {% end %} blocking = nil) : String - open_internal(filename, "r", blocking: blocking) do |file| - if encoding - file.set_encoding(encoding, invalid: invalid) - file.gets_to_end - else - # We try to read a string with an initialize capacity - # equal to the file's size, but the size might not be - # correct or even be zero (for example for /proc files) - size = file.size.to_i - size = 256 if size == 0 - String.build(size) do |io| - IO.copy(file, io) + # Returns the content of *filename* as a string. + # + # ``` + # File.write("bar", "foo") + # File.read("bar") # => "foo" + # ``` + def self.read(filename : Path | String, encoding = nil, invalid = nil, {% if compare_versions(Crystal::VERSION, "1.5.0") >= 0 %} @[Deprecated] {% end %} blocking = nil) : String + open_internal(filename, "r", blocking: blocking) do |file| + if encoding + file.set_encoding(encoding, invalid: invalid) + file.gets_to_end + else + # We try to read a string with an initialize capacity + # equal to the file's size, but the size might not be + # correct or even be zero (for example for /proc files) + size = file.size.to_i + size = 256 if size == 0 + String.build(size) do |io| + IO.copy(file, io) + end end end end - end {% end %} - # Yields each line in *filename* to the given block. - # - # ``` - # File.write("foobar", "foo\nbar") - # - # array = [] of String - # File.each_line("foobar") do |line| - # array << line - # end - # array # => ["foo", "bar"] - # ``` {% begin %} - def self.each_line(filename : Path | String, encoding = nil, invalid = nil, chomp = true, {% if compare_versions(Crystal::VERSION, "1.5.0") >= 0 %} @[Deprecated] {% end %} blocking = nil, &) - open_internal(filename, "r", encoding: encoding, invalid: invalid, blocking: blocking) do |file| - file.each_line(chomp: chomp) do |line| - yield line + # Yields each line in *filename* to the given block. + # + # ``` + # File.write("foobar", "foo\nbar") + # + # array = [] of String + # File.each_line("foobar") do |line| + # array << line + # end + # array # => ["foo", "bar"] + # ``` + def self.each_line(filename : Path | String, encoding = nil, invalid = nil, chomp = true, {% if compare_versions(Crystal::VERSION, "1.5.0") >= 0 %} @[Deprecated] {% end %} blocking = nil, &) + open_internal(filename, "r", encoding: encoding, invalid: invalid, blocking: blocking) do |file| + file.each_line(chomp: chomp) do |line| + yield line + end end end - end {% end %} - # Returns all lines in *filename* as an array of strings. - # - # ``` - # File.write("foobar", "foo\nbar") - # File.read_lines("foobar") # => ["foo", "bar"] - # ``` {% begin %} - def self.read_lines(filename : Path | String, encoding = nil, invalid = nil, chomp = true, {% if compare_versions(Crystal::VERSION, "1.5.0") >= 0 %} @[Deprecated] {% end %} blocking = nil) : Array(String) - lines = [] of String - open_internal(filename, "r", encoding: encoding, invalid: invalid, blocking: blocking) do |file| - file.each_line(chomp: chomp) do |line| - lines << line + # Returns all lines in *filename* as an array of strings. + # + # ``` + # File.write("foobar", "foo\nbar") + # File.read_lines("foobar") # => ["foo", "bar"] + # ``` + def self.read_lines(filename : Path | String, encoding = nil, invalid = nil, chomp = true, {% if compare_versions(Crystal::VERSION, "1.5.0") >= 0 %} @[Deprecated] {% end %} blocking = nil) : Array(String) + lines = [] of String + open_internal(filename, "r", encoding: encoding, invalid: invalid, blocking: blocking) do |file| + file.each_line(chomp: chomp) do |line| + lines << line + end end + lines end - lines - end {% end %} - # Writes the given *content* to *filename*. - # - # By default, an existing file will be overwritten. - # - # *filename* will be created if it does not already exist. - # - # ``` - # File.write("foo", "bar") - # File.write("foo", "baz", mode: "a") - # ``` - # - # NOTE: If the content is a `Slice(UInt8)`, those bytes will be written. - # If it's an `IO`, all bytes from the `IO` will be written. - # Otherwise, the string representation of *content* will be written - # (the result of invoking `to_s` on *content*). - # - # See `self.new` for what *mode* can be. {% begin %} - def self.write(filename : Path | String, content, perm = DEFAULT_CREATE_PERMISSIONS, encoding = nil, invalid = nil, mode = "w", {% if compare_versions(Crystal::VERSION, "1.5.0") >= 0 %} @[Deprecated] {% end %} blocking = nil) - open_internal(filename, mode, perm, encoding: encoding, invalid: invalid, blocking: blocking) do |file| - case content - when Bytes - file.sync = true - file.write(content) - when IO - file.sync = true - IO.copy(content, file) - else - file.print(content) + # Writes the given *content* to *filename*. + # + # By default, an existing file will be overwritten. + # + # *filename* will be created if it does not already exist. + # + # ``` + # File.write("foo", "bar") + # File.write("foo", "baz", mode: "a") + # ``` + # + # NOTE: If the content is a `Slice(UInt8)`, those bytes will be written. + # If it's an `IO`, all bytes from the `IO` will be written. + # Otherwise, the string representation of *content* will be written + # (the result of invoking `to_s` on *content*). + # + # See `self.new` for what *mode* can be. + def self.write(filename : Path | String, content, perm = DEFAULT_CREATE_PERMISSIONS, encoding = nil, invalid = nil, mode = "w", {% if compare_versions(Crystal::VERSION, "1.5.0") >= 0 %} @[Deprecated] {% end %} blocking = nil) + open_internal(filename, mode, perm, encoding: encoding, invalid: invalid, blocking: blocking) do |file| + case content + when Bytes + file.sync = true + file.write(content) + when IO + file.sync = true + IO.copy(content, file) + else + file.print(content) + end end end - end {% end %} # Copies the file *src* to the file *dst*. diff --git a/src/io/file_descriptor.cr b/src/io/file_descriptor.cr index 3bdf0d784bd1..caff9fa58088 100644 --- a/src/io/file_descriptor.cr +++ b/src/io/file_descriptor.cr @@ -45,20 +45,20 @@ class IO::FileDescriptor < IO write_timeout end - # Creates an IO::FileDescriptor from an existing system file descriptor or - # handle. - # - # This adopts *fd* into the IO system that will reconfigure it as per the - # event loop runtime requirements. - # - # NOTE: On Windows, the handle should have been created with - # `FILE_FLAG_OVERLAPPED`. {% begin %} - def self.new(fd : Handle, {% if compare_versions(Crystal::VERSION, "1.5.0") >= 0 %} @[Deprecated("Use IO::FileDescriptor.set_blocking instead.")] {% end %} blocking = nil, *, close_on_finalize = true) - file_descriptor = new(handle: fd, close_on_finalize: close_on_finalize) - file_descriptor.system_blocking_init(blocking) unless file_descriptor.closed? - file_descriptor - end + # Creates an IO::FileDescriptor from an existing system file descriptor or + # handle. + # + # This adopts *fd* into the IO system that will reconfigure it as per the + # event loop runtime requirements. + # + # NOTE: On Windows, the handle should have been created with + # `FILE_FLAG_OVERLAPPED`. + def self.new(fd : Handle, {% if compare_versions(Crystal::VERSION, "1.5.0") >= 0 %} @[Deprecated("Use IO::FileDescriptor.set_blocking instead.")] {% end %} blocking = nil, *, close_on_finalize = true) + file_descriptor = new(handle: fd, close_on_finalize: close_on_finalize) + file_descriptor.system_blocking_init(blocking) unless file_descriptor.closed? + file_descriptor + end {% end %} # :nodoc: diff --git a/src/socket.cr b/src/socket.cr index a768ba19d8fd..660827c8b6e7 100644 --- a/src/socket.cr +++ b/src/socket.cr @@ -49,38 +49,38 @@ class Socket < IO write_timeout end - # Creates a TCP socket. Consider using `TCPSocket` or `TCPServer` unless you - # need full control over the socket. {% begin %} - def self.tcp(family : Family, {% if compare_versions(Crystal::VERSION, "1.5.0") >= 0 %} @[Deprecated("Use Socket.set_blocking instead.")] {% end %} blocking = nil) : self - new(af: family, type: Type::STREAM, protocol: Protocol::TCP, blocking: blocking) - end + # Creates a TCP socket. Consider using `TCPSocket` or `TCPServer` unless you + # need full control over the socket. + def self.tcp(family : Family, {% if compare_versions(Crystal::VERSION, "1.5.0") >= 0 %} @[Deprecated("Use Socket.set_blocking instead.")] {% end %} blocking = nil) : self + new(af: family, type: Type::STREAM, protocol: Protocol::TCP, blocking: blocking) + end {% end %} - # Creates an UDP socket. Consider using `UDPSocket` unless you need full - # control over the socket. {% begin %} - def self.udp(family : Family, {% if compare_versions(Crystal::VERSION, "1.5.0") >= 0 %} @[Deprecated("Use Socket.set_blocking instead.")] {% end %} blocking = nil) : self - new(af: family, type: Type::DGRAM, protocol: Protocol::UDP, blocking: blocking) - end + # Creates an UDP socket. Consider using `UDPSocket` unless you need full + # control over the socket. + def self.udp(family : Family, {% if compare_versions(Crystal::VERSION, "1.5.0") >= 0 %} @[Deprecated("Use Socket.set_blocking instead.")] {% end %} blocking = nil) : self + new(af: family, type: Type::DGRAM, protocol: Protocol::UDP, blocking: blocking) + end {% end %} - # Creates an UNIX socket. Consider using `UNIXSocket` or `UNIXServer` unless - # you need full control over the socket. {% begin %} - def self.unix(type : Type = Type::STREAM, {% if compare_versions(Crystal::VERSION, "1.5.0") >= 0 %} @[Deprecated("Use Socket.set_blocking instead.")] {% end %} blocking = nil) : self - new(af: Family::UNIX, type: type, protocol: Protocol::IP, blocking: blocking) - end + # Creates an UNIX socket. Consider using `UNIXSocket` or `UNIXServer` unless + # you need full control over the socket. + def self.unix(type : Type = Type::STREAM, {% if compare_versions(Crystal::VERSION, "1.5.0") >= 0 %} @[Deprecated("Use Socket.set_blocking instead.")] {% end %} blocking = nil) : self + new(af: Family::UNIX, type: type, protocol: Protocol::IP, blocking: blocking) + end {% end %} - # Creates a socket. Consider using `TCPSocket`, `TCPServer`, `UDPSocket`, - # `UNIXSocket` or `UNIXServer` unless you need full control over the socket. {% begin %} - def initialize(family : Family, type : Type, protocol : Protocol = Protocol::IP, {% if compare_versions(Crystal::VERSION, "1.5.0") >= 0 %} @[Deprecated("Use Socket.set_blocking instead.")] {% end %} blocking = nil) - # This method is `#initialize` instead of `.new` because it is used as super - # constructor from subclasses. - initialize(af: family, type: type, protocol: protocol, blocking: blocking) - end + # Creates a socket. Consider using `TCPSocket`, `TCPServer`, `UDPSocket`, + # `UNIXSocket` or `UNIXServer` unless you need full control over the socket. + def initialize(family : Family, type : Type, protocol : Protocol = Protocol::IP, {% if compare_versions(Crystal::VERSION, "1.5.0") >= 0 %} @[Deprecated("Use Socket.set_blocking instead.")] {% end %} blocking = nil) + # This method is `#initialize` instead of `.new` because it is used as super + # constructor from subclasses. + initialize(af: family, type: type, protocol: protocol, blocking: blocking) + end {% end %} # :nodoc: @@ -93,20 +93,20 @@ class Socket < IO self.sync = true end - # Creates a Socket from an existing system file descriptor or socket handle. - # - # This adopts *fd* into the IO system that will reconfigure it as per the - # event loop runtime requirements. - # - # NOTE: On Windows, the handle must have been created with - # `WSA_FLAG_OVERLAPPED`. {% begin %} - def initialize(fd, @family : Family, @type : Type, @protocol : Protocol = Protocol::IP, {% if compare_versions(Crystal::VERSION, "1.5.0") >= 0 %} @[Deprecated("Use Socket.set_blocking instead.")] {% end %} blocking = nil) - initialize(handle: fd, family: family, type: type, protocol: protocol) - blocking = Crystal::EventLoop.default_socket_blocking? if blocking.nil? - Crystal::System::Socket.set_blocking(fd, blocking) unless blocking - self.sync = true - end + # Creates a Socket from an existing system file descriptor or socket handle. + # + # This adopts *fd* into the IO system that will reconfigure it as per the + # event loop runtime requirements. + # + # NOTE: On Windows, the handle must have been created with + # `WSA_FLAG_OVERLAPPED`. + def initialize(fd, @family : Family, @type : Type, @protocol : Protocol = Protocol::IP, {% if compare_versions(Crystal::VERSION, "1.5.0") >= 0 %} @[Deprecated("Use Socket.set_blocking instead.")] {% end %} blocking = nil) + initialize(handle: fd, family: family, type: type, protocol: protocol) + blocking = Crystal::EventLoop.default_socket_blocking? if blocking.nil? + Crystal::System::Socket.set_blocking(fd, blocking) unless blocking + self.sync = true + end {% end %} # :nodoc: diff --git a/src/socket/tcp_socket.cr b/src/socket/tcp_socket.cr index 03b440755c25..054dd1d443e0 100644 --- a/src/socket/tcp_socket.cr +++ b/src/socket/tcp_socket.cr @@ -14,28 +14,28 @@ require "./ip_socket" # client.close # ``` class TCPSocket < IPSocket - # Creates a new `TCPSocket`, waiting to be connected. {% begin %} - def self.new(family : Family = Family::INET, {% if compare_versions(Crystal::VERSION, "1.5.0") >= 0 %} @[Deprecated("Use Socket.set_blocking instead.")] {% end %} blocking = nil) - super(af: family, type: Type::STREAM, protocol: Protocol::TCP, blocking: blocking) - end + # Creates a new `TCPSocket`, waiting to be connected. + def self.new(family : Family = Family::INET, {% if compare_versions(Crystal::VERSION, "1.5.0") >= 0 %} @[Deprecated("Use Socket.set_blocking instead.")] {% end %} blocking = nil) + super(af: family, type: Type::STREAM, protocol: Protocol::TCP, blocking: blocking) + end {% end %} - # Creates a new TCP connection to a remote TCP server. - # - # You may limit the DNS resolution time with `dns_timeout` and limit the - # connection time to the remote server with `connect_timeout`. Both values - # must be in seconds (integers or floats). {% begin %} - def initialize(host : String, port, dns_timeout = nil, connect_timeout = nil, {% if compare_versions(Crystal::VERSION, "1.5.0") >= 0 %} @[Deprecated("Use Socket.set_blocking instead.")] {% end %} blocking = nil) - Addrinfo.tcp(host, port, timeout: dns_timeout) do |addrinfo| - super(af: addrinfo.family, type: addrinfo.type, protocol: addrinfo.protocol, blocking: blocking) - connect(addrinfo, timeout: connect_timeout) do |error| - close - error + # Creates a new TCP connection to a remote TCP server. + # + # You may limit the DNS resolution time with `dns_timeout` and limit the + # connection time to the remote server with `connect_timeout`. Both values + # must be in seconds (integers or floats). + def initialize(host : String, port, dns_timeout = nil, connect_timeout = nil, {% if compare_versions(Crystal::VERSION, "1.5.0") >= 0 %} @[Deprecated("Use Socket.set_blocking instead.")] {% end %} blocking = nil) + Addrinfo.tcp(host, port, timeout: dns_timeout) do |addrinfo| + super(af: addrinfo.family, type: addrinfo.type, protocol: addrinfo.protocol, blocking: blocking) + connect(addrinfo, timeout: connect_timeout) do |error| + close + error + end end end - end {% end %} protected def initialize(family : Family, type : Type, protocol : Protocol = Protocol::IP) @@ -48,18 +48,18 @@ class TCPSocket < IPSocket super(handle: handle, family: family, type: type, protocol: protocol, blocking: blocking) end - # Creates an UNIXSocket from an existing system file descriptor or socket - # handle. - # - # This adopts *fd* into the IO system that will reconfigure it as per the - # event loop runtime requirements. - # - # NOTE: On Windows, the handle must have been created with - # `WSA_FLAG_OVERLAPPED`. {% begin %} - def initialize(*, fd : Handle, family : Family = Family::INET, {% if compare_versions(Crystal::VERSION, "1.5.0") >= 0 %} @[Deprecated("Use Socket.set_blocking instead.")] {% end %} blocking = nil) - super fd, family, Type::STREAM, Protocol::TCP, blocking - end + # Creates an UNIXSocket from an existing system file descriptor or socket + # handle. + # + # This adopts *fd* into the IO system that will reconfigure it as per the + # event loop runtime requirements. + # + # NOTE: On Windows, the handle must have been created with + # `WSA_FLAG_OVERLAPPED`. + def initialize(*, fd : Handle, family : Family = Family::INET, {% if compare_versions(Crystal::VERSION, "1.5.0") >= 0 %} @[Deprecated("Use Socket.set_blocking instead.")] {% end %} blocking = nil) + super fd, family, Type::STREAM, Protocol::TCP, blocking + end {% end %} # Opens a TCP socket to a remote TCP server, yields it to the block, then