mirror of
https://github.com/ncruces/go-sqlite3.git
synced 2026-01-12 05:59:14 +00:00
Refactor extensions.
This commit is contained in:
95
util/fsutil/mode.go
Normal file
95
util/fsutil/mode.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package fsutil
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
)
|
||||
|
||||
// ParseFileMode parses a file mode as returned by
|
||||
// [fs.FileMode.String].
|
||||
func ParseFileMode(str string) (fs.FileMode, error) {
|
||||
var mode fs.FileMode
|
||||
err := util.ErrorString("invalid mode: " + str)
|
||||
|
||||
if len(str) < 10 {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
for i, c := range []byte("dalTLDpSugct?") {
|
||||
if str[0] == c {
|
||||
if len(str) < 10 {
|
||||
return 0, err
|
||||
}
|
||||
mode |= 1 << uint(32-1-i)
|
||||
str = str[1:]
|
||||
}
|
||||
}
|
||||
|
||||
if mode == 0 {
|
||||
if str[0] != '-' {
|
||||
return 0, err
|
||||
}
|
||||
str = str[1:]
|
||||
}
|
||||
if len(str) != 9 {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
for i, c := range []byte("rwxrwxrwx") {
|
||||
if str[i] == c {
|
||||
mode |= 1 << uint(9-1-i)
|
||||
}
|
||||
if str[i] != '-' {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return mode, nil
|
||||
}
|
||||
|
||||
// FileModeFromUnix converts a POSIX mode_t to a file mode.
|
||||
func FileModeFromUnix(mode fs.FileMode) fs.FileMode {
|
||||
const (
|
||||
S_IFMT fs.FileMode = 0170000
|
||||
S_IFIFO fs.FileMode = 0010000
|
||||
S_IFCHR fs.FileMode = 0020000
|
||||
S_IFDIR fs.FileMode = 0040000
|
||||
S_IFBLK fs.FileMode = 0060000
|
||||
S_IFREG fs.FileMode = 0100000
|
||||
S_IFLNK fs.FileMode = 0120000
|
||||
S_IFSOCK fs.FileMode = 0140000
|
||||
)
|
||||
|
||||
switch mode & S_IFMT {
|
||||
case S_IFDIR:
|
||||
mode |= fs.ModeDir
|
||||
case S_IFLNK:
|
||||
mode |= fs.ModeSymlink
|
||||
case S_IFBLK:
|
||||
mode |= fs.ModeDevice
|
||||
case S_IFCHR:
|
||||
mode |= fs.ModeCharDevice | fs.ModeDevice
|
||||
case S_IFIFO:
|
||||
mode |= fs.ModeNamedPipe
|
||||
case S_IFSOCK:
|
||||
mode |= fs.ModeSocket
|
||||
case S_IFREG, 0:
|
||||
//
|
||||
default:
|
||||
mode |= fs.ModeIrregular
|
||||
}
|
||||
|
||||
return mode &^ S_IFMT
|
||||
}
|
||||
|
||||
// FileModeFromValue calls [FileModeFromUnix] for numeric values,
|
||||
// and [ParseFileMode] for textual values.
|
||||
func FileModeFromValue(val sqlite3.Value) fs.FileMode {
|
||||
if n := val.Int64(); n != 0 {
|
||||
return FileModeFromUnix(fs.FileMode(n))
|
||||
}
|
||||
mode, _ := ParseFileMode(val.Text())
|
||||
return mode
|
||||
}
|
||||
54
util/fsutil/mode_test.go
Normal file
54
util/fsutil/mode_test.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package fsutil
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFileModeFromUnix(t *testing.T) {
|
||||
tests := []struct {
|
||||
mode fs.FileMode
|
||||
want fs.FileMode
|
||||
}{
|
||||
{0010754, 0754 | fs.ModeNamedPipe},
|
||||
{0020754, 0754 | fs.ModeCharDevice | fs.ModeDevice},
|
||||
{0040754, 0754 | fs.ModeDir},
|
||||
{0060754, 0754 | fs.ModeDevice},
|
||||
{0100754, 0754},
|
||||
{0120754, 0754 | fs.ModeSymlink},
|
||||
{0140754, 0754 | fs.ModeSocket},
|
||||
{0170754, 0754 | fs.ModeIrregular},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.mode.String(), func(t *testing.T) {
|
||||
if got := FileModeFromUnix(tt.mode); got != tt.want {
|
||||
t.Errorf("fixMode() = %o, want %o", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func FuzzParseFileMode(f *testing.F) {
|
||||
f.Add("---------")
|
||||
f.Add("rwxrwxrwx")
|
||||
f.Add("----------")
|
||||
f.Add("-rwxrwxrwx")
|
||||
f.Add("b")
|
||||
f.Add("b---------")
|
||||
f.Add("drwxrwxrwx")
|
||||
f.Add("dalTLDpSugct?")
|
||||
f.Add("dalTLDpSugct?---------")
|
||||
f.Add("dalTLDpSugct?rwxrwxrwx")
|
||||
f.Add("dalTLDpSugct?----------")
|
||||
|
||||
f.Fuzz(func(t *testing.T, str string) {
|
||||
mode, err := ParseFileMode(str)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
got := mode.String()
|
||||
if got != str {
|
||||
t.Errorf("was %q, got %q (%o)", str, got, mode)
|
||||
}
|
||||
})
|
||||
}
|
||||
34
util/fsutil/osfs.go
Normal file
34
util/fsutil/osfs.go
Normal file
@@ -0,0 +1,34 @@
|
||||
// Package fsutil implements file system utility functions.
|
||||
package fsutil
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"os"
|
||||
)
|
||||
|
||||
// OSFS implements [fs.FS], [fs.StatFS], and [fs.ReadFileFS]
|
||||
// using package [os].
|
||||
//
|
||||
// This filesystem does not respect [fs.ValidPath] rules,
|
||||
// and fails [testing/fstest.TestFS]!
|
||||
//
|
||||
// Still, it can be a useful tool to unify implementations
|
||||
// that can access either the [os] filesystem or an [fs.FS].
|
||||
// It's OK to use this to open files, but you should avoid
|
||||
// opening directories, resolving paths, or walking the file system.
|
||||
type OSFS struct{}
|
||||
|
||||
// Open implements [fs.FS].
|
||||
func (OSFS) Open(name string) (fs.File, error) {
|
||||
return os.Open(name)
|
||||
}
|
||||
|
||||
// ReadFileFS implements [fs.StatFS].
|
||||
func (OSFS) Stat(name string) (fs.FileInfo, error) {
|
||||
return os.Stat(name)
|
||||
}
|
||||
|
||||
// ReadFile implements [fs.ReadFileFS].
|
||||
func (OSFS) ReadFile(name string) ([]byte, error) {
|
||||
return os.ReadFile(name)
|
||||
}
|
||||
60
util/ioutil/seek.go
Normal file
60
util/ioutil/seek.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package ioutil
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// SeekingReaderAt implements [io.ReaderAt]
|
||||
// through an underlying [io.ReadSeeker].
|
||||
type SeekingReaderAt struct {
|
||||
l sync.Mutex
|
||||
r io.ReadSeeker
|
||||
}
|
||||
|
||||
// NewSeekingReaderAt creates a new SeekingReaderAt.
|
||||
// The SeekingReaderAt takes ownership of r
|
||||
// and will modify its seek offset,
|
||||
// so callers should not use r after this call.
|
||||
func NewSeekingReaderAt(r io.ReadSeeker) *SeekingReaderAt {
|
||||
return &SeekingReaderAt{r: r}
|
||||
}
|
||||
|
||||
// ReadAt implements [io.ReaderAt].
|
||||
func (s *SeekingReaderAt) ReadAt(p []byte, off int64) (n int, _ error) {
|
||||
s.l.Lock()
|
||||
defer s.l.Unlock()
|
||||
|
||||
_, err := s.r.Seek(off, io.SeekStart)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
for len(p) > 0 {
|
||||
i, err := s.r.Read(p)
|
||||
p = p[i:]
|
||||
n += i
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Size implements [SizeReaderAt].
|
||||
func (s *SeekingReaderAt) Size() (int64, error) {
|
||||
s.l.Lock()
|
||||
defer s.l.Unlock()
|
||||
return s.r.Seek(0, io.SeekEnd)
|
||||
}
|
||||
|
||||
// ReadAt implements [io.Closer].
|
||||
func (s *SeekingReaderAt) Close() error {
|
||||
s.l.Lock()
|
||||
defer s.l.Unlock()
|
||||
if c, ok := s.r.(io.Closer); ok {
|
||||
s.r = nil
|
||||
return c.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
28
util/ioutil/seek_test.go
Normal file
28
util/ioutil/seek_test.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package ioutil
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewSeekingReaderAt(t *testing.T) {
|
||||
reader := NewSeekingReaderAt(strings.NewReader("abc"))
|
||||
defer reader.Close()
|
||||
|
||||
n, err := reader.Size()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n != 3 {
|
||||
t.Errorf("got %d", n)
|
||||
}
|
||||
|
||||
var buf [3]byte
|
||||
r, err := reader.ReadAt(buf[:], 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if r != 3 {
|
||||
t.Errorf("got %d", r)
|
||||
}
|
||||
}
|
||||
49
util/ioutil/size.go
Normal file
49
util/ioutil/size.go
Normal file
@@ -0,0 +1,49 @@
|
||||
// Package ioutil implements I/O utility functions.
|
||||
package ioutil
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/fs"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
)
|
||||
|
||||
// A SizeReaderAt is a ReaderAt with a Size method.
|
||||
// Use [NewSizeReaderAt] to adapt different Size interfaces.
|
||||
type SizeReaderAt interface {
|
||||
Size() (int64, error)
|
||||
io.ReaderAt
|
||||
}
|
||||
|
||||
// NewSizeReaderAt returns a SizeReaderAt given an io.ReaderAt
|
||||
// that implements one of:
|
||||
// - Size() (int64, error)
|
||||
// - Size() int64
|
||||
// - Len() int
|
||||
// - Stat() (fs.FileInfo, error)
|
||||
// - Seek(offset int64, whence int) (int64, error)
|
||||
func NewSizeReaderAt(r io.ReaderAt) SizeReaderAt {
|
||||
return sizer{r}
|
||||
}
|
||||
|
||||
type sizer struct{ io.ReaderAt }
|
||||
|
||||
func (s sizer) Size() (int64, error) {
|
||||
switch s := s.ReaderAt.(type) {
|
||||
case interface{ Size() (int64, error) }:
|
||||
return s.Size()
|
||||
case interface{ Size() int64 }:
|
||||
return s.Size(), nil
|
||||
case interface{ Len() int }:
|
||||
return int64(s.Len()), nil
|
||||
case interface{ Stat() (fs.FileInfo, error) }:
|
||||
fi, err := s.Stat()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return fi.Size(), nil
|
||||
case io.Seeker:
|
||||
return s.Seek(0, io.SeekEnd)
|
||||
}
|
||||
return 0, sqlite3.IOERR_SEEK
|
||||
}
|
||||
87
util/ioutil/size_test.go
Normal file
87
util/ioutil/size_test.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package ioutil
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewSizeReaderAt(t *testing.T) {
|
||||
f, err := os.Create(filepath.Join(t.TempDir(), "abc.txt"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
n, err := NewSizeReaderAt(f).Size()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n != 0 {
|
||||
t.Errorf("got %d", n)
|
||||
}
|
||||
|
||||
reader := strings.NewReader("abc")
|
||||
|
||||
n, err = NewSizeReaderAt(reader).Size()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n != 3 {
|
||||
t.Errorf("got %d", n)
|
||||
}
|
||||
|
||||
n, err = NewSizeReaderAt(readlener{reader, reader.Len()}).Size()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n != 3 {
|
||||
t.Errorf("got %d", n)
|
||||
}
|
||||
|
||||
n, err = NewSizeReaderAt(readsizer{reader, reader.Size()}).Size()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n != 3 {
|
||||
t.Errorf("got %d", n)
|
||||
}
|
||||
|
||||
n, err = NewSizeReaderAt(readseeker{reader, reader}).Size()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n != 3 {
|
||||
t.Errorf("got %d", n)
|
||||
}
|
||||
|
||||
_, err = NewSizeReaderAt(readerat{reader}).Size()
|
||||
if err == nil {
|
||||
t.Error("want error")
|
||||
}
|
||||
}
|
||||
|
||||
type readlener struct {
|
||||
io.ReaderAt
|
||||
len int
|
||||
}
|
||||
|
||||
func (l readlener) Len() int { return l.len }
|
||||
|
||||
type readsizer struct {
|
||||
io.ReaderAt
|
||||
size int64
|
||||
}
|
||||
|
||||
func (l readsizer) Size() (int64, error) { return l.size, nil }
|
||||
|
||||
type readseeker struct {
|
||||
io.ReaderAt
|
||||
io.Seeker
|
||||
}
|
||||
|
||||
type readerat struct {
|
||||
io.ReaderAt
|
||||
}
|
||||
34
util/vtabutil/arg.go
Normal file
34
util/vtabutil/arg.go
Normal file
@@ -0,0 +1,34 @@
|
||||
// Package ioutil implements virtual table utility functions.
|
||||
package vtabutil
|
||||
|
||||
import "strings"
|
||||
|
||||
// NamedArg splits an named arg into a key and value,
|
||||
// around an equals sign.
|
||||
// Spaces are trimmed around both key and value.
|
||||
func NamedArg(arg string) (key, val string) {
|
||||
key, val, _ = strings.Cut(arg, "=")
|
||||
key = strings.TrimSpace(key)
|
||||
val = strings.TrimSpace(val)
|
||||
return
|
||||
}
|
||||
|
||||
// Unquote unquotes a string.
|
||||
func Unquote(val string) string {
|
||||
if len(val) < 2 {
|
||||
return val
|
||||
}
|
||||
if val[0] != val[len(val)-1] {
|
||||
return val
|
||||
}
|
||||
var old, new string
|
||||
switch val[0] {
|
||||
default:
|
||||
return val
|
||||
case '"':
|
||||
old, new = `""`, `"`
|
||||
case '\'':
|
||||
old, new = `''`, `'`
|
||||
}
|
||||
return strings.ReplaceAll(val[1:len(val)-1], old, new)
|
||||
}
|
||||
Reference in New Issue
Block a user