aboutsummaryrefslogtreecommitdiff
path: root/v_windows/v/vlib/io
diff options
context:
space:
mode:
Diffstat (limited to 'v_windows/v/vlib/io')
-rw-r--r--v_windows/v/vlib/io/buffered_reader.v145
-rw-r--r--v_windows/v/vlib/io/custom_string_reading_test.v57
-rw-r--r--v_windows/v/vlib/io/io.v16
-rw-r--r--v_windows/v/vlib/io/io_cp_test.v13
-rw-r--r--v_windows/v/vlib/io/io_test.v41
-rw-r--r--v_windows/v/vlib/io/multi_writer.v33
-rw-r--r--v_windows/v/vlib/io/multi_writer_test.v66
-rw-r--r--v_windows/v/vlib/io/os_file_reader_test.v29
-rw-r--r--v_windows/v/vlib/io/reader.v66
-rw-r--r--v_windows/v/vlib/io/reader_test.v130
-rw-r--r--v_windows/v/vlib/io/readerwriter.v34
-rw-r--r--v_windows/v/vlib/io/util/util.v104
-rw-r--r--v_windows/v/vlib/io/util/util_test.v127
-rw-r--r--v_windows/v/vlib/io/writer.v12
14 files changed, 873 insertions, 0 deletions
diff --git a/v_windows/v/vlib/io/buffered_reader.v b/v_windows/v/vlib/io/buffered_reader.v
new file mode 100644
index 0000000..f336e2d
--- /dev/null
+++ b/v_windows/v/vlib/io/buffered_reader.v
@@ -0,0 +1,145 @@
+module io
+
+// BufferedReader provides a buffered interface for a reader
+struct BufferedReader {
+mut:
+ reader Reader
+ buf []byte
+ offset int // current offset in the buffer
+ len int
+ fails int // how many times fill_buffer has read 0 bytes in a row
+ mfails int // maximum fails, after which we can assume that the stream has ended
+pub mut:
+ end_of_stream bool // whether we reached the end of the upstream reader
+}
+
+// BufferedReaderConfig are options that can be given to a reader
+pub struct BufferedReaderConfig {
+ reader Reader
+ cap int = 128 * 1024 // large for fast reading of big(ish) files
+ retries int = 2 // how many times to retry before assuming the stream ended
+}
+
+// new_buffered_reader creates new BufferedReader
+pub fn new_buffered_reader(o BufferedReaderConfig) &BufferedReader {
+ if o.cap <= 0 {
+ panic('new_buffered_reader should be called with a positive `cap`')
+ }
+ // create
+ r := &BufferedReader{
+ reader: o.reader
+ buf: []byte{len: o.cap, cap: o.cap}
+ offset: 0
+ mfails: o.retries
+ }
+ return r
+}
+
+// read fufills the Reader interface
+pub fn (mut r BufferedReader) read(mut buf []byte) ?int {
+ if r.end_of_stream {
+ return none
+ }
+ // read data out of the buffer if we dont have any
+ if r.needs_fill() {
+ if !r.fill_buffer() {
+ // end of stream
+ return none
+ }
+ }
+ read := copy(buf, r.buf[r.offset..r.len])
+ if read == 0 {
+ return none
+ }
+ r.offset += read
+ return read
+}
+
+pub fn (mut r BufferedReader) free() {
+ unsafe {
+ r.buf.free()
+ }
+}
+
+// fill_buffer attempts to refill the internal buffer
+// and returns whether it got any data
+fn (mut r BufferedReader) fill_buffer() bool {
+ if r.end_of_stream {
+ // we know we have already reached the end of stream
+ // so return early
+ return true
+ }
+ r.offset = 0
+ r.len = 0
+ r.len = r.reader.read(mut r.buf) or {
+ // end of stream was reached
+ r.end_of_stream = true
+ return false
+ }
+ if r.len == 0 {
+ r.fails++
+ } else {
+ r.fails = 0
+ }
+ if r.fails >= r.mfails {
+ // When reading 0 bytes several times in a row, assume the stream has ended.
+ // This prevents infinite loops ¯\_(ツ)_/¯ ...
+ r.end_of_stream = true
+ return false
+ }
+ // we got some data
+ return true
+}
+
+// needs_fill returns whether the buffer needs refilling
+fn (r BufferedReader) needs_fill() bool {
+ return r.offset >= r.len
+}
+
+// end_of_stream returns whether the end of the stream was reached
+pub fn (r BufferedReader) end_of_stream() bool {
+ return r.end_of_stream
+}
+
+// read_line attempts to read a line from the buffered reader
+// it will read until it finds a new line character (\n) or
+// the end of stream
+pub fn (mut r BufferedReader) read_line() ?string {
+ if r.end_of_stream {
+ return none
+ }
+ mut line := []byte{}
+ for {
+ if r.needs_fill() {
+ // go fetch some new data
+ if !r.fill_buffer() {
+ // We are at the end of the stream
+ if line.len == 0 {
+ // we had nothing so return nothing
+ return none
+ }
+ return line.bytestr()
+ }
+ }
+ // try and find a newline character
+ mut i := r.offset
+ for ; i < r.len; i++ {
+ c := r.buf[i]
+ if c == `\n` {
+ // great, we hit something
+ // do some checking for whether we hit \r\n or just \n
+ if i != 0 && r.buf[i - 1] == `\r` {
+ x := i - 1
+ line << r.buf[r.offset..x]
+ } else {
+ line << r.buf[r.offset..i]
+ }
+ r.offset = i + 1
+ return line.bytestr()
+ }
+ }
+ line << r.buf[r.offset..i]
+ r.offset = i
+ }
+ return none
+}
diff --git a/v_windows/v/vlib/io/custom_string_reading_test.v b/v_windows/v/vlib/io/custom_string_reading_test.v
new file mode 100644
index 0000000..76e3307
--- /dev/null
+++ b/v_windows/v/vlib/io/custom_string_reading_test.v
@@ -0,0 +1,57 @@
+import io
+
+struct StringReader {
+ text string
+mut:
+ place int
+}
+
+fn imin(a int, b int) int {
+ return if a < b { a } else { b }
+}
+
+fn (mut s StringReader) read(mut buf []byte) ?int {
+ $if debug {
+ eprintln('>>>> StringReader.read output buf.len: $buf.len')
+ }
+ if s.place > s.text.len + 1 {
+ return none
+ }
+ mut howmany := imin(buf.len, s.text.len - s.place)
+ xxx := s.text[s.place..s.place + howmany].bytes()
+ read := copy(buf, xxx)
+ s.place += read
+ return read
+}
+
+fn read_from_string(text string, capacity int) []byte {
+ mut str := StringReader{
+ text: text
+ }
+ mut stream := io.new_buffered_reader(reader: str, cap: capacity)
+ //
+ mut buf := []byte{len: 1}
+ mut res := []byte{}
+ mut i := 0
+ for {
+ z := stream.read(mut buf) or { break }
+ res << buf
+ $if debug {
+ println('capacity: $capacity, i: $i, buf: $buf | z: $z')
+ }
+ i++
+ }
+ return res
+}
+
+pub fn test_reading_from_a_string() {
+ for capacity in 1 .. 1000 {
+ assert read_from_string('a', capacity) == [byte(`a`)]
+ assert read_from_string('ab', capacity) == [byte(`a`), `b`]
+ assert read_from_string('abc', capacity) == [byte(`a`), `b`, `c`]
+ assert read_from_string('abcde', capacity) == [byte(`a`), `b`, `c`, `d`, `e`]
+ large_string_bytes := []byte{len: 1000, init: `x`}
+ large_string := large_string_bytes.bytestr()
+ assert read_from_string(large_string, capacity) == large_string_bytes
+ }
+}
diff --git a/v_windows/v/vlib/io/io.v b/v_windows/v/vlib/io/io.v
new file mode 100644
index 0000000..c07e074
--- /dev/null
+++ b/v_windows/v/vlib/io/io.v
@@ -0,0 +1,16 @@
+module io
+
+const (
+ buf_max_len = 1024
+)
+
+pub fn cp(src Reader, mut dst Writer) ? {
+ mut buf := []byte{len: io.buf_max_len}
+ for {
+ len := src.read(mut buf) or { break }
+ dst.write(buf[..len]) or { return err }
+ }
+ unsafe {
+ buf.free()
+ }
+}
diff --git a/v_windows/v/vlib/io/io_cp_test.v b/v_windows/v/vlib/io/io_cp_test.v
new file mode 100644
index 0000000..21c743a
--- /dev/null
+++ b/v_windows/v/vlib/io/io_cp_test.v
@@ -0,0 +1,13 @@
+import io
+import os
+
+fn test_cp() ? {
+ mut f := os.open(@FILE) or { panic(err) }
+ defer {
+ f.close()
+ }
+ mut r := io.new_buffered_reader(reader: f)
+ mut stdout := os.stdout()
+ io.cp(r, mut stdout) ?
+ assert true
+}
diff --git a/v_windows/v/vlib/io/io_test.v b/v_windows/v/vlib/io/io_test.v
new file mode 100644
index 0000000..fca17c2
--- /dev/null
+++ b/v_windows/v/vlib/io/io_test.v
@@ -0,0 +1,41 @@
+import io
+
+struct Buf {
+pub:
+ bytes []byte
+mut:
+ i int
+}
+
+struct Writ {
+pub mut:
+ bytes []byte
+}
+
+fn (mut b Buf) read(mut buf []byte) ?int {
+ if !(b.i < b.bytes.len) {
+ return none
+ }
+ n := copy(buf, b.bytes[b.i..])
+ b.i += n
+ return n
+}
+
+fn (mut w Writ) write(buf []byte) ?int {
+ if buf.len <= 0 {
+ return none
+ }
+ w.bytes << buf
+ return buf.len
+}
+
+fn test_copy() {
+ src := Buf{
+ bytes: 'abcdefghij'.repeat(10).bytes()
+ }
+ mut dst := Writ{
+ bytes: []byte{}
+ }
+ io.cp(src, mut dst) or { assert false }
+ assert dst.bytes == src.bytes
+}
diff --git a/v_windows/v/vlib/io/multi_writer.v b/v_windows/v/vlib/io/multi_writer.v
new file mode 100644
index 0000000..837ea30
--- /dev/null
+++ b/v_windows/v/vlib/io/multi_writer.v
@@ -0,0 +1,33 @@
+module io
+
+// new_multi_writer returns a Writer that writes to all writers. The write
+// function of the returned Writer writes to all writers of the MultiWriter,
+// returns the length of bytes written, and if any writer fails to write the
+// full length an error is returned and writing to other writers stops, and if
+// any writer returns an error the error is returned immediately and writing to
+// other writers stops.
+pub fn new_multi_writer(writers ...Writer) Writer {
+ return &MultiWriter{
+ writers: writers
+ }
+}
+
+// MultiWriter writes to all its writers.
+pub struct MultiWriter {
+pub mut:
+ writers []Writer
+}
+
+// write writes to all writers of the MultiWriter. Returns the length of bytes
+// written. If any writer fails to write the full length an error is returned
+// and writing to other writers stops. If any writer returns an error the error
+// is returned immediately and writing to other writers stops.
+pub fn (mut m MultiWriter) write(buf []byte) ?int {
+ for mut w in m.writers {
+ n := w.write(buf) ?
+ if n != buf.len {
+ return error('io: incomplete write to writer of MultiWriter')
+ }
+ }
+ return buf.len
+}
diff --git a/v_windows/v/vlib/io/multi_writer_test.v b/v_windows/v/vlib/io/multi_writer_test.v
new file mode 100644
index 0000000..2e317e8
--- /dev/null
+++ b/v_windows/v/vlib/io/multi_writer_test.v
@@ -0,0 +1,66 @@
+module io
+
+fn test_multi_writer_write_successful() {
+ w0 := TestWriter{}
+ w1 := TestWriter{}
+ mut mw := new_multi_writer(w0, w1)
+ n := mw.write('0123456789'.bytes()) or {
+ assert false
+ return
+ }
+ assert n == 10
+ assert w0.bytes == '0123456789'.bytes()
+ assert w1.bytes == '0123456789'.bytes()
+}
+
+fn test_multi_writer_write_incomplete() {
+ w0 := TestWriter{}
+ w1 := TestIncompleteWriter{}
+ mut mw := new_multi_writer(w0, w1)
+ n := mw.write('0123456789'.bytes()) or {
+ assert w0.bytes == '0123456789'.bytes()
+ assert w1.bytes == '012345678'.bytes()
+ return
+ }
+ assert false
+}
+
+fn test_multi_writer_write_error() {
+ w0 := TestWriter{}
+ w1 := TestErrorWriter{}
+ w2 := TestWriter{}
+ mut mw := new_multi_writer(w0, w1, w2)
+ n := mw.write('0123456789'.bytes()) or {
+ assert w0.bytes == '0123456789'.bytes()
+ assert w2.bytes == []
+ return
+ }
+ assert false
+}
+
+struct TestWriter {
+pub mut:
+ bytes []byte
+}
+
+fn (mut w TestWriter) write(buf []byte) ?int {
+ w.bytes << buf
+ return buf.len
+}
+
+struct TestIncompleteWriter {
+pub mut:
+ bytes []byte
+}
+
+fn (mut w TestIncompleteWriter) write(buf []byte) ?int {
+ b := buf[..buf.len - 1]
+ w.bytes << b
+ return b.len
+}
+
+struct TestErrorWriter {}
+
+fn (mut w TestErrorWriter) write(buf []byte) ?int {
+ return error('error writer errored')
+}
diff --git a/v_windows/v/vlib/io/os_file_reader_test.v b/v_windows/v/vlib/io/os_file_reader_test.v
new file mode 100644
index 0000000..1ed3a1f
--- /dev/null
+++ b/v_windows/v/vlib/io/os_file_reader_test.v
@@ -0,0 +1,29 @@
+import os
+import io
+
+fn read_file(file string, cap int) []string {
+ mut lines := []string{}
+ mut f := os.open(file) or { panic(err) }
+ defer {
+ f.close()
+ }
+ mut r := io.new_buffered_reader(reader: f, cap: cap)
+ for {
+ l := r.read_line() or { break }
+ lines << l
+ // println('Line: $l')
+ }
+ assert lines.len > 0
+ assert r.end_of_stream == true
+ println('------------------------------------------------ cap: ${cap:6}; read: ${lines.len:3} lines')
+ return lines
+}
+
+fn test_file_reader() {
+ for cap := 1; cap <= 10000; cap += 256 {
+ lines := read_file(@FILE, cap)
+ assert lines.last() == '// my last line'
+ }
+}
+
+// my last line
diff --git a/v_windows/v/vlib/io/reader.v b/v_windows/v/vlib/io/reader.v
new file mode 100644
index 0000000..61f566f
--- /dev/null
+++ b/v_windows/v/vlib/io/reader.v
@@ -0,0 +1,66 @@
+module io
+
+// Reader represents a stream of data that can be read
+pub interface Reader {
+ // read reads up to buf.len bytes and places
+ // them into buf.
+ // A type that implements this should return
+ // `none` on end of stream (EOF) instead of just returning 0
+ read(mut buf []byte) ?int
+}
+
+const (
+ read_all_len = 10 * 1024
+ read_all_grow_len = 1024
+)
+
+// ReadAllConfig allows options to be passed for the behaviour
+// of read_all
+pub struct ReadAllConfig {
+ reader Reader
+ read_to_end_of_stream bool
+}
+
+// read_all reads all bytes from a reader until either a 0 length read
+// or if read_to_end_of_stream is true then the end of the stream (`none`)
+pub fn read_all(config ReadAllConfig) ?[]byte {
+ r := config.reader
+ read_till_eof := config.read_to_end_of_stream
+
+ mut b := []byte{len: io.read_all_len}
+ mut read := 0
+ for {
+ new_read := r.read(mut b[read..]) or { break }
+ read += new_read
+ if !read_till_eof && read == 0 {
+ break
+ }
+ if b.len == read {
+ unsafe { b.grow_len(io.read_all_grow_len) }
+ }
+ }
+ return b[..read]
+}
+
+// read_any reads any available bytes from a reader
+// (until the reader returns a read of 0 length)
+pub fn read_any(r Reader) ?[]byte {
+ mut b := []byte{len: io.read_all_len}
+ mut read := 0
+ for {
+ new_read := r.read(mut b[read..]) or { break }
+ read += new_read
+ if new_read == 0 {
+ break
+ }
+ if b.len == read {
+ unsafe { b.grow_len(io.read_all_grow_len) }
+ }
+ }
+ return b[..read]
+}
+
+// RandomReader represents a stream of data that can be read from at a random location
+interface RandomReader {
+ read_from(pos u64, mut buf []byte) ?int
+}
diff --git a/v_windows/v/vlib/io/reader_test.v b/v_windows/v/vlib/io/reader_test.v
new file mode 100644
index 0000000..c7a2265
--- /dev/null
+++ b/v_windows/v/vlib/io/reader_test.v
@@ -0,0 +1,130 @@
+module io
+
+struct Buf {
+pub:
+ bytes []byte
+mut:
+ i int
+}
+
+fn (mut b Buf) read(mut buf []byte) ?int {
+ if !(b.i < b.bytes.len) {
+ return none
+ }
+ n := copy(buf, b.bytes[b.i..])
+ b.i += n
+ return n
+}
+
+fn test_read_all() {
+ buf := Buf{
+ bytes: '123'.repeat(10).bytes()
+ }
+ res := read_all(reader: buf) or {
+ assert false
+ ''.bytes()
+ }
+ assert res == '123'.repeat(10).bytes()
+}
+
+fn test_read_all_huge() {
+ buf := Buf{
+ bytes: '123'.repeat(100000).bytes()
+ }
+ res := read_all(reader: buf) or {
+ assert false
+ ''.bytes()
+ }
+ assert res == '123'.repeat(100000).bytes()
+}
+
+struct StringReader {
+ text string
+mut:
+ place int
+}
+
+fn (mut s StringReader) read(mut buf []byte) ?int {
+ if s.place >= s.text.len {
+ return none
+ }
+ read := copy(buf, s.text[s.place..].bytes())
+ s.place += read
+ return read
+}
+
+const (
+ newline_count = 100000
+)
+
+fn test_stringreader() {
+ text := '12345\n'.repeat(io.newline_count)
+ mut s := StringReader{
+ text: text
+ }
+ mut r := new_buffered_reader(reader: s)
+ for i := 0; true; i++ {
+ if _ := r.read_line() {
+ } else {
+ assert i == io.newline_count
+ break
+ }
+ }
+ if _ := r.read_line() {
+ assert false
+ }
+ leftover := read_all(reader: r) or {
+ assert false
+ panic('bad')
+ }
+ if leftover.len > 0 {
+ assert false
+ }
+}
+
+fn test_stringreader2() {
+ text := '12345\r\n'.repeat(io.newline_count)
+ mut s := StringReader{
+ text: text
+ }
+ mut r := new_buffered_reader(reader: s)
+ for i := 0; true; i++ {
+ if _ := r.read_line() {
+ } else {
+ assert i == io.newline_count
+ break
+ }
+ }
+ if _ := r.read_line() {
+ assert false
+ }
+ leftover := read_all(reader: r) or {
+ assert false
+ panic('bad')
+ }
+ if leftover.len > 0 {
+ assert false
+ }
+}
+
+fn test_leftover() {
+ text := 'This is a test\r\nNice try!'
+ mut s := StringReader{
+ text: text
+ }
+ mut r := new_buffered_reader(reader: s)
+ _ := r.read_line() or {
+ assert false
+ panic('bad')
+ }
+ line2 := r.read_line() or {
+ assert false
+ panic('bad')
+ }
+ assert line2 == 'Nice try!'
+ if _ := r.read_line() {
+ assert false
+ panic('bad')
+ }
+ assert r.end_of_stream()
+}
diff --git a/v_windows/v/vlib/io/readerwriter.v b/v_windows/v/vlib/io/readerwriter.v
new file mode 100644
index 0000000..ba44172
--- /dev/null
+++ b/v_windows/v/vlib/io/readerwriter.v
@@ -0,0 +1,34 @@
+module io
+
+// ReaderWriter represents a stream that can be read from and wrote to
+pub interface ReaderWriter {
+ // from Reader
+ read(mut buf []byte) ?int
+ // from Writer
+ write(buf []byte) ?int
+}
+
+// ReaderWriterImpl is a ReaderWriter that can be made from
+// a seperate reader and writer (see fn make_readerwriter)
+struct ReaderWriterImpl {
+ r Reader
+mut:
+ w Writer
+}
+
+pub fn (mut r ReaderWriterImpl) read(mut buf []byte) ?int {
+ return r.r.read(mut buf)
+}
+
+pub fn (mut r ReaderWriterImpl) write(buf []byte) ?int {
+ return r.w.write(buf)
+}
+
+// make_readerwriter takes a rstream and a wstream and makes
+// an rwstream with them
+pub fn make_readerwriter(r Reader, w Writer) ReaderWriterImpl {
+ return ReaderWriterImpl{
+ r: r
+ w: w
+ }
+}
diff --git a/v_windows/v/vlib/io/util/util.v b/v_windows/v/vlib/io/util/util.v
new file mode 100644
index 0000000..6f0d93f
--- /dev/null
+++ b/v_windows/v/vlib/io/util/util.v
@@ -0,0 +1,104 @@
+module util
+
+import os
+import rand
+import rand.seed as rseed
+
+const (
+ retries = 10000
+)
+
+pub struct TempFileOptions {
+ path string = os.temp_dir()
+ pattern string
+}
+
+// temp_file returns an uniquely named, open, writable, `os.File` and it's path
+pub fn temp_file(tfo TempFileOptions) ?(os.File, string) {
+ mut d := tfo.path
+ if d == '' {
+ d = os.temp_dir()
+ }
+ os.is_writable_folder(d) or {
+ return error(@FN +
+ ' could not create temporary file in "$d". Please ensure write permissions.')
+ }
+ d = d.trim_right(os.path_separator)
+ mut rng := rand.new_default()
+ prefix, suffix := prefix_and_suffix(tfo.pattern) or { return error(@FN + ' ' + err.msg) }
+ for retry := 0; retry < util.retries; retry++ {
+ path := os.join_path(d, prefix + random_number(mut rng) + suffix)
+ mut mode := 'rw+'
+ $if windows {
+ mode = 'w+'
+ }
+ mut file := os.open_file(path, mode, 0o600) or {
+ rng.seed(rseed.time_seed_array(2))
+ continue
+ }
+ if os.exists(path) && os.is_file(path) {
+ return file, path
+ }
+ }
+ return error(@FN +
+ ' could not create temporary file in "$d". Retry limit ($util.retries) exhausted. Please ensure write permissions.')
+}
+
+pub struct TempDirOptions {
+ path string = os.temp_dir()
+ pattern string
+}
+
+// temp_dir returns an uniquely named, writable, directory path
+pub fn temp_dir(tdo TempFileOptions) ?string {
+ mut d := tdo.path
+ if d == '' {
+ d = os.temp_dir()
+ }
+ os.is_writable_folder(d) or {
+ return error(@FN +
+ ' could not create temporary directory "$d". Please ensure write permissions.')
+ }
+ d = d.trim_right(os.path_separator)
+ mut rng := rand.new_default()
+ prefix, suffix := prefix_and_suffix(tdo.pattern) or { return error(@FN + ' ' + err.msg) }
+ for retry := 0; retry < util.retries; retry++ {
+ path := os.join_path(d, prefix + random_number(mut rng) + suffix)
+ os.mkdir_all(path) or {
+ rng.seed(rseed.time_seed_array(2))
+ continue
+ }
+ if os.is_dir(path) && os.exists(path) {
+ os.is_writable_folder(path) or {
+ return error(@FN +
+ ' could not create temporary directory "$d". Please ensure write permissions.')
+ }
+ return path
+ }
+ }
+ return error(@FN +
+ ' could not create temporary directory "$d". Retry limit ($util.retries) exhausted. Please ensure write permissions.')
+}
+
+// * Utility functions
+fn random_number(mut rng rand.PRNG) string {
+ s := (u32(1e9) + (u32(os.getpid()) + rng.u32() % u32(1e9))).str()
+ return s.substr(1, s.len)
+}
+
+fn prefix_and_suffix(pattern string) ?(string, string) {
+ mut pat := pattern
+ if pat.contains(os.path_separator) {
+ return error('pattern cannot contain path separators ($os.path_separator).')
+ }
+ pos := pat.last_index('*') or { -1 }
+ mut prefix := ''
+ mut suffix := ''
+ if pos != -1 {
+ prefix = pat.substr(0, pos)
+ suffix = pat.substr(pos + 1, pat.len)
+ } else {
+ prefix = pat
+ }
+ return prefix, suffix
+}
diff --git a/v_windows/v/vlib/io/util/util_test.v b/v_windows/v/vlib/io/util/util_test.v
new file mode 100644
index 0000000..18f0164
--- /dev/null
+++ b/v_windows/v/vlib/io/util/util_test.v
@@ -0,0 +1,127 @@
+import os
+import io.util
+
+const (
+ // tfolder will contain all the temporary files/subfolders made by
+ // the different tests. It would be removed in testsuite_end(), so
+ // individual os tests do not need to clean up after themselves.
+ tfolder = os.join_path(os.temp_dir(), 'v', 'tests', 'io_util_test')
+)
+
+fn testsuite_begin() {
+ eprintln('testsuite_begin, tfolder = $tfolder')
+ os.rmdir_all(tfolder) or {}
+ assert !os.is_dir(tfolder)
+ os.mkdir_all(tfolder) or { panic(err) }
+ os.chdir(tfolder) or {}
+ assert os.is_dir(tfolder)
+}
+
+fn testsuite_end() {
+ os.chdir(os.wd_at_startup) or {}
+ os.rmdir_all(tfolder) or {}
+ assert !os.is_dir(tfolder)
+ // eprintln('testsuite_end , tfolder = $tfolder removed.')
+}
+
+fn test_temp_file() {
+ // Test defaults
+ mut f, mut path := util.temp_file() or {
+ assert false
+ return
+ }
+ mut prev_path := path
+ defer {
+ f.close()
+ }
+ assert os.is_file(path)
+ assert f.is_opened
+ // Test pattern
+ f.close()
+ f, path = util.temp_file(
+ pattern: 'some_*_test.file'
+ ) or {
+ assert false
+ return
+ }
+ assert path != prev_path
+ assert os.is_file(path)
+ assert f.is_opened
+ mut filename := os.file_name(path)
+ assert filename.contains('_test.file')
+ // Check for 9 digits where the wildcard is placed in the pattern
+ for i, c in filename {
+ if i > 4 && i <= 4 + 9 {
+ assert c.is_digit()
+ }
+ }
+ // Test custom path
+ prev_path = path
+ f.close()
+ f, path = util.temp_file(
+ path: tfolder
+ ) or {
+ assert false
+ return
+ }
+ assert path != prev_path
+ assert os.is_file(path)
+ assert path.contains(tfolder)
+ assert f.is_opened
+ filename = os.file_name(path)
+ for c in filename {
+ assert c.is_digit()
+ }
+}
+
+fn test_temp_dir() {
+ // Test defaults
+ mut path := util.temp_dir() or {
+ assert false
+ return
+ }
+ assert os.is_dir(path)
+ mut writable := os.is_writable_folder(path) or {
+ assert false
+ return
+ }
+ assert writable
+ mut prev_path := path
+ // Test pattern
+ path = util.temp_dir(
+ pattern: 'some_*_test_dir'
+ ) or {
+ assert false
+ return
+ }
+ assert path != prev_path
+ assert os.is_dir(path)
+ mut filename := os.file_name(path)
+ assert filename.contains('_test_dir')
+ // Check for 9 digits where the wildcard is placed in the pattern
+ for i, c in filename {
+ if i > 4 && i <= 4 + 9 {
+ assert c.is_digit()
+ }
+ }
+ // Test custom path
+ prev_path = path
+ path = util.temp_dir(
+ path: tfolder
+ ) or {
+ assert false
+ return
+ }
+ assert path != prev_path
+ assert os.is_dir(path)
+ writable = os.is_writable_folder(path) or {
+ assert false
+ return
+ }
+ assert writable
+ assert path.contains(tfolder)
+ filename = os.file_name(path)
+ for c in filename {
+ assert c.is_digit()
+ }
+}
diff --git a/v_windows/v/vlib/io/writer.v b/v_windows/v/vlib/io/writer.v
new file mode 100644
index 0000000..322dd45
--- /dev/null
+++ b/v_windows/v/vlib/io/writer.v
@@ -0,0 +1,12 @@
+module io
+
+// Writer represents a stream of data that can be wrote to
+pub interface Writer {
+ write(buf []byte) ?int
+}
+
+// RandomWriter represents a stream of data that can be wrote to
+// at a random pos
+pub interface RandomWriter {
+ write_to(pos u64, buf []byte) ?int
+}