Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move write_bytes to be part of Vector. #3583

Merged
merged 8 commits into from
Jul 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

79 changes: 41 additions & 38 deletions distribution/lib/Standard/Base/0.0.0-dev/src/System/File.enso
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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.
Expand All @@ -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)
14 changes: 14 additions & 0 deletions std-bits/base/src/main/java/org/enso/base/Array_Utils.java
Original file line number Diff line number Diff line change
@@ -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) {
jdunkerley marked this conversation as resolved.
Show resolved Hide resolved
return input;
jdunkerley marked this conversation as resolved.
Show resolved Hide resolved
}
}
2 changes: 1 addition & 1 deletion test/Table_Tests/src/Delimited_Read_Spec.enso
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down
89 changes: 82 additions & 7 deletions test/Tests/src/System/File_Spec.enso
Original file line number Diff line number Diff line change
Expand Up @@ -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
"line 1!".write f on_existing_file=Existing_File_Behavior.Append
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
[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
Expand All @@ -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

Expand All @@ -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

Expand All @@ -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->
Expand Down