From fbc6757f82a6dd76b5d4e427aae37471dca50fb5 Mon Sep 17 00:00:00 2001 From: "Dr. Carsten Leue" Date: Sun, 30 Jul 2023 16:04:37 +0200 Subject: [PATCH] fix: add WithTempFile Signed-off-by: Dr. Carsten Leue --- file/getters.go | 23 +++++++++++++++++ io/file/file.go | 39 ++++++++++++++++++++++++++++ ioeither/file/close.go | 1 + ioeither/file/file.go | 26 +++++++++++++------ ioeither/file/readall.go | 2 +- ioeither/file/tempfile.go | 45 +++++++++++++++++++++++++++++++++ ioeither/file/tempfile_test.go | 46 ++++++++++++++++++++++++++++++++++ ioeither/file/write.go | 6 ++--- ioeither/ioeither.go | 2 +- 9 files changed, 177 insertions(+), 13 deletions(-) create mode 100644 file/getters.go create mode 100644 io/file/file.go create mode 100644 ioeither/file/tempfile.go create mode 100644 ioeither/file/tempfile_test.go diff --git a/file/getters.go b/file/getters.go new file mode 100644 index 0000000..a51561f --- /dev/null +++ b/file/getters.go @@ -0,0 +1,23 @@ +// Copyright (c) 2023 IBM Corp. +// All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package file + +import "os" + +// GetName is the getter for the `Name` property of [os.File] +func GetName(f *os.File) string { + return f.Name() +} diff --git a/io/file/file.go b/io/file/file.go new file mode 100644 index 0000000..0ef7467 --- /dev/null +++ b/io/file/file.go @@ -0,0 +1,39 @@ +// Copyright (c) 2023 IBM Corp. +// All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package file + +import ( + "io" + "os" + + IO "github.com/IBM/fp-go/io" +) + +// Close closes a closeable resource and ignores a potential error +func Close[R io.Closer](r R) IO.IO[R] { + return IO.MakeIO[R](func() R { + r.Close() // #nosec: G104 + return r + }) +} + +// Remove removes a resource and ignores a potential error +func Remove(name string) IO.IO[string] { + return IO.MakeIO[string](func() string { + os.Remove(name) // #nosec: G104 + return name + }) +} diff --git a/ioeither/file/close.go b/ioeither/file/close.go index ac04db4..8082319 100644 --- a/ioeither/file/close.go +++ b/ioeither/file/close.go @@ -21,6 +21,7 @@ import ( IOE "github.com/IBM/fp-go/ioeither" ) +// onClose closes a closeable resource func onClose[R io.Closer](r R) IOE.IOEither[error, R] { return IOE.TryCatchError(func() (R, error) { return r, r.Close() diff --git a/ioeither/file/file.go b/ioeither/file/file.go index 1d44e61..2c048ed 100644 --- a/ioeither/file/file.go +++ b/ioeither/file/file.go @@ -24,14 +24,24 @@ import ( var ( // Open opens a file for reading Open = IOE.Eitherize1(os.Open) + // Create opens a file for writing + Create = IOE.Eitherize1(os.Create) // ReadFile reads the context of a file ReadFile = IOE.Eitherize1(os.ReadFile) - // WriteFile writes a data blob to a file - WriteFile = func(dstName string, perm os.FileMode) func([]byte) IOE.IOEither[error, []byte] { - return func(data []byte) IOE.IOEither[error, []byte] { - return IOE.TryCatchError(func() ([]byte, error) { - return data, os.WriteFile(dstName, data, perm) - }) - } - } ) + +// WriteFile writes a data blob to a file +func WriteFile(dstName string, perm os.FileMode) func([]byte) IOE.IOEither[error, []byte] { + return func(data []byte) IOE.IOEither[error, []byte] { + return IOE.TryCatchError(func() ([]byte, error) { + return data, os.WriteFile(dstName, data, perm) + }) + } +} + +// Remove removes a file by name +func Remove(name string) IOE.IOEither[error, string] { + return IOE.TryCatchError(func() (string, error) { + return name, os.Remove(name) + }) +} diff --git a/ioeither/file/readall.go b/ioeither/file/readall.go index f689273..0ab2368 100644 --- a/ioeither/file/readall.go +++ b/ioeither/file/readall.go @@ -29,7 +29,7 @@ func onReadAll[R io.Reader](r R) IOE.IOEither[error, []byte] { // ReadAll uses a generator function to create a stream, reads it and closes it func ReadAll[R io.ReadCloser](acquire IOE.IOEither[error, R]) IOE.IOEither[error, []byte] { - return IOE.WithResource[error, R, []byte]( + return IOE.WithResource[[]byte]( acquire, onClose[R])( onReadAll[R], diff --git a/ioeither/file/tempfile.go b/ioeither/file/tempfile.go new file mode 100644 index 0000000..e993d5b --- /dev/null +++ b/ioeither/file/tempfile.go @@ -0,0 +1,45 @@ +// Copyright (c) 2023 IBM Corp. +// All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package file + +import ( + "os" + + FL "github.com/IBM/fp-go/file" + F "github.com/IBM/fp-go/function" + IO "github.com/IBM/fp-go/io" + IOF "github.com/IBM/fp-go/io/file" + IOE "github.com/IBM/fp-go/ioeither" +) + +var ( + // CreateTemp created a temp file with proper parametrization + CreateTemp = IOE.Eitherize2(os.CreateTemp) + // onCreateTempFile creates a temp file with sensible defaults + onCreateTempFile = CreateTemp("", "*") + // destroy handler + onReleaseTempFile = F.Flow4( + IOF.Close[*os.File], + IO.Map(FL.GetName), + IOE.FromIO[error, string], + IOE.Chain(Remove), + ) +) + +// WithTempFile creates a temporary file, then invokes a callback to create a resource based on the file, then close and remove the temp file +func WithTempFile[A any](f func(*os.File) IOE.IOEither[error, A]) IOE.IOEither[error, A] { + return IOE.WithResource[A](onCreateTempFile, onReleaseTempFile)(f) +} diff --git a/ioeither/file/tempfile_test.go b/ioeither/file/tempfile_test.go new file mode 100644 index 0000000..c586a33 --- /dev/null +++ b/ioeither/file/tempfile_test.go @@ -0,0 +1,46 @@ +// Copyright (c) 2023 IBM Corp. +// All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package file + +import ( + "os" + "testing" + + E "github.com/IBM/fp-go/either" + F "github.com/IBM/fp-go/function" + IOE "github.com/IBM/fp-go/ioeither" + "github.com/stretchr/testify/assert" +) + +func TestWithTempFile(t *testing.T) { + + res := WithTempFile(onWriteAll[*os.File]([]byte("Carsten"))) + + assert.Equal(t, E.Of[error]([]byte("Carsten")), res()) +} + +func TestWithTempFileOnClosedFile(t *testing.T) { + + res := WithTempFile(func(f *os.File) IOE.IOEither[error, []byte] { + return F.Pipe2( + f, + onWriteAll[*os.File]([]byte("Carsten")), + IOE.ChainFirst(F.Constant1[[]byte](onClose(f))), + ) + }) + + assert.Equal(t, E.Of[error]([]byte("Carsten")), res()) +} diff --git a/ioeither/file/write.go b/ioeither/file/write.go index 8f9831b..1a4bbd9 100644 --- a/ioeither/file/write.go +++ b/ioeither/file/write.go @@ -34,7 +34,7 @@ func onWriteAll[W io.Writer](data []byte) func(w W) IOE.IOEither[error, []byte] func WriteAll[W io.WriteCloser](data []byte) func(acquire IOE.IOEither[error, W]) IOE.IOEither[error, []byte] { onWrite := onWriteAll[W](data) return func(onCreate IOE.IOEither[error, W]) IOE.IOEither[error, []byte] { - return IOE.WithResource[error, W, []byte]( + return IOE.WithResource[[]byte]( onCreate, onClose[W])( onWrite, @@ -43,8 +43,8 @@ func WriteAll[W io.WriteCloser](data []byte) func(acquire IOE.IOEither[error, W] } // Write uses a generator function to create a stream, writes data to it and closes it -func Write[W io.WriteCloser, R any](acquire IOE.IOEither[error, W]) func(use func(W) IOE.IOEither[error, R]) IOE.IOEither[error, R] { - return IOE.WithResource[error, W, R]( +func Write[R any, W io.WriteCloser](acquire IOE.IOEither[error, W]) func(use func(W) IOE.IOEither[error, R]) IOE.IOEither[error, R] { + return IOE.WithResource[R]( acquire, onClose[W]) } diff --git a/ioeither/ioeither.go b/ioeither/ioeither.go index 50050b0..b5ecc35 100644 --- a/ioeither/ioeither.go +++ b/ioeither/ioeither.go @@ -192,7 +192,7 @@ func ChainFirstIOK[E, A, B any](f func(A) I.IO[B]) func(IOEither[E, A]) IOEither } // WithResource constructs a function that creates a resource, then operates on it and then releases the resource -func WithResource[E, R, A, ANY any](onCreate IOEither[E, R], onRelease func(R) IOEither[E, ANY]) func(func(R) IOEither[E, A]) IOEither[E, A] { +func WithResource[A, E, R, ANY any](onCreate IOEither[E, R], onRelease func(R) IOEither[E, ANY]) func(func(R) IOEither[E, A]) IOEither[E, A] { return G.WithResource[IOEither[E, A]](onCreate, onRelease) }