Initial project import
This commit is contained in:
commit
23f2d29e32
11957 files changed
+1754577
No files matched your search
@@ -0,0 +1,3 @@
|
||||
## Description
|
||||
|
||||
`io` provides common interfaces for buffered reading/writing of data.
|
||||
@@ -0,0 +1,168 @@
|
||||
module io
|
||||
|
||||
// BufferedReader provides a buffered interface for a reader.
|
||||
pub struct BufferedReader {
|
||||
mut:
|
||||
reader Reader
|
||||
buf []u8
|
||||
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
|
||||
total_read int // total number of bytes read
|
||||
}
|
||||
|
||||
// BufferedReaderConfig are options that can be given to a buffered reader.
|
||||
pub struct BufferedReaderConfig {
|
||||
pub:
|
||||
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
|
||||
}
|
||||
|
||||
// BufferedReadLineConfig are options that can be given to the read_line() function.
|
||||
@[params]
|
||||
pub struct BufferedReadLineConfig {
|
||||
pub:
|
||||
delim u8 = `\n` // line delimiter; default `\n` mode strips both `\n` and `\r\n`
|
||||
}
|
||||
|
||||
// new_buffered_reader creates a 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: []u8{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 []u8) !int {
|
||||
if buf.len == 0 {
|
||||
return 0
|
||||
}
|
||||
if r.end_of_stream {
|
||||
return Eof{}
|
||||
}
|
||||
// read data out of the buffer if we dont have any
|
||||
if r.needs_fill() {
|
||||
if !r.fill_buffer() {
|
||||
// end of stream
|
||||
return Eof{}
|
||||
}
|
||||
}
|
||||
read := copy(mut buf, r.buf[r.offset..r.len])
|
||||
if read == 0 {
|
||||
return NotExpected{
|
||||
cause: 'invalid copy of buffer'
|
||||
code: -1
|
||||
}
|
||||
}
|
||||
r.offset += read
|
||||
r.total_read += read
|
||||
return read
|
||||
}
|
||||
|
||||
// free deallocates the memory for a buffered reader's internal buffer.
|
||||
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 false
|
||||
}
|
||||
for {
|
||||
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 = 0
|
||||
return true
|
||||
}
|
||||
r.fails++
|
||||
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
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 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 reads until it finds the specified delimiter or the end of stream.
|
||||
// The returned string does not include the delimiter.
|
||||
// With the default delimiter `\n`, both `\n` and `\r\n` line endings are
|
||||
// accepted, and neither terminator byte is included in the returned string.
|
||||
pub fn (mut r BufferedReader) read_line(config BufferedReadLineConfig) !string {
|
||||
if r.end_of_stream {
|
||||
return Eof{}
|
||||
}
|
||||
mut line := []u8{}
|
||||
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 Eof{}
|
||||
}
|
||||
return line.bytestr()
|
||||
}
|
||||
}
|
||||
// try and find a newline character
|
||||
mut i := r.offset
|
||||
for ; i < r.len; i++ {
|
||||
r.total_read++
|
||||
c := r.buf[i]
|
||||
if c == config.delim {
|
||||
// great, we hit something
|
||||
mut end := i
|
||||
if config.delim == `\n` {
|
||||
if i > r.offset && r.buf[i - 1] == `\r` {
|
||||
end--
|
||||
} else if i == r.offset && line.len > 0 && line[line.len - 1] == `\r` {
|
||||
line.delete_last()
|
||||
}
|
||||
}
|
||||
line << r.buf[r.offset..end]
|
||||
r.offset = i + 1
|
||||
return line.bytestr()
|
||||
}
|
||||
}
|
||||
line << r.buf[r.offset..i]
|
||||
r.offset = i
|
||||
}
|
||||
return Eof{}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
module io
|
||||
|
||||
pub struct BufferedWriter {
|
||||
mut:
|
||||
n int
|
||||
wr Writer
|
||||
pub mut:
|
||||
buf []u8
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct BufferedWriterConfig {
|
||||
pub:
|
||||
writer Writer
|
||||
cap int = 128 * 1024
|
||||
}
|
||||
|
||||
// new_buffered_writer creates a new BufferedWriter with the specified BufferedWriterConfig.
|
||||
// Returns an error when cap is 0 or negative.
|
||||
pub fn new_buffered_writer(o BufferedWriterConfig) !&BufferedWriter {
|
||||
if o.cap < 1 {
|
||||
return error('`o.cap` must be a positive integer')
|
||||
}
|
||||
|
||||
return &BufferedWriter{
|
||||
buf: []u8{len: o.cap}
|
||||
wr: o.writer
|
||||
}
|
||||
}
|
||||
|
||||
// reset resets the buffer to its initial state.
|
||||
pub fn (mut b BufferedWriter) reset() {
|
||||
cap := b.buf.len
|
||||
b.buf = []u8{len: cap}
|
||||
b.n = 0
|
||||
}
|
||||
|
||||
// buffered returns the number of bytes currently stored in the buffer.
|
||||
pub fn (b BufferedWriter) buffered() int {
|
||||
return b.n
|
||||
}
|
||||
|
||||
// flush writes the buffered data to the underlying writer and clears the buffer, ensures all data is
|
||||
// written.
|
||||
// Returns an error if the writer fails to write all buffered data.
|
||||
pub fn (mut b BufferedWriter) flush() ! {
|
||||
if b.buffered() == 0 {
|
||||
return
|
||||
}
|
||||
mut written := 0
|
||||
for written < b.n {
|
||||
n := b.wr.write(b.buf[written..b.n]) or {
|
||||
b.shift_unwritten_to_front(written)
|
||||
return err
|
||||
}
|
||||
if n <= 0 || n > b.n - written {
|
||||
b.shift_unwritten_to_front(written)
|
||||
return error('writer returned an invalid number of bytes while flushing')
|
||||
}
|
||||
written += n
|
||||
}
|
||||
b.n = 0
|
||||
}
|
||||
|
||||
// available returns the amount of available space left in the buffer.
|
||||
pub fn (b BufferedWriter) available() int {
|
||||
return b.buf.len - b.n
|
||||
}
|
||||
|
||||
fn (mut b BufferedWriter) shift_unwritten_to_front(written int) {
|
||||
if written <= 0 {
|
||||
return
|
||||
}
|
||||
remaining := b.n - written
|
||||
if remaining <= 0 {
|
||||
b.n = 0
|
||||
return
|
||||
}
|
||||
copy(mut b.buf[..remaining], b.buf[written..b.n])
|
||||
b.n = remaining
|
||||
}
|
||||
|
||||
// write writes `src` in the buffer, flushing it to the underlying writer as needed, and returns the
|
||||
// number of bytes written.
|
||||
pub fn (mut b BufferedWriter) write(src []u8) !int {
|
||||
mut written := 0
|
||||
for written < src.len {
|
||||
remaining := src.len - written
|
||||
if b.buffered() == 0 && remaining > b.available() {
|
||||
n := b.wr.write(src[written..])!
|
||||
if n <= 0 || n > remaining {
|
||||
return error('writer returned an invalid number of bytes while writing')
|
||||
}
|
||||
written += n
|
||||
continue
|
||||
}
|
||||
n := copy(mut b.buf[b.n..], src[written..])
|
||||
b.n += n
|
||||
written += n
|
||||
if b.available() == 0 {
|
||||
b.flush()!
|
||||
}
|
||||
}
|
||||
return written
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
import io
|
||||
import rand
|
||||
|
||||
fn small_amount() int {
|
||||
return int(rand.u8()) + 1
|
||||
}
|
||||
|
||||
struct ArrayWriter {
|
||||
pub mut:
|
||||
result []u8
|
||||
}
|
||||
|
||||
// implement io.Writer
|
||||
fn (mut aw ArrayWriter) write(buf []u8) !int {
|
||||
len := buf.len
|
||||
mut res := 0
|
||||
for i := 0; i < len; i++ {
|
||||
aw.result << buf[i]
|
||||
res++
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
struct ChunkedArrayWriter {
|
||||
chunk int
|
||||
pub mut:
|
||||
result []u8
|
||||
}
|
||||
|
||||
fn (mut cw ChunkedArrayWriter) write(buf []u8) !int {
|
||||
if buf.len == 0 {
|
||||
return 0
|
||||
}
|
||||
n := if buf.len < cw.chunk { buf.len } else { cw.chunk }
|
||||
for i := 0; i < n; i++ {
|
||||
cw.result << buf[i]
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
struct FailOnceChunkedWriter {
|
||||
chunk int
|
||||
pub mut:
|
||||
result []u8
|
||||
mut:
|
||||
writes int
|
||||
}
|
||||
|
||||
fn (mut fw FailOnceChunkedWriter) write(buf []u8) !int {
|
||||
if fw.writes == 1 {
|
||||
fw.writes++
|
||||
return error('forced write failure')
|
||||
}
|
||||
fw.writes++
|
||||
n := if buf.len < fw.chunk { buf.len } else { fw.chunk }
|
||||
for i := 0; i < n; i++ {
|
||||
fw.result << buf[i]
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
struct ZeroProgressWriter {}
|
||||
|
||||
fn (mut w ZeroProgressWriter) write(buf []u8) !int {
|
||||
return 0
|
||||
}
|
||||
|
||||
// write less than max bytes, returns number of bytes written
|
||||
// data is written in chunks.
|
||||
fn write_random_data(mut aw ArrayWriter, mut bw io.BufferedWriter, max int) !int {
|
||||
less_than_max := max - 255 // guarantee 1 full u8 less than max
|
||||
mut total := 0
|
||||
for total < less_than_max {
|
||||
r := rand.u8()
|
||||
d := rand.bytes(r)!
|
||||
w := bw.write(d)!
|
||||
total += w
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
fn test_flush() {
|
||||
mut aw := ArrayWriter{}
|
||||
max := 65536
|
||||
mut bw := io.new_buffered_writer(writer: &aw, cap: max)!
|
||||
|
||||
// write less data than buffer capacity, the underlying writer should receive no data.
|
||||
written := write_random_data(mut aw, mut bw, 65536)!
|
||||
assert written == bw.buffered()
|
||||
assert aw.result.len == 0
|
||||
|
||||
bw.flush()!
|
||||
assert aw.result.len == written
|
||||
assert bw.buffered() == 0
|
||||
assert bw.available() == max
|
||||
}
|
||||
|
||||
fn test_write() {
|
||||
mut aw := ArrayWriter{}
|
||||
max := 65536
|
||||
mut bw := io.new_buffered_writer(writer: &aw, cap: max)!
|
||||
|
||||
// write less data than buffer capacity, the underlying writer should receive no data.
|
||||
written := write_random_data(mut aw, mut bw, 65536)!
|
||||
|
||||
// now exceed buffer capacity by a little
|
||||
little := small_amount()
|
||||
excess := bw.available() + little
|
||||
excess_data := rand.bytes(excess)!
|
||||
w := bw.write(excess_data)!
|
||||
assert bw.buffered() == little
|
||||
assert bw.available() == max - little
|
||||
assert aw.result.len == max
|
||||
}
|
||||
|
||||
fn test_write_big() {
|
||||
mut aw := ArrayWriter{}
|
||||
max := 65536
|
||||
mut bw := io.new_buffered_writer(writer: &aw, cap: max)!
|
||||
|
||||
more_than_max := max + small_amount()
|
||||
big_source := rand.bytes(more_than_max)!
|
||||
w := bw.write(big_source)!
|
||||
assert w == more_than_max
|
||||
assert bw.buffered() == 0
|
||||
assert bw.available() == max
|
||||
assert aw.result.len == more_than_max
|
||||
}
|
||||
|
||||
// create_data returns an array with `n` elements plus `\n` as last character.
|
||||
fn create_data(n int) []u8 {
|
||||
mut res := []u8{}
|
||||
for i := 0; i < n; i++ {
|
||||
res << `X`
|
||||
}
|
||||
res << `\n`
|
||||
return res
|
||||
}
|
||||
|
||||
fn test_simple_write() {
|
||||
mut aw := ArrayWriter{}
|
||||
mut bw := io.new_buffered_writer(writer: &aw, cap: 10)!
|
||||
mut data := create_data(6)
|
||||
w1 := bw.write(data)!
|
||||
|
||||
// add data to buffer, less than cap
|
||||
// data is written to buffer, not to underlying writer.
|
||||
assert w1 == 7 // 6*x + \n
|
||||
assert bw.buffered() == 7
|
||||
assert aw.result.len == 0
|
||||
|
||||
// add more data, exceed cap
|
||||
// data is flushed to underlying writer when buffer is full, then more data is written to buffer.
|
||||
w2 := bw.write(data)!
|
||||
assert w2 == 7
|
||||
assert bw.buffered() == 4
|
||||
assert aw.result.len == 10
|
||||
|
||||
// exceed cap immediately
|
||||
// all data is written without buffering
|
||||
aw.result = []u8{}
|
||||
data = create_data(33)
|
||||
bw.reset()
|
||||
w3 := bw.write(data)!
|
||||
assert bw.buffered() == 0
|
||||
assert aw.result.len == 34 // 33*x + \n
|
||||
}
|
||||
|
||||
fn test_simple_flush() {
|
||||
mut aw := ArrayWriter{}
|
||||
mut bw := io.new_buffered_writer(writer: &aw, cap: 10)!
|
||||
data := create_data(6)
|
||||
w := bw.write(data)!
|
||||
|
||||
assert w == 7 // 6*x + \n
|
||||
assert bw.buffered() == 7
|
||||
|
||||
bw.flush()!
|
||||
assert bw.buffered() == 0
|
||||
assert aw.result.len == 7
|
||||
}
|
||||
|
||||
fn test_flush_handles_partial_writes() {
|
||||
mut cw := ChunkedArrayWriter{
|
||||
chunk: 2
|
||||
}
|
||||
mut bw := io.new_buffered_writer(writer: &cw, cap: 8)!
|
||||
data := 'abcdefg'.bytes()
|
||||
|
||||
written := bw.write(data)!
|
||||
assert written == data.len
|
||||
assert bw.buffered() == data.len
|
||||
assert cw.result.len == 0
|
||||
|
||||
bw.flush()!
|
||||
assert bw.buffered() == 0
|
||||
assert cw.result == data
|
||||
}
|
||||
|
||||
fn test_flush_preserves_unwritten_data_on_error() {
|
||||
mut fw := FailOnceChunkedWriter{
|
||||
chunk: 3
|
||||
}
|
||||
mut bw := io.new_buffered_writer(writer: &fw, cap: 8)!
|
||||
data := 'abcdefg'.bytes()
|
||||
_ = bw.write(data)!
|
||||
|
||||
if _ := bw.flush() {
|
||||
assert false
|
||||
} else {
|
||||
assert err.msg() == 'forced write failure'
|
||||
}
|
||||
assert fw.result == 'abc'.bytes()
|
||||
assert bw.buffered() == 4
|
||||
assert bw.buf[..bw.buffered()] == 'defg'.bytes()
|
||||
|
||||
bw.flush()!
|
||||
assert bw.buffered() == 0
|
||||
assert fw.result == data
|
||||
}
|
||||
|
||||
fn test_write_returns_error_for_zero_progress_writes() {
|
||||
mut zw := ZeroProgressWriter{}
|
||||
mut bw := io.new_buffered_writer(writer: &zw, cap: 4)!
|
||||
|
||||
if _ := bw.write('12345'.bytes()) {
|
||||
assert false
|
||||
} else {
|
||||
assert err.msg() == 'writer returned an invalid number of bytes while writing'
|
||||
}
|
||||
assert bw.buffered() == 0
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
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 []u8) !int {
|
||||
$if debug {
|
||||
eprintln('>>>> StringReader.read output buf.len: ${buf.len}')
|
||||
}
|
||||
if s.place > s.text.len + 1 {
|
||||
return io.Eof{}
|
||||
}
|
||||
mut howmany := imin(buf.len, s.text.len - s.place)
|
||||
xxx := s.text[s.place..s.place + howmany].bytes()
|
||||
read := copy(mut buf, xxx)
|
||||
s.place += read
|
||||
return read
|
||||
}
|
||||
|
||||
fn read_from_string(text string, capacity int) []u8 {
|
||||
mut str := StringReader{
|
||||
text: text
|
||||
}
|
||||
mut stream := io.new_buffered_reader(reader: str, cap: capacity)
|
||||
|
||||
mut buf := []u8{len: 1}
|
||||
mut res := []u8{}
|
||||
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) == [u8(`a`)]
|
||||
assert read_from_string('ab', capacity) == [u8(`a`), `b`]
|
||||
assert read_from_string('abc', capacity) == [u8(`a`), `b`, `c`]
|
||||
assert read_from_string('abcde', capacity) == [u8(`a`), `b`, `c`, `d`, `e`]
|
||||
large_string_bytes := []u8{len: 1000, init: `x`}
|
||||
large_string := large_string_bytes.bytestr()
|
||||
assert read_from_string(large_string, capacity) == large_string_bytes
|
||||
}
|
||||
}
|
||||
|
||||
pub fn test_buffered_reader_readline() {
|
||||
text := 'hello\nworld'
|
||||
want := text.split_into_lines()
|
||||
mut str := StringReader{
|
||||
text: text
|
||||
}
|
||||
mut stream := io.new_buffered_reader(reader: str)
|
||||
mut buf := []u8{len: 1}
|
||||
mut i := 0
|
||||
for {
|
||||
line := stream.read_line() or {
|
||||
assert err is io.Eof
|
||||
break
|
||||
}
|
||||
assert i < want.len
|
||||
assert want[i] == line
|
||||
i++
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
# io.fs
|
||||
|
||||
`io.fs` defines small filesystem interfaces that can be shared by different
|
||||
backends and virtual filesystems.
|
||||
|
||||
The module is modeled after Go's `io/fs` package, but follows V naming and type
|
||||
conventions.
|
||||
|
||||
Current helpers:
|
||||
|
||||
- `valid_path(name string) bool`
|
||||
- `read_file(filesystem FS, name string) ![]u8`
|
||||
- `read_dir(filesystem FS, name string) ![]DirEntry`
|
||||
- `stat(filesystem FS, name string) !FileInfo`
|
||||
- `file_info_to_dir_entry(info FileInfo) DirEntry`
|
||||
@@ -0,0 +1,163 @@
|
||||
module fs
|
||||
|
||||
import io
|
||||
import os
|
||||
|
||||
// FileInfo describes a file or directory entry.
|
||||
pub interface FileInfo {
|
||||
name() string
|
||||
size() u64
|
||||
mode() os.FileMode
|
||||
mod_time() i64
|
||||
is_dir() bool
|
||||
}
|
||||
|
||||
// DirEntry describes a single directory entry.
|
||||
pub interface DirEntry {
|
||||
name() string
|
||||
is_dir() bool
|
||||
typ() os.FileType
|
||||
info() !FileInfo
|
||||
}
|
||||
|
||||
// File is the minimum interface required to read from a filesystem entry.
|
||||
pub interface File {
|
||||
io.Reader
|
||||
stat() !FileInfo
|
||||
mut:
|
||||
close()
|
||||
}
|
||||
|
||||
// FS opens slash-separated paths that pass `valid_path`.
|
||||
pub interface FS {
|
||||
open(name string) !File
|
||||
}
|
||||
|
||||
// ReadDirFile can read directory entries directly from an opened file.
|
||||
pub interface ReadDirFile {
|
||||
File
|
||||
mut:
|
||||
read_dir(n int) ![]DirEntry
|
||||
}
|
||||
|
||||
// ReadDirFS can read a directory without first opening it as a `File`.
|
||||
pub interface ReadDirFS {
|
||||
read_dir(name string) ![]DirEntry
|
||||
}
|
||||
|
||||
// ReadFileFS can read a whole file without first opening it as a `File`.
|
||||
pub interface ReadFileFS {
|
||||
read_file(name string) ![]u8
|
||||
}
|
||||
|
||||
// StatFS can stat a path without first opening it as a `File`.
|
||||
pub interface StatFS {
|
||||
stat(name string) !FileInfo
|
||||
}
|
||||
|
||||
// GlobFS can expand glob patterns within a filesystem.
|
||||
pub interface GlobFS {
|
||||
glob(pattern string) ![]string
|
||||
}
|
||||
|
||||
// SubFS can return a filesystem rooted at a subdirectory.
|
||||
pub interface SubFS {
|
||||
sub(dir string) !FS
|
||||
}
|
||||
|
||||
struct FileInfoDirEntry {
|
||||
source FileInfo
|
||||
}
|
||||
|
||||
fn (entry FileInfoDirEntry) name() string {
|
||||
return entry.source.name()
|
||||
}
|
||||
|
||||
fn (entry FileInfoDirEntry) is_dir() bool {
|
||||
return entry.source.is_dir()
|
||||
}
|
||||
|
||||
fn (entry FileInfoDirEntry) typ() os.FileType {
|
||||
return entry.source.mode().typ
|
||||
}
|
||||
|
||||
fn (entry FileInfoDirEntry) info() !FileInfo {
|
||||
return entry.source
|
||||
}
|
||||
|
||||
fn sort_dir_entries(mut entries []DirEntry) {
|
||||
entries.sort(a.name() < b.name())
|
||||
}
|
||||
|
||||
// valid_path reports whether `name` is a valid slash-separated path for `FS.open`.
|
||||
pub fn valid_path(name string) bool {
|
||||
if name == '.' {
|
||||
return true
|
||||
}
|
||||
if name.len == 0 {
|
||||
return false
|
||||
}
|
||||
for elem in name.split('/') {
|
||||
if elem.len == 0 || elem == '.' || elem == '..' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// file_info_to_dir_entry wraps a `FileInfo` in a `DirEntry`.
|
||||
pub fn file_info_to_dir_entry(info FileInfo) DirEntry {
|
||||
return FileInfoDirEntry{
|
||||
source: info
|
||||
}
|
||||
}
|
||||
|
||||
// read_file reads the named file from `filesystem`.
|
||||
pub fn read_file(filesystem FS, name string) ![]u8 {
|
||||
if filesystem is ReadFileFS {
|
||||
reader := filesystem as ReadFileFS
|
||||
return reader.read_file(name)
|
||||
}
|
||||
mut file := filesystem.open(name)!
|
||||
defer {
|
||||
file.close()
|
||||
}
|
||||
reader := file as io.Reader
|
||||
return io.read_all(
|
||||
reader: reader
|
||||
read_to_end_of_stream: true
|
||||
)
|
||||
}
|
||||
|
||||
// read_dir reads and sorts the named directory from `filesystem`.
|
||||
pub fn read_dir(filesystem FS, name string) ![]DirEntry {
|
||||
if filesystem is ReadDirFS {
|
||||
reader := filesystem as ReadDirFS
|
||||
mut entries := reader.read_dir(name)!
|
||||
sort_dir_entries(mut entries)
|
||||
return entries
|
||||
}
|
||||
mut file := filesystem.open(name)!
|
||||
defer {
|
||||
file.close()
|
||||
}
|
||||
if mut file is ReadDirFile {
|
||||
mut entries := file.read_dir(-1)!
|
||||
sort_dir_entries(mut entries)
|
||||
return entries
|
||||
}
|
||||
return error('fs: `${name}` does not support read_dir')
|
||||
}
|
||||
|
||||
// stat returns file information for the named path from `filesystem`.
|
||||
pub fn stat(filesystem FS, name string) !FileInfo {
|
||||
if filesystem is StatFS {
|
||||
stats := filesystem as StatFS
|
||||
return stats.stat(name)
|
||||
}
|
||||
mut file := filesystem.open(name)!
|
||||
defer {
|
||||
file.close()
|
||||
}
|
||||
return file.stat()
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
module fs
|
||||
|
||||
import io
|
||||
import os
|
||||
|
||||
struct MockInfo {
|
||||
name_ string
|
||||
size_ u64
|
||||
mode_ os.FileMode
|
||||
mod_time_ i64
|
||||
}
|
||||
|
||||
fn (info MockInfo) name() string {
|
||||
return info.name_
|
||||
}
|
||||
|
||||
fn (info MockInfo) size() u64 {
|
||||
return info.size_
|
||||
}
|
||||
|
||||
fn (info MockInfo) mode() os.FileMode {
|
||||
return info.mode_
|
||||
}
|
||||
|
||||
fn (info MockInfo) mod_time() i64 {
|
||||
return info.mod_time_
|
||||
}
|
||||
|
||||
fn (info MockInfo) is_dir() bool {
|
||||
return info.mode_.typ == .directory
|
||||
}
|
||||
|
||||
struct MockFile {
|
||||
data []u8
|
||||
info_ MockInfo
|
||||
mut:
|
||||
offset int
|
||||
entries []DirEntry
|
||||
}
|
||||
|
||||
fn (mut file MockFile) read(mut buf []u8) !int {
|
||||
if file.offset >= file.data.len {
|
||||
return io.Eof{}
|
||||
}
|
||||
read := copy(mut buf, file.data[file.offset..])
|
||||
file.offset += read
|
||||
return read
|
||||
}
|
||||
|
||||
fn (file MockFile) stat() !FileInfo {
|
||||
return file.info_
|
||||
}
|
||||
|
||||
fn (mut file MockFile) close() {}
|
||||
|
||||
fn (mut file MockFile) read_dir(n int) ![]DirEntry {
|
||||
if n <= 0 || n >= file.entries.len {
|
||||
return file.entries.clone()
|
||||
}
|
||||
return file.entries[..n].clone()
|
||||
}
|
||||
|
||||
struct FallbackFS {}
|
||||
|
||||
fn (filesystem FallbackFS) open(name string) !File {
|
||||
match name {
|
||||
'notes.txt' {
|
||||
return File(MockFile{
|
||||
data: 'fallback'.bytes()
|
||||
info_: MockInfo{
|
||||
name_: 'notes.txt'
|
||||
size_: u64('fallback'.len)
|
||||
mode_: os.FileMode{
|
||||
typ: .regular
|
||||
}
|
||||
mod_time_: 101
|
||||
}
|
||||
})
|
||||
}
|
||||
'dir' {
|
||||
return File(MockFile{
|
||||
info_: MockInfo{
|
||||
name_: 'dir'
|
||||
mode_: os.FileMode{
|
||||
typ: .directory
|
||||
}
|
||||
}
|
||||
entries: [
|
||||
file_info_to_dir_entry(MockInfo{
|
||||
name_: 'b.txt'
|
||||
mode_: os.FileMode{
|
||||
typ: .regular
|
||||
}
|
||||
}),
|
||||
file_info_to_dir_entry(MockInfo{
|
||||
name_: 'a.txt'
|
||||
mode_: os.FileMode{
|
||||
typ: .regular
|
||||
}
|
||||
}),
|
||||
]
|
||||
})
|
||||
}
|
||||
else {
|
||||
return error('missing ${name}')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct OptimizedFS {}
|
||||
|
||||
fn (filesystem OptimizedFS) open(name string) !File {
|
||||
return error('unexpected open for ${name}')
|
||||
}
|
||||
|
||||
fn (filesystem OptimizedFS) read_file(name string) ![]u8 {
|
||||
return 'optimized'.bytes()
|
||||
}
|
||||
|
||||
fn (filesystem OptimizedFS) read_dir(name string) ![]DirEntry {
|
||||
return [
|
||||
file_info_to_dir_entry(MockInfo{
|
||||
name_: 'z.txt'
|
||||
mode_: os.FileMode{
|
||||
typ: .regular
|
||||
}
|
||||
}),
|
||||
file_info_to_dir_entry(MockInfo{
|
||||
name_: 'm.txt'
|
||||
mode_: os.FileMode{
|
||||
typ: .regular
|
||||
}
|
||||
}),
|
||||
]
|
||||
}
|
||||
|
||||
fn (filesystem OptimizedFS) stat(name string) !FileInfo {
|
||||
return MockInfo{
|
||||
name_: name
|
||||
size_: 42
|
||||
mode_: os.FileMode{
|
||||
typ: .regular
|
||||
}
|
||||
mod_time_: 202
|
||||
}
|
||||
}
|
||||
|
||||
fn entry_names(entries []DirEntry) []string {
|
||||
mut names := []string{cap: entries.len}
|
||||
for entry in entries {
|
||||
names << entry.name()
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
fn test_valid_path() {
|
||||
assert valid_path('.')
|
||||
assert valid_path('alpha')
|
||||
assert valid_path('alpha/beta')
|
||||
assert valid_path(r'alpha\beta')
|
||||
assert !valid_path('')
|
||||
assert !valid_path('/alpha')
|
||||
assert !valid_path('alpha/')
|
||||
assert !valid_path('alpha//beta')
|
||||
assert !valid_path('alpha/./beta')
|
||||
assert !valid_path('alpha/../beta')
|
||||
}
|
||||
|
||||
fn test_file_info_to_dir_entry() {
|
||||
info := MockInfo{
|
||||
name_: 'subdir'
|
||||
size_: 12
|
||||
mode_: os.FileMode{
|
||||
typ: .directory
|
||||
}
|
||||
mod_time_: 303
|
||||
}
|
||||
entry := file_info_to_dir_entry(info)
|
||||
assert entry.name() == 'subdir'
|
||||
assert entry.is_dir()
|
||||
assert entry.typ() == .directory
|
||||
assert entry.info()!.mod_time() == 303
|
||||
}
|
||||
|
||||
fn test_read_file_uses_open_fallback() {
|
||||
data := read_file(FallbackFS{}, 'notes.txt')!
|
||||
assert data == 'fallback'.bytes()
|
||||
}
|
||||
|
||||
fn test_read_file_uses_read_file_fs_when_available() {
|
||||
data := read_file(OptimizedFS{}, 'notes.txt')!
|
||||
assert data == 'optimized'.bytes()
|
||||
}
|
||||
|
||||
fn test_read_dir_uses_open_fallback_and_sorts() {
|
||||
entries := read_dir(FallbackFS{}, 'dir')!
|
||||
assert entry_names(entries) == ['a.txt', 'b.txt']
|
||||
}
|
||||
|
||||
fn test_read_dir_uses_read_dir_fs_when_available() {
|
||||
entries := read_dir(OptimizedFS{}, 'dir')!
|
||||
assert entry_names(entries) == ['m.txt', 'z.txt']
|
||||
}
|
||||
|
||||
fn test_stat_uses_open_fallback() {
|
||||
info := stat(FallbackFS{}, 'notes.txt')!
|
||||
assert info.name() == 'notes.txt'
|
||||
assert info.size() == u64('fallback'.len)
|
||||
}
|
||||
|
||||
fn test_stat_uses_stat_fs_when_available() {
|
||||
info := stat(OptimizedFS{}, 'notes.txt')!
|
||||
assert info.name() == 'notes.txt'
|
||||
assert info.size() == 42
|
||||
assert info.mod_time() == 202
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
module io
|
||||
|
||||
// CopySettings provides additional options to io.cp
|
||||
@[params]
|
||||
pub struct CopySettings {
|
||||
pub mut:
|
||||
buffer_size int = 64 * 1024 // The buffer size used during the copying. A larger buffer is more performant, but uses more RAM.
|
||||
}
|
||||
|
||||
// cp copies from `src` to `dst` by allocating
|
||||
// a maximum of 1024 bytes buffer for reading
|
||||
// until either EOF is reached on `src` or an error occurs.
|
||||
// An error is returned if an error is encountered during write.
|
||||
@[manualfree]
|
||||
pub fn cp(mut src Reader, mut dst Writer, params CopySettings) ! {
|
||||
mut buf := []u8{len: params.buffer_size}
|
||||
defer {
|
||||
unsafe {
|
||||
buf.free()
|
||||
}
|
||||
}
|
||||
for {
|
||||
bytes := src.read(mut buf) or { break }
|
||||
dst.write(buf[..bytes]) or { return err }
|
||||
}
|
||||
}
|
||||
@@ -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(mut r, mut stdout)!
|
||||
assert true
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import io
|
||||
|
||||
struct Buf {
|
||||
pub:
|
||||
bytes []u8
|
||||
mut:
|
||||
i int
|
||||
}
|
||||
|
||||
struct Writ {
|
||||
pub mut:
|
||||
bytes []u8
|
||||
}
|
||||
|
||||
fn (mut b Buf) read(mut buf []u8) !int {
|
||||
if !(b.i < b.bytes.len) {
|
||||
return io.Eof{}
|
||||
}
|
||||
n := copy(mut buf, b.bytes[b.i..])
|
||||
b.i += n
|
||||
return n
|
||||
}
|
||||
|
||||
fn (mut w Writ) write(buf []u8) !int {
|
||||
if buf.len <= 0 {
|
||||
return error('none')
|
||||
}
|
||||
w.bytes << buf
|
||||
return buf.len
|
||||
}
|
||||
|
||||
fn test_copy() {
|
||||
mut src := Buf{
|
||||
bytes: 'abcdefghij'.repeat(10).bytes()
|
||||
}
|
||||
mut dst := Writ{
|
||||
bytes: []u8{}
|
||||
}
|
||||
io.cp(mut src, mut dst) or { assert false }
|
||||
assert dst.bytes == src.bytes
|
||||
}
|
||||
@@ -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 []u8) !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
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
module io
|
||||
|
||||
fn test_multi_writer_write_successful() {
|
||||
mut mw := new_multi_writer(&TestWriter{}, &TestWriter{})
|
||||
n := mw.write('0123456789'.bytes()) or {
|
||||
assert false
|
||||
return
|
||||
}
|
||||
assert n == 10
|
||||
}
|
||||
|
||||
fn test_multi_writer_write_incomplete() {
|
||||
mut mw := new_multi_writer(&TestWriter{}, &TestIncompleteWriter{})
|
||||
_ := mw.write('0123456789'.bytes()) or { return }
|
||||
assert false
|
||||
}
|
||||
|
||||
fn test_multi_writer_write_error() {
|
||||
mut mw := new_multi_writer(&TestWriter{}, &TestErrorWriter{}, &TestWriter{})
|
||||
_ := mw.write('0123456789'.bytes()) or { return }
|
||||
assert false
|
||||
}
|
||||
|
||||
struct TestWriter {
|
||||
pub mut:
|
||||
bytes []u8
|
||||
}
|
||||
|
||||
fn (mut w TestWriter) write(buf []u8) !int {
|
||||
w.bytes << buf
|
||||
return buf.len
|
||||
}
|
||||
|
||||
struct TestIncompleteWriter {
|
||||
pub mut:
|
||||
bytes []u8
|
||||
}
|
||||
|
||||
fn (mut w TestIncompleteWriter) write(buf []u8) !int {
|
||||
b := buf[..buf.len - 1]
|
||||
w.bytes << b
|
||||
return b.len
|
||||
}
|
||||
|
||||
struct TestErrorWriter {}
|
||||
|
||||
fn (mut w TestErrorWriter) write(buf []u8) !int {
|
||||
return error('error writer errored')
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,91 @@
|
||||
module io
|
||||
|
||||
// Eof error means that we reach the end of the stream.
|
||||
pub struct Eof {
|
||||
Error
|
||||
}
|
||||
|
||||
// NotExpected is a generic error that means that we receave a not expected error.
|
||||
pub struct NotExpected {
|
||||
cause string
|
||||
code int
|
||||
}
|
||||
|
||||
fn (err NotExpected) msg() string {
|
||||
return err.cause
|
||||
}
|
||||
|
||||
fn (err NotExpected) code() int {
|
||||
return err.code
|
||||
}
|
||||
|
||||
// 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
|
||||
// `io.Eof` on end of stream (EOF) instead of just returning 0
|
||||
mut:
|
||||
read(mut buf []u8) !int
|
||||
}
|
||||
|
||||
pub const read_all_len = 10 * 1024
|
||||
pub const read_all_grow_len = 1024
|
||||
|
||||
// ReadAllConfig allows options to be passed for the behaviour
|
||||
// of read_all.
|
||||
pub struct ReadAllConfig {
|
||||
pub:
|
||||
read_to_end_of_stream bool
|
||||
reader Reader
|
||||
}
|
||||
|
||||
// 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) ![]u8 {
|
||||
mut r := config.reader
|
||||
read_till_eof := config.read_to_end_of_stream
|
||||
|
||||
mut b := []u8{len: read_all_len}
|
||||
mut read := 0
|
||||
for {
|
||||
new_read := r.read(mut b[read..]) or { break }
|
||||
if new_read < 0 {
|
||||
return error('io.read_all: reader returned a negative read count (${new_read})')
|
||||
}
|
||||
read += new_read
|
||||
if !read_till_eof && new_read == 0 {
|
||||
break
|
||||
}
|
||||
if b.len == read {
|
||||
unsafe { b.grow_len(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(mut r Reader) ![]u8 {
|
||||
mut b := []u8{len: read_all_len}
|
||||
mut read := 0
|
||||
for {
|
||||
new_read := r.read(mut b[read..]) or { return error('none') }
|
||||
if new_read < 0 {
|
||||
return error('io.read_any: reader returned a negative read count (${new_read})')
|
||||
}
|
||||
read += new_read
|
||||
if new_read == 0 {
|
||||
break
|
||||
}
|
||||
if b.len == read {
|
||||
unsafe { b.grow_len(read_all_grow_len) }
|
||||
}
|
||||
}
|
||||
return b[..read]
|
||||
}
|
||||
|
||||
// RandomReader represents a stream of readable data from at a random location.
|
||||
pub interface RandomReader {
|
||||
read_from(pos u64, mut buf []u8) !int
|
||||
}
|
||||
@@ -0,0 +1,302 @@
|
||||
module io
|
||||
|
||||
struct Buf {
|
||||
pub:
|
||||
bytes []u8
|
||||
mut:
|
||||
i int
|
||||
}
|
||||
|
||||
fn (mut b Buf) read(mut buf []u8) !int {
|
||||
if !(b.i < b.bytes.len) {
|
||||
return Eof{}
|
||||
}
|
||||
n := copy(mut 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 ZeroReadAfterDataReader {
|
||||
mut:
|
||||
call_count int
|
||||
}
|
||||
|
||||
fn (mut r ZeroReadAfterDataReader) read(mut buf []u8) !int {
|
||||
match r.call_count {
|
||||
0 {
|
||||
r.call_count++
|
||||
return copy(mut buf, 'hello'.bytes())
|
||||
}
|
||||
1 {
|
||||
r.call_count++
|
||||
return 0
|
||||
}
|
||||
else {
|
||||
return error('unexpected extra read after zero-length read')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_read_all_stops_on_zero_length_read_after_partial_data() {
|
||||
mut reader := &ZeroReadAfterDataReader{}
|
||||
res := read_all(reader: reader) or {
|
||||
assert false
|
||||
''.bytes()
|
||||
}
|
||||
assert res == 'hello'.bytes()
|
||||
assert reader.call_count == 2
|
||||
}
|
||||
|
||||
struct StringReaderTest {
|
||||
text string
|
||||
mut:
|
||||
place int
|
||||
}
|
||||
|
||||
fn (mut s StringReaderTest) read(mut buf []u8) !int {
|
||||
if s.place >= s.text.len {
|
||||
return Eof{}
|
||||
}
|
||||
read := copy(mut buf, s.text[s.place..].bytes())
|
||||
s.place += read
|
||||
return read
|
||||
}
|
||||
|
||||
struct ZeroThenDataReader {
|
||||
pub:
|
||||
text string
|
||||
mut:
|
||||
place int
|
||||
empty_reads int
|
||||
}
|
||||
|
||||
fn (mut s ZeroThenDataReader) read(mut buf []u8) !int {
|
||||
if buf.len == 0 {
|
||||
return 0
|
||||
}
|
||||
if s.empty_reads == 0 {
|
||||
s.empty_reads++
|
||||
return 0
|
||||
}
|
||||
if s.place >= s.text.len {
|
||||
return Eof{}
|
||||
}
|
||||
read := copy(mut buf, s.text[s.place..].bytes())
|
||||
s.place += read
|
||||
return read
|
||||
}
|
||||
|
||||
const newline_count = 100000
|
||||
|
||||
fn test_stringreadertest() {
|
||||
text := '12345\n'.repeat(newline_count)
|
||||
mut s := StringReaderTest{
|
||||
text: text
|
||||
}
|
||||
mut r := new_buffered_reader(reader: s)
|
||||
for i := 0; true; i++ {
|
||||
if _ := r.read_line() {
|
||||
} else {
|
||||
assert i == 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_stringreadertest2() {
|
||||
text := '12345\r\n'.repeat(newline_count)
|
||||
mut s := StringReaderTest{
|
||||
text: text
|
||||
}
|
||||
mut r := new_buffered_reader(reader: s)
|
||||
for i := 0; true; i++ {
|
||||
if _ := r.read_line() {
|
||||
} else {
|
||||
assert i == 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 := StringReaderTest{
|
||||
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()
|
||||
}
|
||||
|
||||
fn test_read_line_strips_crlf_across_buffer_fills() {
|
||||
text := '12345\r\n67890\r\n'
|
||||
mut s := StringReaderTest{
|
||||
text: text
|
||||
}
|
||||
mut r := new_buffered_reader(reader: s, cap: 2)
|
||||
assert r.read_line()! == '12345'
|
||||
assert r.read_line()! == '67890'
|
||||
if _ := r.read_line() {
|
||||
assert false
|
||||
}
|
||||
assert r.end_of_stream()
|
||||
}
|
||||
|
||||
fn test_totalread_read() {
|
||||
text := 'Some testing text'
|
||||
mut s := StringReaderTest{
|
||||
text: text
|
||||
}
|
||||
mut r := new_buffered_reader(reader: s)
|
||||
|
||||
mut buf := []u8{len: text.len}
|
||||
total := r.read(mut buf) or {
|
||||
assert false
|
||||
panic('bad')
|
||||
}
|
||||
|
||||
assert r.total_read == total
|
||||
}
|
||||
|
||||
fn test_buffered_reader_retries_zero_length_reads() {
|
||||
text := 'Some testing text'
|
||||
mut s := ZeroThenDataReader{
|
||||
text: text
|
||||
}
|
||||
mut r := new_buffered_reader(reader: s, retries: 2)
|
||||
mut buf := []u8{len: text.len}
|
||||
total := r.read(mut buf) or {
|
||||
assert false
|
||||
panic('bad')
|
||||
}
|
||||
|
||||
assert total == text.len
|
||||
assert buf[..total] == text.bytes()
|
||||
assert r.total_read == total
|
||||
}
|
||||
|
||||
struct NegativeReader {
|
||||
mut:
|
||||
read_count int
|
||||
}
|
||||
|
||||
fn (mut r NegativeReader) read(mut _ []u8) !int {
|
||||
r.read_count++
|
||||
return -1
|
||||
}
|
||||
|
||||
fn test_read_all_errors_on_negative_read_count() {
|
||||
mut reader := &NegativeReader{}
|
||||
if _ := read_all(reader: reader) {
|
||||
assert false
|
||||
} else {
|
||||
assert err.msg() == 'io.read_all: reader returned a negative read count (-1)'
|
||||
}
|
||||
assert reader.read_count == 1
|
||||
}
|
||||
|
||||
fn test_read_any_errors_on_negative_read_count() {
|
||||
mut reader := &NegativeReader{}
|
||||
if _ := read_any(mut reader) {
|
||||
assert false
|
||||
} else {
|
||||
assert err.msg() == 'io.read_any: reader returned a negative read count (-1)'
|
||||
}
|
||||
assert reader.read_count == 1
|
||||
}
|
||||
|
||||
fn test_totalread_readline() {
|
||||
text := 'Some testing text\nmore_enters'
|
||||
mut s := StringReaderTest{
|
||||
text: text
|
||||
}
|
||||
mut r := new_buffered_reader(reader: s)
|
||||
|
||||
_ := r.read_line() or {
|
||||
assert false
|
||||
panic('bad')
|
||||
}
|
||||
_ := r.read_line() or {
|
||||
assert false
|
||||
panic('bad')
|
||||
}
|
||||
|
||||
assert r.total_read == text.len
|
||||
}
|
||||
|
||||
fn test_read_line_until_zero_terminated() {
|
||||
text := 'This is a test\0Nice try!\0'
|
||||
mut s := StringReaderTest{
|
||||
text: text
|
||||
}
|
||||
mut r := new_buffered_reader(reader: s)
|
||||
line1 := r.read_line(delim: `\0`) or {
|
||||
assert false
|
||||
panic('bad')
|
||||
}
|
||||
assert line1 == 'This is a test'
|
||||
line2 := r.read_line(delim: `\0`) or {
|
||||
assert false
|
||||
panic('bad')
|
||||
}
|
||||
assert line2 == 'Nice try!'
|
||||
if _ := r.read_line(delim: `\0`) {
|
||||
assert false
|
||||
panic('bad')
|
||||
}
|
||||
assert r.end_of_stream()
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
module io
|
||||
|
||||
// ReaderWriter represents a stream that can be read and written.
|
||||
pub interface ReaderWriter {
|
||||
Reader
|
||||
Writer
|
||||
}
|
||||
|
||||
// ReaderWriterImpl is a ReaderWriter that can be made from
|
||||
// a separate reader and writer (see fn make_readerwriter).
|
||||
struct ReaderWriterImpl {
|
||||
mut:
|
||||
r Reader
|
||||
w Writer
|
||||
}
|
||||
|
||||
// read reads up to `buf.len` bytes into `buf`. It returns
|
||||
// the number of bytes read or any error encountered.
|
||||
pub fn (mut r ReaderWriterImpl) read(mut buf []u8) !int {
|
||||
return r.r.read(mut buf)
|
||||
}
|
||||
|
||||
// write writes `buf.len` bytes from `buf` to the underlying
|
||||
// data stream. It returns the number of bytes written or any error
|
||||
// encountered.
|
||||
pub fn (mut r ReaderWriterImpl) write(buf []u8) !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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,329 @@
|
||||
@[deprecated: 'If you use it, create a local module named `string_reader`, and copy https://github.com/vlang/v/blob/f24d49259db5d2d7211e79bcf6b6f507d22847f3/vlib/io/string_reader/string_reader.v there.']
|
||||
@[deprecated_after: '2026-01-06']
|
||||
module string_reader
|
||||
|
||||
import io
|
||||
import strings
|
||||
|
||||
@[params]
|
||||
pub struct StringReaderParams {
|
||||
pub:
|
||||
// the reader interface
|
||||
reader ?io.Reader
|
||||
// initialize the builder with this source string
|
||||
source ?string
|
||||
// if no source is given the string builder is initialized with this size
|
||||
initial_size int
|
||||
}
|
||||
|
||||
// StringReader is able to read data from a Reader interface and/or source string to a dynamically
|
||||
// growing buffer using a string builder. Unlike the BufferedReader, StringReader will
|
||||
// keep the entire contents of the buffer in memory, allowing the incoming data to be reused
|
||||
// and read in an efficient matter. The StringReader will not set a maximum capacity to the string
|
||||
// builders buffer and could grow very large.
|
||||
pub struct StringReader {
|
||||
mut:
|
||||
reader ?io.Reader
|
||||
offset int // current offset in the buffer
|
||||
pub mut:
|
||||
end_of_stream bool // whether we reached the end of the upstream reader
|
||||
builder strings.Builder
|
||||
}
|
||||
|
||||
// new creates a new StringReader and sets the string builder size to `initial_size`.
|
||||
// If a source
|
||||
pub fn StringReader.new(params StringReaderParams) StringReader {
|
||||
mut r := StringReader{
|
||||
reader: params.reader
|
||||
}
|
||||
|
||||
if source := params.source {
|
||||
r.builder = strings.new_builder(source.len)
|
||||
r.builder.write_string(source)
|
||||
if params.reader == none {
|
||||
// There is no upstream reader; only the preloaded buffer can be consumed.
|
||||
r.end_of_stream = true
|
||||
}
|
||||
} else {
|
||||
r.builder = strings.new_builder(params.initial_size)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// needs_fill returns whether the buffer needs refilling
|
||||
pub fn (r StringReader) needs_fill() bool {
|
||||
return r.offset >= r.builder.len
|
||||
}
|
||||
|
||||
// needs_fill_until returns whether the buffer needs refilling in order to read
|
||||
// `n` bytes
|
||||
pub fn (r StringReader) needs_fill_until(n int) bool {
|
||||
return r.offset + n > r.builder.len
|
||||
}
|
||||
|
||||
// fill_bufer tries to read data into the buffer until either a 0 length read or if read_to_end_of_stream
|
||||
// is true then the end of the stream. It returns the number of bytes read
|
||||
pub fn (mut r StringReader) fill_buffer(read_till_end_of_stream bool) !int {
|
||||
if r.end_of_stream {
|
||||
return io.Eof{}
|
||||
}
|
||||
|
||||
mut reader := r.reader or { return error('reader is not set') }
|
||||
|
||||
start := r.builder.len
|
||||
mut end := start
|
||||
|
||||
// make sure there is enough room in the string builder
|
||||
unsafe { r.builder.grow_len(io.read_all_len) }
|
||||
defer {
|
||||
// shrink the length of the buffer to the total of bytes read
|
||||
r.builder.go_back(r.builder.len - end)
|
||||
}
|
||||
|
||||
for {
|
||||
read := reader.read(mut r.builder[end..]) or {
|
||||
r.end_of_stream = true
|
||||
break
|
||||
}
|
||||
end += read
|
||||
|
||||
if !read_till_end_of_stream || read == 0 {
|
||||
break
|
||||
} else if r.builder.len == end {
|
||||
unsafe { r.builder.grow_len(io.read_all_grow_len) }
|
||||
}
|
||||
}
|
||||
|
||||
if end == start {
|
||||
return io.Eof{}
|
||||
}
|
||||
|
||||
return end - start
|
||||
}
|
||||
|
||||
// fill_buffer_until tries read `n` amount of bytes from the reader into the buffer
|
||||
// and returns the actual number of bytes read
|
||||
pub fn (mut r StringReader) fill_buffer_until(n int) !int {
|
||||
if r.end_of_stream {
|
||||
return io.Eof{}
|
||||
}
|
||||
|
||||
mut reader := r.reader or { return error('reader is not set') }
|
||||
|
||||
start := r.builder.len
|
||||
// make sure there is enough room in the string builder
|
||||
if n > io.read_all_len {
|
||||
unsafe { r.builder.grow_len(io.read_all_len) }
|
||||
} else {
|
||||
unsafe { r.builder.grow_len(n) }
|
||||
}
|
||||
|
||||
mut end := start
|
||||
defer {
|
||||
// shrink the length of the buffer to the total of bytes read
|
||||
r.builder.go_back(r.builder.len - end)
|
||||
}
|
||||
for {
|
||||
read := reader.read(mut r.builder[end..]) or {
|
||||
r.end_of_stream = true
|
||||
break
|
||||
}
|
||||
end += read
|
||||
|
||||
if read == 0 || end - start == n {
|
||||
break
|
||||
} else if r.builder.len == end {
|
||||
remaining := n - (end - start)
|
||||
if remaining > io.read_all_grow_len {
|
||||
unsafe { r.builder.grow_len(io.read_all_grow_len) }
|
||||
} else {
|
||||
unsafe { r.builder.grow_len(remaining) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if end == start {
|
||||
return io.Eof{}
|
||||
}
|
||||
return end - start
|
||||
}
|
||||
|
||||
// read_all_bytes 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. It returns a copy of the read data
|
||||
pub fn (mut r StringReader) read_all_bytes(read_till_end_of_stream bool) ![]u8 {
|
||||
start := r.offset
|
||||
// ignore Eof error from fill buffer
|
||||
r.fill_buffer(read_till_end_of_stream) or {}
|
||||
r.offset = r.builder.len
|
||||
// check if there was still data in the buffer, but the reader has reached its end of stream
|
||||
if start == r.offset {
|
||||
return io.Eof{}
|
||||
}
|
||||
|
||||
return r.get_part(start, r.offset - start)!
|
||||
}
|
||||
|
||||
// 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. It produces a string from the read data
|
||||
pub fn (mut r StringReader) read_all(read_till_end_of_stream bool) !string {
|
||||
buf := r.read_all_bytes(read_till_end_of_stream)!
|
||||
return unsafe { tos(buf.data, buf.len) }
|
||||
}
|
||||
|
||||
// read_bytes tries to read n amount of bytes from the reader
|
||||
pub fn (mut r StringReader) read_bytes(n int) ![]u8 {
|
||||
start := r.offset
|
||||
|
||||
if r.needs_fill_until(n) {
|
||||
actual_read := r.fill_buffer_until(n - (r.builder.len - r.offset))!
|
||||
r.offset += actual_read
|
||||
} else {
|
||||
r.offset += n
|
||||
}
|
||||
|
||||
return r.get_part(start, r.offset - start)!
|
||||
}
|
||||
|
||||
// read_bytes tries to read `n` amount of bytes from the reader and produces a string
|
||||
// from the read data
|
||||
pub fn (mut r StringReader) read_string(n int) !string {
|
||||
buf := r.read_bytes(n)!
|
||||
return unsafe { tos(buf.data, buf.len) }
|
||||
}
|
||||
|
||||
// read implements the Reader interface
|
||||
pub fn (mut r StringReader) read(mut buf []u8) !int {
|
||||
if buf.len == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
available := r.builder.len - r.offset
|
||||
if available < buf.len && !r.end_of_stream {
|
||||
if r.reader != none {
|
||||
missing := buf.len - available
|
||||
r.fill_buffer_until(missing) or {
|
||||
if err !is io.Eof {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
available_after_fill := r.builder.len - r.offset
|
||||
if available_after_fill == 0 {
|
||||
if r.reader == none && !r.end_of_stream {
|
||||
return error('reader is not set')
|
||||
}
|
||||
return io.Eof{}
|
||||
}
|
||||
|
||||
read := if available_after_fill < buf.len { available_after_fill } else { buf.len }
|
||||
start := r.offset
|
||||
r.offset += read
|
||||
|
||||
copy(mut buf, r.builder[start..start + read])
|
||||
return read
|
||||
}
|
||||
|
||||
// read_line attempts to read a line from the reader.
|
||||
// It reads until it finds the specified delimiter or the end of stream.
|
||||
// The returned string does not include the delimiter.
|
||||
// When the delimiter is `\n`, a preceding `\r` is treated as part of CRLF
|
||||
// and is also omitted from the returned string.
|
||||
@[direct_array_access]
|
||||
pub fn (mut r StringReader) read_line(config io.BufferedReadLineConfig) !string {
|
||||
if r.end_of_stream && r.needs_fill() {
|
||||
return io.Eof{}
|
||||
}
|
||||
|
||||
start := r.offset
|
||||
for {
|
||||
if r.needs_fill() {
|
||||
r.fill_buffer(false) or {
|
||||
// we are at the end of the stream
|
||||
if r.offset == start {
|
||||
return io.Eof{}
|
||||
}
|
||||
return r.get_string_part(start, r.offset - start)!
|
||||
}
|
||||
}
|
||||
// try to find a newline character
|
||||
mut i := r.offset
|
||||
for ; i < r.builder.len; i++ {
|
||||
c := r.builder[i]
|
||||
if c == config.delim {
|
||||
// great, we hit something
|
||||
// do some checking for whether we hit \r\n or just \n
|
||||
mut x := i
|
||||
if i > start && config.delim == `\n` && r.builder[i - 1] == `\r` {
|
||||
x--
|
||||
}
|
||||
r.offset = i + 1
|
||||
return r.get_string_part(start, x - start)!
|
||||
}
|
||||
}
|
||||
r.offset = i
|
||||
}
|
||||
|
||||
return io.Eof{}
|
||||
}
|
||||
|
||||
// write implements the Writer interface
|
||||
pub fn (mut r StringReader) write(buf []u8) !int {
|
||||
return r.builder.write(buf)!
|
||||
}
|
||||
|
||||
// get_data returns a copy of the buffer
|
||||
@[inline]
|
||||
pub fn (r StringReader) get_data() []u8 {
|
||||
unsafe {
|
||||
mut x := malloc_noscan(r.builder.len)
|
||||
vmemcpy(x, &u8(r.builder.data), r.builder.len)
|
||||
return x.vbytes(r.builder.len)
|
||||
}
|
||||
}
|
||||
|
||||
// get get_part returns a copy of a part of the buffer from `start` till `start` + `n`
|
||||
pub fn (r StringReader) get_part(start int, n int) ![]u8 {
|
||||
if start + n > r.builder.len {
|
||||
return io.Eof{}
|
||||
}
|
||||
|
||||
unsafe {
|
||||
mut x := malloc_noscan(n)
|
||||
vmemcpy(x, &u8(r.builder.data) + start, n)
|
||||
return x.vbytes(n)
|
||||
}
|
||||
}
|
||||
|
||||
// get_string produces a string from all the bytes in the buffer
|
||||
@[inline]
|
||||
pub fn (r StringReader) get_string() string {
|
||||
return r.builder.spart(0, r.builder.len)
|
||||
}
|
||||
|
||||
// get_string_part produces a string from `start` till `start` + `n` of the buffer
|
||||
pub fn (r StringReader) get_string_part(start int, n int) !string {
|
||||
if start + n > r.builder.len {
|
||||
return io.Eof{}
|
||||
}
|
||||
|
||||
return r.builder.spart(start, n)
|
||||
}
|
||||
|
||||
// flush clears the stringbuilder and returns the resulting string and the stringreaders
|
||||
// offset is reset to 0
|
||||
pub fn (mut r StringReader) flush() string {
|
||||
r.offset = 0
|
||||
return r.builder.str()
|
||||
}
|
||||
|
||||
// free frees the memory block used for the string builders buffer,
|
||||
// a new string builder with size 0 is initialized and the stringreaders offset is reset to 0
|
||||
@[unsafe]
|
||||
pub fn (mut r StringReader) free() {
|
||||
unsafe { r.builder.free() }
|
||||
r.builder = strings.new_builder(0)
|
||||
r.offset = 0
|
||||
}
|
||||
@@ -0,0 +1,409 @@
|
||||
module string_reader
|
||||
|
||||
import io
|
||||
|
||||
struct Buf {
|
||||
pub:
|
||||
bytes []u8
|
||||
mut:
|
||||
i int
|
||||
}
|
||||
|
||||
struct TwoByteReader {
|
||||
mut:
|
||||
data string
|
||||
pos int
|
||||
}
|
||||
|
||||
fn (mut b Buf) read(mut buf []u8) !int {
|
||||
if !(b.i < b.bytes.len) {
|
||||
return io.Eof{}
|
||||
}
|
||||
n := copy(mut buf, b.bytes[b.i..])
|
||||
b.i += n
|
||||
return n
|
||||
}
|
||||
|
||||
fn (mut r TwoByteReader) read(mut buf []u8) !int {
|
||||
if r.pos >= r.data.len {
|
||||
return io.Eof{}
|
||||
}
|
||||
min := int_min(int_min(r.data.len - r.pos, 2), buf.len)
|
||||
for i in 0 .. min {
|
||||
buf[i] = r.data[r.pos]
|
||||
r.pos++
|
||||
}
|
||||
return min
|
||||
}
|
||||
|
||||
fn read_from_reader_interface(mut r io.Reader, mut buf []u8) !int {
|
||||
return r.read(mut buf)
|
||||
}
|
||||
|
||||
fn test_read_all() {
|
||||
mut reader := StringReader.new()
|
||||
|
||||
if _ := reader.read_all(false) {
|
||||
assert false, 'should return io.Eof'
|
||||
} else {
|
||||
assert err is io.Eof
|
||||
}
|
||||
|
||||
contents := 'test string'
|
||||
buf := Buf{
|
||||
bytes: contents.bytes()
|
||||
}
|
||||
|
||||
reader = StringReader.new(reader: buf)
|
||||
data := reader.read_all(false)!
|
||||
|
||||
assert data == contents
|
||||
}
|
||||
|
||||
fn test_read_bytes() {
|
||||
mut reader := StringReader.new()
|
||||
|
||||
if _ := reader.read_bytes(4) {
|
||||
assert false, 'should return io.Eof'
|
||||
} else {
|
||||
assert err.msg() == 'reader is not set'
|
||||
}
|
||||
|
||||
buf := Buf{
|
||||
bytes: '12345678'.bytes()
|
||||
}
|
||||
|
||||
reader = StringReader.new(reader: buf)
|
||||
mut data := reader.read_bytes(4)!
|
||||
assert data == buf.bytes[..4]
|
||||
|
||||
data = reader.read_bytes(4)!
|
||||
assert data == buf.bytes[4..8]
|
||||
}
|
||||
|
||||
fn test_read_line() {
|
||||
mut reader := StringReader.new()
|
||||
|
||||
if _ := reader.read_line() {
|
||||
assert false, 'should return io.Eof'
|
||||
} else {
|
||||
assert err is io.Eof
|
||||
}
|
||||
|
||||
buf := Buf{
|
||||
bytes: 'first line\r\nsecond line\n'.bytes()
|
||||
}
|
||||
|
||||
reader = StringReader.new(reader: buf)
|
||||
|
||||
assert reader.read_line()! == 'first line'
|
||||
assert reader.read_line()! == 'second line'
|
||||
}
|
||||
|
||||
fn test_from_string() {
|
||||
mut reader := StringReader.new(source: 'test')
|
||||
assert reader.read_all(false)! == 'test'
|
||||
|
||||
if _ := reader.read_all(false) {
|
||||
assert false, 'should return Eof'
|
||||
} else {
|
||||
assert err is io.Eof
|
||||
}
|
||||
}
|
||||
|
||||
fn test_read_from_source_read() {
|
||||
source := 'line 1\nline 2\n'
|
||||
mut reader := StringReader.new(source: source)
|
||||
mut buf := []u8{len: 100}
|
||||
read := reader.read(mut buf)!
|
||||
assert read == source.len
|
||||
assert buf[..read].bytestr() == source
|
||||
if _ := reader.read(mut buf) {
|
||||
assert false, 'should return io.Eof'
|
||||
} else {
|
||||
assert err is io.Eof
|
||||
}
|
||||
}
|
||||
|
||||
fn test_read_from_source_read_via_reader_interface() {
|
||||
source := 'line 1\nline 2\n'
|
||||
mut reader := StringReader.new(source: source)
|
||||
mut buf := []u8{len: 100}
|
||||
read := read_from_reader_interface(mut reader, mut buf)!
|
||||
assert read == source.len
|
||||
assert buf[..read].bytestr() == source
|
||||
if _ := read_from_reader_interface(mut reader, mut buf) {
|
||||
assert false, 'should return io.Eof'
|
||||
} else {
|
||||
assert err is io.Eof
|
||||
}
|
||||
}
|
||||
|
||||
fn test_from_string_read_byte_one_by_one() {
|
||||
mut reader := StringReader.new(source: 'test')
|
||||
assert reader.read_bytes(1)![0].ascii_str() == 't'
|
||||
assert reader.read_bytes(1)![0].ascii_str() == 'e'
|
||||
assert reader.read_bytes(1)![0].ascii_str() == 's'
|
||||
assert reader.read_bytes(1)![0].ascii_str() == 't'
|
||||
|
||||
if _ := reader.read_all(false) {
|
||||
assert false, 'should return Eof'
|
||||
} else {
|
||||
assert err is io.Eof
|
||||
}
|
||||
}
|
||||
|
||||
fn test_from_string_and_reader() {
|
||||
buf := Buf{
|
||||
bytes: 'buffer'.bytes()
|
||||
}
|
||||
|
||||
mut reader := StringReader.new(reader: buf, source: 'string')
|
||||
|
||||
assert reader.read_all(false)! == 'stringbuffer'
|
||||
}
|
||||
|
||||
fn test_flush() {
|
||||
mut reader := StringReader.new(source: 'flushed data')
|
||||
|
||||
str := reader.flush()
|
||||
assert str == 'flushed data'
|
||||
|
||||
assert reader.offset == 0
|
||||
assert reader.builder.len == 0
|
||||
}
|
||||
|
||||
fn test_fill_buffer_true() {
|
||||
mut two := TwoByteReader{
|
||||
data: '12345'
|
||||
}
|
||||
mut reader := StringReader.new(reader: two)
|
||||
assert reader.fill_buffer(true)! == 5
|
||||
assert reader.get_string() == '12345'
|
||||
}
|
||||
|
||||
fn test_fill_buffer_false() {
|
||||
mut two := TwoByteReader{
|
||||
data: '12345'
|
||||
}
|
||||
mut reader := StringReader.new(reader: two)
|
||||
assert reader.fill_buffer(false)! == 2
|
||||
assert reader.get_string() == '12'
|
||||
}
|
||||
|
||||
fn test_fill_buffer_until() {
|
||||
mut two := TwoByteReader{
|
||||
data: '12345'
|
||||
}
|
||||
mut reader := StringReader.new(reader: two)
|
||||
assert reader.fill_buffer_until(1)! == 1
|
||||
assert reader.builder.len == 1
|
||||
assert reader.fill_buffer_until(2)! == 2
|
||||
assert reader.builder.len == 3
|
||||
assert reader.fill_buffer_until(2)! == 2
|
||||
assert reader.builder.len == 5
|
||||
assert reader.get_string() == '12345'
|
||||
}
|
||||
|
||||
fn test_fill_buffer_until_one() {
|
||||
mut two := TwoByteReader{
|
||||
data: '12345'
|
||||
}
|
||||
mut reader := StringReader.new(reader: two)
|
||||
assert reader.fill_buffer_until(1)! == 1
|
||||
assert reader.builder.len == 1
|
||||
assert reader.fill_buffer_until(1)! == 1
|
||||
assert reader.builder.len == 2
|
||||
assert reader.fill_buffer_until(1)! == 1
|
||||
assert reader.builder.len == 3
|
||||
assert reader.fill_buffer_until(1)! == 1
|
||||
assert reader.builder.len == 4
|
||||
assert reader.fill_buffer_until(1)! == 1
|
||||
assert reader.builder.len == 5
|
||||
assert reader.get_string() == '12345'
|
||||
}
|
||||
|
||||
fn test_fill_buffer_until_many() {
|
||||
mut two := TwoByteReader{
|
||||
data: '12345'
|
||||
}
|
||||
mut reader := StringReader.new(reader: two)
|
||||
assert reader.fill_buffer_until(1)! == 1
|
||||
assert reader.builder.len == 1
|
||||
assert reader.fill_buffer_until(12)! == 4
|
||||
assert reader.builder.len == 5
|
||||
assert reader.fill_buffer_until(123) or { -1 } == -1
|
||||
assert reader.builder.len == 5
|
||||
assert reader.get_string() == '12345'
|
||||
}
|
||||
|
||||
fn test_read_all_bytes_false() {
|
||||
mut two := TwoByteReader{
|
||||
data: '12345'
|
||||
}
|
||||
mut reader := StringReader.new(reader: two)
|
||||
assert reader.read_all_bytes(false)! == [u8(49), 50]
|
||||
assert reader.read_all_bytes(false)! == [u8(51), 52]
|
||||
assert reader.read_all_bytes(false)! == [u8(53)]
|
||||
}
|
||||
|
||||
fn test_read_all_bytes_true() {
|
||||
mut two := TwoByteReader{
|
||||
data: '12345'
|
||||
}
|
||||
mut reader := StringReader.new(reader: two)
|
||||
assert reader.read_all_bytes(false)! == [u8(49), 50]
|
||||
assert reader.read_all_bytes(true)! == [u8(51), 52, 53]
|
||||
}
|
||||
|
||||
fn test_read_all_false() {
|
||||
mut two := TwoByteReader{
|
||||
data: '12345'
|
||||
}
|
||||
mut reader := StringReader.new(reader: two)
|
||||
assert reader.read_all(false)! == '12'
|
||||
assert reader.read_all(false)! == '34'
|
||||
assert reader.read_all(false)! == '5'
|
||||
}
|
||||
|
||||
fn test_read_all_true() {
|
||||
mut two := TwoByteReader{
|
||||
data: '12345'
|
||||
}
|
||||
mut reader := StringReader.new(reader: two)
|
||||
assert reader.read_all(false)! == '12'
|
||||
assert reader.read_all(true)! == '345'
|
||||
}
|
||||
|
||||
fn test_read_bytes_1() {
|
||||
mut two := TwoByteReader{
|
||||
data: '12345'
|
||||
}
|
||||
mut reader := StringReader.new(reader: two)
|
||||
assert reader.read_bytes(1)! == [u8(49)]
|
||||
assert reader.read_bytes(2)! == [u8(50), 51]
|
||||
assert reader.read_bytes(2)! == [u8(52), 53]
|
||||
assert reader.read_bytes(2) or { [u8(255)] } == [u8(255)]
|
||||
}
|
||||
|
||||
fn test_read_bytes_many() {
|
||||
mut two := TwoByteReader{
|
||||
data: '12345'
|
||||
}
|
||||
mut reader := StringReader.new(reader: two)
|
||||
assert reader.read_bytes(1)! == [u8(49)]
|
||||
assert reader.builder.len == 1
|
||||
assert reader.offset == 1
|
||||
assert reader.read_bytes(123)! == [u8(50), 51, 52, 53]
|
||||
assert reader.builder.len == 5
|
||||
assert reader.offset == 5
|
||||
}
|
||||
|
||||
fn test_read_string() {
|
||||
mut two := TwoByteReader{
|
||||
data: '12345'
|
||||
}
|
||||
mut reader := StringReader.new(reader: two)
|
||||
assert reader.read_string(1)! == '1'
|
||||
assert reader.read_string(2)! == '23'
|
||||
assert reader.read_string(3)! == '45'
|
||||
}
|
||||
|
||||
fn test_read() {
|
||||
mut two := TwoByteReader{
|
||||
data: '12345'
|
||||
}
|
||||
mut reader := StringReader.new(reader: two)
|
||||
mut o1 := []u8{len: 1}
|
||||
mut o5 := []u8{len: 5}
|
||||
mut res := reader.read(mut o1)!
|
||||
assert res == 1
|
||||
assert o1[..res] == [u8(49)]
|
||||
res = reader.read(mut o5)!
|
||||
assert res == 4
|
||||
assert o5[..res] == [u8(50), 51, 52, 53]
|
||||
}
|
||||
|
||||
fn test_read_many() {
|
||||
mut two := TwoByteReader{
|
||||
data: '12345'
|
||||
}
|
||||
mut reader := StringReader.new(reader: two)
|
||||
mut o4 := []u8{len: 4}
|
||||
mut res := reader.read(mut o4)!
|
||||
assert res == 4
|
||||
assert o4[..res] == [u8(49), 50, 51, 52]
|
||||
res = reader.read(mut o4)!
|
||||
assert res == 1
|
||||
assert o4[..res] == [u8(53)]
|
||||
res = reader.read(mut o4) or { -1 }
|
||||
assert res == -1
|
||||
assert o4 == [u8(53), 50, 51, 52]
|
||||
}
|
||||
|
||||
fn test_read_line_2() {
|
||||
mut two := TwoByteReader{
|
||||
data: '12345\n67890'
|
||||
}
|
||||
mut reader := StringReader.new(reader: two)
|
||||
assert reader.read_line()! == '12345'
|
||||
assert reader.read_line()! == '67890'
|
||||
}
|
||||
|
||||
fn test_read_line_3() {
|
||||
mut two := TwoByteReader{
|
||||
data: '12345\t67890'
|
||||
}
|
||||
mut reader := StringReader.new(reader: two)
|
||||
assert reader.read_line(delim: `\t`)! == '12345'
|
||||
assert reader.read_line()! == '67890'
|
||||
}
|
||||
|
||||
fn test_read_line_4() {
|
||||
mut two := TwoByteReader{
|
||||
data: '12345\t67890'
|
||||
}
|
||||
mut reader := StringReader.new(reader: two)
|
||||
assert reader.read_line(delim: `\t`)! == '12345'
|
||||
assert reader.read_line(delim: `\t`)! == '67890'
|
||||
}
|
||||
|
||||
fn test_read_line_5() {
|
||||
mut two := TwoByteReader{
|
||||
data: '\n12345\n67890'
|
||||
}
|
||||
mut reader := StringReader.new(reader: two)
|
||||
assert reader.read_line()! == ''
|
||||
assert reader.read_line()! == '12345'
|
||||
assert reader.read_line()! == '67890'
|
||||
}
|
||||
|
||||
fn test_read_line_6() {
|
||||
mut two := TwoByteReader{
|
||||
data: '\r\n12345\r\n67890'
|
||||
}
|
||||
mut reader := StringReader.new(reader: two)
|
||||
assert reader.read_line()! == ''
|
||||
assert reader.read_line()! == '12345'
|
||||
assert reader.read_line()! == '67890'
|
||||
}
|
||||
|
||||
fn test_read_line_7() {
|
||||
mut two := TwoByteReader{
|
||||
data: '1234567890'
|
||||
}
|
||||
mut reader := StringReader.new(reader: two)
|
||||
assert reader.read_line(delim: `6`)! == '12345'
|
||||
assert reader.read_line(delim: `6`)! == '7890'
|
||||
assert reader.read_line() or { '777' } == '777'
|
||||
}
|
||||
|
||||
fn test_read_line_8() {
|
||||
mut two := TwoByteReader{
|
||||
data: '12345\r\n67890'
|
||||
}
|
||||
mut reader := StringReader.new(reader: two)
|
||||
assert reader.read_bytes(6)! == [u8(49), 50, 51, 52, 53, 13]
|
||||
reader.read_line()! // \n
|
||||
assert reader.read_line()! == '67890'
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
module util
|
||||
|
||||
import os
|
||||
import rand
|
||||
|
||||
const retries = 10000
|
||||
|
||||
@[params]
|
||||
pub struct TempFileOptions {
|
||||
pub:
|
||||
path string = os.temp_dir()
|
||||
pattern string
|
||||
}
|
||||
|
||||
// temp_file returns a 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.ensure_folder_is_writable(d) or {
|
||||
return error(@FN +
|
||||
' could not create temporary file in "${d}". Please ensure write permissions.')
|
||||
}
|
||||
d = d.trim_right(os.path_separator)
|
||||
prefix, suffix := prefix_and_suffix(tfo.pattern) or { return error(@FN + ' ${err.msg()}') }
|
||||
for retry := 0; retry < retries; retry++ {
|
||||
path := os.join_path(d, prefix + random_number() + suffix)
|
||||
mut mode := 'rw+'
|
||||
$if windows {
|
||||
mode = 'w+'
|
||||
}
|
||||
mut file := os.open_file(path, mode, 0o600) or { continue }
|
||||
if os.exists(path) && os.is_file(path) {
|
||||
return file, path
|
||||
}
|
||||
}
|
||||
return error(@FN +
|
||||
' could not create temporary file in "${d}". Retry limit (${retries}) exhausted. Please ensure write permissions.')
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct TempDirOptions {
|
||||
pub:
|
||||
path string = os.temp_dir()
|
||||
pattern string
|
||||
}
|
||||
|
||||
fn error_for_temporary_folder(fn_name string, d string) !string {
|
||||
return error('${fn_name} could not create temporary directory "${d}". Please ensure you have write permissions for it.')
|
||||
}
|
||||
|
||||
// temp_dir returns a uniquely named, writable, directory path.
|
||||
pub fn temp_dir(tdo TempDirOptions) !string {
|
||||
mut d := tdo.path
|
||||
if d == '' {
|
||||
d = os.temp_dir()
|
||||
}
|
||||
os.ensure_folder_is_writable(d) or { return error_for_temporary_folder(@FN, d) }
|
||||
d = d.trim_right(os.path_separator)
|
||||
prefix, suffix := prefix_and_suffix(tdo.pattern) or { return error(@FN + ' ${err.msg()}') }
|
||||
for retry := 0; retry < retries; retry++ {
|
||||
path := os.join_path(d, prefix + random_number() + suffix)
|
||||
os.mkdir_all(path) or { continue }
|
||||
if os.is_dir(path) && os.exists(path) {
|
||||
os.ensure_folder_is_writable(path) or { return error_for_temporary_folder(@FN, d) }
|
||||
return path
|
||||
}
|
||||
}
|
||||
return error('${@FN} could not create temporary directory "${d}". Retry limit (${retries}) exhausted.')
|
||||
}
|
||||
|
||||
// * Utility functions
|
||||
fn random_number() string {
|
||||
s := (1_000_000_000 + (u32(os.getpid()) + rand.u32n(1_000_000_000) or { 0 })).str()
|
||||
return s.substr(1, s.len)
|
||||
}
|
||||
|
||||
fn prefix_and_suffix(pattern string) !(string, string) {
|
||||
if pattern.contains(os.path_separator) {
|
||||
return error('pattern cannot contain path separators (${os.path_separator}).')
|
||||
}
|
||||
prefix, suffix := pattern.rsplit_once('*') or { pattern, '' }
|
||||
return prefix, suffix
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
import os
|
||||
import io.util
|
||||
|
||||
// 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.
|
||||
const tfolder = os.join_path(os.vtmp_dir(), 'io_util_tests')
|
||||
|
||||
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)
|
||||
os.ensure_folder_is_writable(path) or {
|
||||
assert false
|
||||
return
|
||||
}
|
||||
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)
|
||||
os.ensure_folder_is_writable(path) or {
|
||||
assert false
|
||||
return
|
||||
}
|
||||
assert path.contains(tfolder)
|
||||
filename = os.file_name(path)
|
||||
for c in filename {
|
||||
assert c.is_digit()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
module io
|
||||
|
||||
// Writer is the interface that wraps the `write` method, which
|
||||
// writes `buf.len` bytes to the underlying data stream.
|
||||
pub interface Writer {
|
||||
mut:
|
||||
write(buf []u8) !int
|
||||
}
|
||||
|
||||
// RandomWriter is the interface that wraps the `write_to` method,
|
||||
// which writes `buf.len` bytes to the underlying data stream at a random `pos`.
|
||||
pub interface RandomWriter {
|
||||
write_to(pos u64, buf []u8) !int
|
||||
}
|
||||
Reference in new issue
Block a user