Move write_bytes to be part of Vector. (#3583)

Updates `write_bytes` API to be part of `Vector` and to conform to `write` APIs.

# Important Notes
Ensures doesn't touch the file if an invalid byte array.
This commit is contained in:
James Dunkerley 2022-07-14 12:30:40 +01:00 committed by GitHub
parent 2c780ef6ba
commit 9578dc1e43
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 141 additions and 47 deletions

View File

@ -152,6 +152,7 @@
- [Added support for custom encodings in `File_Format.Delimited` writing.][3564]
- [Allow filtering caught error type in `Error.catch`.][3574]
- [Implemented `Append` mode for `File_Format.Delimited`.][3573]
- [Added `Vector.write_bytes` function and removed old `File.write_bytes`][3583]
[debug-shortcuts]:
https://github.com/enso-org/enso/blob/develop/app/gui/docs/product/shortcuts.md#debug
@ -243,6 +244,7 @@
[3564]: https://github.com/enso-org/enso/pull/3564
[3574]: https://github.com/enso-org/enso/pull/3574
[3573]: https://github.com/enso-org/enso/pull/3573
[3583]: https://github.com/enso-org/enso/pull/3583
#### Enso Compiler

View File

@ -49,6 +49,6 @@ type Body
Examples.get_geo_data.to_file Examples.scratch_file
to_file : File -> File
to_file file =
file.write_bytes self.bytes
self.bytes.write_bytes file
file

View File

@ -12,6 +12,7 @@ from Standard.Base.Data.Time as Time_Module import Time
export Standard.Base.System.File.Option
polyglot java import org.enso.base.Array_Utils
polyglot java import org.enso.base.Encoding_Utils
polyglot java import java.io.InputStream as Java_Input_Stream
polyglot java import java.io.OutputStream as Java_Output_Stream
@ -289,41 +290,6 @@ type File
bytes = self.read_bytes
Text.from_bytes bytes encoding on_problems
## Appends a number of bytes at the end of this file.
Arguments:
- contents: A vector of bytes to append to the file.
> Example
Append the bytes of the text "hello" to a file.
import Standard.Examples
example_append_bytes = Examples.scratch_file.append_bytes "hello".utf_8
append_bytes : Vector.Vector -> Nothing ! File_Error
append_bytes contents =
opts = [Option.Append, Option.Create]
self.with_output_stream opts (_.write_bytes contents)
## Writes a number of bytes into this file, replacing any existing contents.
Arguments:
- contents: The vector of bytes to write into the file.
If the file does not exist, it will be created.
> Example
Write the bytes of the text "hello" to a file.
import Standard.Examples
example_write_bytes = Examples.scratch_file.write_bytes "hello".utf_8
write_bytes : Vector.Vector -> Nothing ! File_Error
write_bytes contents =
opts = [Option.Write, Option.Create, Option.Truncate_Existing]
self.with_output_stream opts (_.write_bytes contents)
Nothing
## Join two path segments together.
Arguments:
@ -1066,9 +1032,9 @@ get_cwd = @Builtin_Method "File.get_cwd"
get_file : Text -> File
get_file path = @Builtin_Method "File.get_file"
## Writes (or appends) the specified bytes to the specified file.
The behavior specified in the `existing_file` parameter will be used if the
file exists.
## Writes (or appends) the text to the specified file using the supplied
encoding. The behavior specified in the `existing_file` parameter will be
used if the file exists.
Arguments:
- path: The path to the target file.
@ -1090,3 +1056,40 @@ Text.write path encoding=Encoding.utf_8 on_existing_file=Existing_File_Behavior.
file = new path
on_existing_file.write file stream->
stream.write_bytes bytes
## Writes (or appends) the Vector of bytes into the specified file. The behavior
specified in the `existing_file` parameter will be used if the file exists.
Arguments:
- path: The path to the target file.
- on_existing_file: Specifies how to proceed if the file already exists.
If the Vector contains any item which is not a `Byte`, an
`Illegal_Argument_Error` will be raised. Enso follows the Java convention,
that a `Byte` is between -128 and 127.
If the path to the parent location cannot be found or the filename is
invalid, a `File_Not_Found` is raised.
If another error occurs, such as access denied, an `Io_Error` is raised.
Otherwise, the file is created with the encoded text written to it.
> Example
Write the UTF-8 bytes of the text "$£§€¢" to a file.
import Standard.Examples
[36, -62, -93, -62, -89, -30, -126, -84, -62, -94].write_bytes Examples.scratch_file
> Example
Append the UTF-8 bytes of the text "$£§€¢" to a file.
import Standard.Examples
[36, -62, -93, -62, -89, -30, -126, -84, -62, -94].write_bytes Examples.scratch_file.write_bytes Examples.scratch_file Existing_File_Behavior.Append
Vector.Vector.write_bytes : (File|Text) -> Existing_File_Behavior -> Nothing ! Illegal_Argument_Error | File_Not_Found | Io_Error | File_Already_Exists_Error
Vector.Vector.write_bytes path on_existing_file=Existing_File_Behavior.Backup =
Panic.catch Unsupported_Argument_Types handler=(Error.throw (Illegal_Argument_Error "Only Vectors consisting of bytes (integers in the range from -128 to 127) are supported by the `write_bytes` method.")) <|
## Convert to a byte array before writing - and fail early if there is any problem.
byte_array = Array_Utils.ensureByteArray self.to_array
file = new path
on_existing_file.write file stream->
stream.write_bytes (Vector.Vector byte_array)

View File

@ -0,0 +1,14 @@
package org.enso.base;
public class Array_Utils {
/**
* This function forces the polyglot conversion of an Enso array into a `byte[]`. This allows for
* asserting that it is a valid `byte[]`.
*
* @param input the converted array.
* @return the `input` unchanged.
*/
public static byte[] ensureByteArray(byte[] input) {
return input;
}
}

View File

@ -148,7 +148,7 @@ spec =
Test.specify "should report errors when encountering malformed characters" <|
utf8_file = (enso_project.data / "transient" / "utf8_invalid.csv")
utf8_bytes = [97, 44, 98, 44, 99, 10, -60, -123, 44, -17, -65, -65, 44, -61, 40, -61, 40, 10]
utf8_file.write_bytes utf8_bytes
utf8_bytes.write_bytes utf8_file
action_1 on_problems =
utf8_file.read (Delimited "," headers=True) on_problems
tester_1 table =

View File

@ -120,14 +120,89 @@ spec =
contents_2.should .start_with "Cupcake ipsum dolor sit amet."
Test.group "write operations" <|
data = [32, 127, -128, 0]
data_2 = [10, 15, 20, 30]
transient = enso_project.data / "transient"
Test.specify "should allow to append to files" <|
Test.specify "should allow to writing bytes to a new file" <|
f = transient / "new_file.dat"
f.delete_if_exists
f.exists.should_be_false
data.write_bytes f
f.exists.should_be_true
f.read_bytes.should_equal data
f.delete_if_exists
Test.specify "should backup a file when overwriting with new bytes" <|
f = transient / "work.txt"
f.delete_if_exists
f_bak = transient / "work.txt.bak"
f_bak.delete_if_exists
data.write_bytes f
f.exists.should_be_true
data_2.write_bytes f
f.read_bytes.should_equal data_2
f_bak.exists.should_be_true
f.delete_if_exists
f_bak.delete_if_exists
Test.specify "should allow to overwriting a file with new bytes" <|
f = transient / "work.txt"
f.delete_if_exists
f_bak = transient / "work.txt.bak"
f_bak.delete_if_exists
data.write_bytes f
f.exists.should_be_true
data_2.write_bytes f on_existing_file=Existing_File_Behavior.Overwrite
f.read_bytes.should_equal data_2
f_bak.exists.should_be_false
f.delete_if_exists
Test.specify "should allow appending bytes to a new file" <|
f = transient / "new_file.dat"
f.delete_if_exists
f.exists.should_be_false
data.write_bytes f
data_2.write_bytes f on_existing_file=Existing_File_Behavior.Append
f.read_bytes.should_equal (data + data_2)
f.delete_if_exists
Test.specify "should fail will Illegal_Argument_Error when trying to write invalid byte vector" <|
f = transient / "work.txt"
f.delete_if_exists
f.exists.should_be_false
"line 1!".write f on_existing_file=Existing_File_Behavior.Append
[0, 1, 256].write_bytes f . should_fail_with Illegal_Argument_Error
[0, 1, Nothing].write_bytes f . should_fail_with Illegal_Argument_Error
Test.specify "should not change the file when trying to write an invalid byte vector" <|
f = transient / "work.txt"
f.delete_if_exists
f_bak = transient / "work.txt.bak"
f_bak.delete_if_exists
data.write_bytes f
[0, 1, 256].write_bytes f . should_fail_with Illegal_Argument_Error
f.read_bytes.should_equal data
f_bak.exists.should_be_false
[0, 1, 256].write_bytes f on_existing_file=Existing_File_Behavior.Overwrite . should_fail_with Illegal_Argument_Error
f.read_bytes.should_equal data
[0, 1, 256].write_bytes f on_existing_file=Existing_File_Behavior.Append . should_fail_with Illegal_Argument_Error
f.read_bytes.should_equal data
f.delete_if_exists
Test.specify "should allow to writing text to a new file" <|
f = transient / "work.txt"
f.delete_if_exists
f.exists.should_be_false
"line 1!".write f
f.exists.should_be_true
f.read_text.should_equal "line 1!"
f.delete
f.exists.should_be_false
Test.specify "should allow to appending text to a file" <|
f = transient / "work.txt"
f.delete_if_exists
"line 1!".write f on_existing_file=Existing_File_Behavior.Append
'\nline 2!'.write f on_existing_file=Existing_File_Behavior.Append
f.read_text.should_equal 'line 1!\nline 2!'
f.delete
@ -140,8 +215,8 @@ spec =
"line 1!".write f on_existing_file=Existing_File_Behavior.Overwrite . should_equal Nothing
f.exists.should_be_true
f.read_text.should_equal "line 1!"
'line 2!'.write f on_existing_file=Existing_File_Behavior.Overwrite . should_equal Nothing
f.read_text.should_equal 'line 2!'
"line 2!".write f on_existing_file=Existing_File_Behavior.Overwrite . should_equal Nothing
f.read_text.should_equal "line 2!"
f.delete
f.exists.should_be_false
@ -153,7 +228,7 @@ spec =
f.exists.should_be_true
f.read_text.should_equal "line 1!"
"line 2!".write f on_existing_file=Existing_File_Behavior.Error . should_fail_with File_Already_Exists_Error
f.read_text.should_equal 'line 1!'
f.read_text.should_equal "line 1!"
f.delete
f.exists.should_be_false
@ -180,8 +255,8 @@ spec =
n3.delete_if_exists
"line 2!".write f . should_equal Nothing
f.read_text.should_equal 'line 2!'
bak.read_text.should_equal 'line 1!'
f.read_text.should_equal "line 2!"
bak.read_text.should_equal "line 1!"
if n3.exists then
Test.fail "The temporary file should have been cleaned up."
written_news.each n->