diff --git a/README.md b/README.md index 3ab5ada..f5a645a 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ go test ./... . | service | info ---|---------|------ - ![Build Status](https://img.shields.io/badge/build-passing-brightgreen.svg?style=flat-square) | | go test ./... + ![OSX Build Status](https://simonwaldherr.de/icon/osx.png) | ![Build Status](https://img.shields.io/badge/build-passing-brightgreen.svg?style=flat-square) | | go test ./... [![Audit](https://github.com/SimonWaldherr/golibs/actions/workflows/audit.yml/badge.svg?branch=master&event=push)](https://github.com/SimonWaldherr/golibs/actions/workflows/audit.yml) | github.com | test via GitHub Workflow [![Travis CI](https://app.travis-ci.com/SimonWaldherr/golibs.svg?branch=master)](https://app.travis-ci.com/SimonWaldherr/golibs) | travis-ci.com | test at travis-ci [![Build Status](https://semaphoreci.com/api/v1/simonwaldherr/golibs/branches/master/shields_badge.svg)](https://semaphoreci.com/simonwaldherr/golibs) | semaphoreci.com | test at semaphore-ci @@ -135,7 +135,7 @@ var x int = as.Int("32") var x time.Time = as.Time("31.12.2014") ``` -### bitmask - [![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat-square)](https://godoc.org/github.com/SimonWaldherr/golibs/bitmask) [![Coverage Status](https://img.shields.io/badge/coverage-92%25-brightgreen.svg?style=flat-square)](https://coveralls.io/r/SimonWaldherr/golibs) [![Build Status](https://img.shields.io/badge/build-passing-brightgreen.svg?style=flat-square)](https://travis-ci.org/SimonWaldherr/golibs) +### bitmask - [![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat-square)](https://godoc.org/github.com/SimonWaldherr/golibs/bitmask) [![Coverage Status](https://img.shields.io/badge/coverage-0%25-red.svg?style=flat-square)](https://coveralls.io/r/SimonWaldherr/golibs) [![Build Status](https://img.shields.io/badge/build-passing-brightgreen.svg?style=flat-square)](https://travis-ci.org/SimonWaldherr/golibs) ```go import "simonwaldherr.de/go/golibs/bitmask" @@ -148,7 +148,7 @@ i := bitmask.New(0b11111111) i.Set(0, false) ``` -### cache - [![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat-square)](https://godoc.org/github.com/SimonWaldherr/golibs/cache) [![Coverage Status](https://img.shields.io/badge/coverage-97%25-brightgreen.svg?style=flat-square)](https://coveralls.io/r/SimonWaldherr/golibs) [![Build Status](https://img.shields.io/badge/build-passing-brightgreen.svg?style=flat-square)](https://travis-ci.org/SimonWaldherr/golibs) +### cache - [![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat-square)](https://godoc.org/github.com/SimonWaldherr/golibs/cache) [![Coverage Status](https://img.shields.io/badge/coverage-93%25-brightgreen.svg?style=flat-square)](https://coveralls.io/r/SimonWaldherr/golibs) [![Build Status](https://img.shields.io/badge/build-passing-brightgreen.svg?style=flat-square)](https://travis-ci.org/SimonWaldherr/golibs) ```go import "simonwaldherr.de/go/golibs/cache" @@ -178,7 +178,7 @@ str, _ := cachedfile.Read("filename.txt") and there will be no file access to disk. If you kill the App, wait 15 min or call ```cachedfile.Stop()``` the cached content will be exported to disk. -### channel - [![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat-square)](https://godoc.org/github.com/SimonWaldherr/golibs/channel) [![Coverage Status](https://img.shields.io/badge/coverage-100%25-brightgreen.svg?style=flat-square)](https://coveralls.io/r/SimonWaldherr/golibs) [![Build Status](https://img.shields.io/badge/build-passing-brightgreen.svg?style=flat-square)](https://travis-ci.org/SimonWaldherr/golibs) +### channel - [![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat-square)](https://godoc.org/github.com/SimonWaldherr/golibs/channel) [![Coverage Status](https://img.shields.io/badge/coverage-73%25-yellowgreen.svg?style=flat-square)](https://coveralls.io/r/SimonWaldherr/golibs) [![Build Status](https://img.shields.io/badge/build-passing-brightgreen.svg?style=flat-square)](https://travis-ci.org/SimonWaldherr/golibs) ```go import "simonwaldherr.de/go/golibs/channel" @@ -242,7 +242,7 @@ err := file.Each("..", true, func(filename, extension, filepath string, dir bool If you need the absolute path to a file, but only have a relative path, you can use ```file.GetAbsolutePath("~/path/to/file.txt")```. -### foreach - [![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat-square)](https://godoc.org/github.com/SimonWaldherr/golibs/foreach) [![Coverage Status](https://img.shields.io/badge/coverage-90%25-green.svg?style=flat-square)](https://coveralls.io/r/SimonWaldherr/golibs) [![Build Status](https://img.shields.io/badge/build-passing-brightgreen.svg?style=flat-square)](https://travis-ci.org/SimonWaldherr/golibs) +### foreach - [![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat-square)](https://godoc.org/github.com/SimonWaldherr/golibs/foreach) [![Coverage Status](https://img.shields.io/badge/coverage-42%25-red.svg?style=flat-square)](https://coveralls.io/r/SimonWaldherr/golibs) [![Build Status](https://img.shields.io/badge/build-passing-brightgreen.svg?style=flat-square)](https://travis-ci.org/SimonWaldherr/golibs) ```go import "simonwaldherr.de/go/golibs/foreach" @@ -314,7 +314,7 @@ func main() { ... ``` -### graphics - [![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat-square)](https://godoc.org/github.com/SimonWaldherr/golibs/graphics) [![Coverage Status](https://img.shields.io/badge/coverage-100%25-brightgreen.svg?style=flat-square)](https://coveralls.io/r/SimonWaldherr/golibs) [![Build Status](https://img.shields.io/badge/build-passing-brightgreen.svg?style=flat-square)](https://travis-ci.org/SimonWaldherr/golibs) +### graphics - [![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat-square)](https://godoc.org/github.com/SimonWaldherr/golibs/graphics) [![Coverage Status](https://img.shields.io/badge/coverage-94%25-brightgreen.svg?style=flat-square)](https://coveralls.io/r/SimonWaldherr/golibs) [![Build Status](https://img.shields.io/badge/build-passing-brightgreen.svg?style=flat-square)](https://travis-ci.org/SimonWaldherr/golibs) ```go import "simonwaldherr.de/go/golibs/graphics" @@ -346,7 +346,7 @@ fd.Close() ``` -### http - [![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat-square)](https://godoc.org/github.com/SimonWaldherr/golibs/http) [![Coverage Status](https://img.shields.io/badge/coverage-83%25-green.svg?style=flat-square)](https://coveralls.io/r/SimonWaldherr/golibs) [![Build Status](https://img.shields.io/badge/build-passing-brightgreen.svg?style=flat-square)](https://travis-ci.org/SimonWaldherr/golibs) +### http - [![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat-square)](https://godoc.org/github.com/SimonWaldherr/golibs/http) [![Coverage Status](https://img.shields.io/badge/coverage-8%25-red.svg?style=flat-square)](https://coveralls.io/r/SimonWaldherr/golibs) [![Build Status](https://img.shields.io/badge/build-passing-brightgreen.svg?style=flat-square)](https://travis-ci.org/SimonWaldherr/golibs) ```go import "simonwaldherr.de/go/golibs/http" @@ -418,7 +418,7 @@ err := ssl.Generate(options) ``` -### stack - [![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat-square)](https://godoc.org/github.com/SimonWaldherr/golibs/stack) [![Coverage Status](https://img.shields.io/badge/coverage-100%25-brightgreen.svg?style=flat-square)](https://coveralls.io/r/SimonWaldherr/golibs) [![Build Status](https://img.shields.io/badge/build-passing-brightgreen.svg?style=flat-square)](https://travis-ci.org/SimonWaldherr/golibs) +### stack - [![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat-square)](https://godoc.org/github.com/SimonWaldherr/golibs/stack) [![Coverage Status](https://img.shields.io/badge/coverage-97%25-brightgreen.svg?style=flat-square)](https://coveralls.io/r/SimonWaldherr/golibs) [![Build Status](https://img.shields.io/badge/build-passing-brightgreen.svg?style=flat-square)](https://travis-ci.org/SimonWaldherr/golibs) ```go import "simonwaldherr.de/go/golibs/stack" @@ -463,7 +463,7 @@ fmt.Printf("Harmonic: %v\n", xmath.Harmonic(f)) fmt.Printf("Geometric: %v\n", xmath.Geometric(f)) ``` -### xtime - [![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat-square)](https://godoc.org/github.com/SimonWaldherr/golibs/xtime) [![Coverage Status](https://img.shields.io/badge/coverage-100%25-brightgreen.svg?style=flat-square)](https://coveralls.io/r/SimonWaldherr/golibs) [![Build Status](https://img.shields.io/badge/build-passing-brightgreen.svg?style=flat-square)](https://travis-ci.org/SimonWaldherr/golibs) +### xtime - [![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat-square)](https://godoc.org/github.com/SimonWaldherr/golibs/xtime) [![Coverage Status](https://img.shields.io/badge/coverage-43%25-red.svg?style=flat-square)](https://coveralls.io/r/SimonWaldherr/golibs) [![Build Status](https://img.shields.io/badge/build-passing-brightgreen.svg?style=flat-square)](https://travis-ci.org/SimonWaldherr/golibs) ```go import "simonwaldherr.de/go/golibs/xtime" diff --git a/bitmask/bitmask.go b/bitmask/bitmask.go index 6b14b59..3c9182a 100644 --- a/bitmask/bitmask.go +++ b/bitmask/bitmask.go @@ -1,3 +1,4 @@ +// bitmasks are a way to store multiple boolean values in a single integer package bitmask import "fmt" @@ -6,6 +7,7 @@ type Bitmask struct { value int } +// New creates a new Bitmask func New(init int) *Bitmask { return &Bitmask{value: init} } @@ -21,6 +23,7 @@ func (b *Bitmask) clearBit(pos int) int { return b.value } +// Set sets the bit at position pos to val func (b *Bitmask) Set(pos int, val bool) int { if val == true { return b.setBit(pos) @@ -28,22 +31,27 @@ func (b *Bitmask) Set(pos int, val bool) int { return b.clearBit(pos) } +// hasBit checks if the bit at position pos is set func (b *Bitmask) hasBit(pos int) bool { return ((b.value & (1 << pos)) > 0) } +// Get returns the value of the bit at position pos func (b *Bitmask) Get(pos int) bool { return b.hasBit(pos) } +// Int returns the integer value of the bitmask func (b *Bitmask) Int() int { return b.value } +// String returns the string representation of the bitmask func (b *Bitmask) String() string { return fmt.Sprintf("%b", b.value) } +// Byte returns the byte representation of the bitmask func (b *Bitmask) Byte() []byte { return []byte{byte(b.value)} } diff --git a/channel/channel.go b/channel/channel.go index 688162f..2e0100c 100755 --- a/channel/channel.go +++ b/channel/channel.go @@ -1,3 +1,4 @@ +// channel is a simple communication hub for go routines package channel type Communication struct { @@ -7,6 +8,7 @@ type Communication struct { messages chan interface{} } +// Init creates a new Communication func Init() *Communication { var hub = &Communication{ receiver: make(map[chan interface{}]bool), @@ -38,21 +40,53 @@ func Init() *Communication { return hub } +// AddReceiver adds a new receiver to the hub func (hub *Communication) AddReceiver() chan interface{} { messageChannel := make(chan interface{}) hub.addReceiver <- messageChannel return messageChannel } +// CloseReceiver closes a receiver func (hub *Communication) CloseReceiver(ch chan interface{}) int { hub.rmReceiver <- ch return hub.CountReceiver() } +// CountReceiver returns the number of receivers func (hub *Communication) CountReceiver() int { return len(hub.receiver) } +// AddTransmitter adds a new transmitter to the hub func (hub *Communication) AddTransmitter() chan<- interface{} { return hub.messages } + +// Close closes the hub +func (hub *Communication) Close() { + close(hub.messages) +} + +// CloseTransmitter closes a transmitter +func (hub *Communication) CloseTransmitter(ch chan<- interface{}) { + close(ch) +} + +// CloseAll closes all receivers and transmitters +func (hub *Communication) CloseAll() { + for rec := range hub.receiver { + hub.CloseReceiver(rec) + } + hub.Close() +} + +// Send sends a message to all receivers +func (hub *Communication) Send(message interface{}) { + hub.messages <- message +} + +// SendTo sends a message to a specific receiver +func (hub *Communication) SendTo(message interface{}, receiver chan interface{}) { + receiver <- message +} diff --git a/coverage.log b/coverage.log new file mode 100644 index 00000000..5c78c7f --- /dev/null +++ b/coverage.log @@ -0,0 +1,51 @@ +COVERAGE TEST +ansi _/Users/simonwaldherr/git/golibs/ansi coverage: 100.0% of statements +ok _/Users/simonwaldherr/git/golibs/ansi 0.074s coverage: 100.0% of statements +arg _/Users/simonwaldherr/git/golibs/arg coverage: 72.0% of statements +ok _/Users/simonwaldherr/git/golibs/arg 0.083s coverage: 72.0% of statements +as _/Users/simonwaldherr/git/golibs/as coverage: 98.0% of statements +ok _/Users/simonwaldherr/git/golibs/as 0.103s coverage: 98.0% of statements +bitmask _/Users/simonwaldherr/git/golibs/bitmask coverage: 0.0% of statements +ok _/Users/simonwaldherr/git/golibs/bitmask 0.095s coverage: 0.0% of statements +cache _/Users/simonwaldherr/git/golibs/cache coverage: 93.1% of statements +ok _/Users/simonwaldherr/git/golibs/cache 2.190s coverage: 93.1% of statements +cachedfile _/Users/simonwaldherr/git/golibs/cachedfile coverage: 96.0% of statements +ok _/Users/simonwaldherr/git/golibs/cachedfile 0.107s coverage: 96.0% of statements +channel _/Users/simonwaldherr/git/golibs/channel coverage: 73.1% of statements +ok _/Users/simonwaldherr/git/golibs/channel 0.106s coverage: 73.1% of statements +csv _/Users/simonwaldherr/git/golibs/csv coverage: 100.0% of statements +ok _/Users/simonwaldherr/git/golibs/csv 0.086s coverage: 100.0% of statements +file _/Users/simonwaldherr/git/golibs/file coverage: 76.0% of statements +ok _/Users/simonwaldherr/git/golibs/file 0.141s coverage: 76.0% of statements +foreach _/Users/simonwaldherr/git/golibs/foreach coverage: 42.9% of statements +ok _/Users/simonwaldherr/git/golibs/foreach 0.113s coverage: 42.9% of statements +gopath _/Users/simonwaldherr/git/golibs/gopath coverage: 89.7% of statements +ok _/Users/simonwaldherr/git/golibs/gopath 0.091s coverage: 89.7% of statements +gps _/Users/simonwaldherr/git/golibs/gps coverage: 100.0% of statements +ok _/Users/simonwaldherr/git/golibs/gps 0.091s coverage: 100.0% of statements +graphics _/Users/simonwaldherr/git/golibs/graphics coverage: 94.2% of statements +ok _/Users/simonwaldherr/git/golibs/graphics 3.293s coverage: 94.2% of statements +http _/Users/simonwaldherr/git/golibs/http coverage: 8.3% of statements +ok _/Users/simonwaldherr/git/golibs/http 0.098s coverage: 8.3% of statements +log _/Users/simonwaldherr/git/golibs/log coverage: 100.0% of statements +ok _/Users/simonwaldherr/git/golibs/log 0.093s coverage: 100.0% of statements +node _/Users/simonwaldherr/git/golibs/node coverage: 92.3% of statements +ok _/Users/simonwaldherr/git/golibs/node 0.095s coverage: 92.3% of statements +re _/Users/simonwaldherr/git/golibs/re coverage: 100.0% of statements +ok _/Users/simonwaldherr/git/golibs/re 0.617s coverage: 100.0% of statements +regex _/Users/simonwaldherr/git/golibs/regex coverage: 88.9% of statements +ok _/Users/simonwaldherr/git/golibs/regex 0.095s coverage: 88.9% of statements +rss _/Users/simonwaldherr/git/golibs/rss coverage: 92.9% of statements +ok _/Users/simonwaldherr/git/golibs/rss 1.018s coverage: 92.9% of statements +ssl _/Users/simonwaldherr/git/golibs/ssl coverage: 87.2% of statements +ok _/Users/simonwaldherr/git/golibs/ssl 0.843s coverage: 87.2% of statements +stack _/Users/simonwaldherr/git/golibs/stack coverage: 97.6% of statements +ok _/Users/simonwaldherr/git/golibs/stack 0.095s coverage: 97.6% of statements +structs _/Users/simonwaldherr/git/golibs/structs coverage: 100.0% of statements +ok _/Users/simonwaldherr/git/golibs/structs 0.092s coverage: 100.0% of statements +xmath _/Users/simonwaldherr/git/golibs/xmath coverage: 100.0% of statements +ok _/Users/simonwaldherr/git/golibs/xmath 0.091s coverage: 100.0% of statements +xtime _/Users/simonwaldherr/git/golibs/xtime coverage: 43.8% of statements +ok _/Users/simonwaldherr/git/golibs/xtime 0.091s coverage: 43.8% of statements +yoloDB _/Users/simonwaldherr/git/golibs/yoloDB coverage: 35.6% of statements +ok _/Users/simonwaldherr/git/golibs/yoloDB 0.090s coverage: 35.6% of statements diff --git a/file/file.go b/file/file.go index 5fd9bd6..0a5d06a 100755 --- a/file/file.go +++ b/file/file.go @@ -9,8 +9,9 @@ import ( "os" "path/filepath" "runtime" - "simonwaldherr.de/go/golibs/gopath" "strings" + + "simonwaldherr.de/go/golibs/gopath" ) func contains(s []string, e string) bool { @@ -22,6 +23,7 @@ func contains(s []string, e string) bool { return false } +// Exists checks if a file exists func Exists(fn string) bool { if _, err := os.Stat(fn); err == nil { return true @@ -29,6 +31,7 @@ func Exists(fn string) bool { return false } +// IsDir checks if a file is a directory func IsDir(fn string) bool { file, err := os.Stat(fn) if err != nil { @@ -40,6 +43,7 @@ func IsDir(fn string) bool { return false } +// IsFile checks if a file is a regular file func IsFile(fn string) bool { file, err := os.Stat(fn) if err != nil { @@ -55,6 +59,7 @@ func isSymlink(fi os.FileInfo) bool { return fi.Mode()&os.ModeSymlink == os.ModeSymlink } +// IsSymlink checks if a file is a symlink func IsSymlink(fn string) bool { file, err := os.Lstat(fn) if err != nil { @@ -63,6 +68,7 @@ func IsSymlink(fn string) bool { return isSymlink(file) } +// Read reads a file and returns the content as a string func Read(fn string) (string, error) { var file *os.File var err error @@ -84,6 +90,7 @@ func Read(fn string) (string, error) { return s, nil } +// ReadUntil reads a file until a delimiter is found func ReadUntil(fn string, delim []string) (string, string, int, error) { file, err := os.Open(fn) @@ -111,6 +118,35 @@ func ReadUntil(fn string, delim []string) (string, string, int, error) { return buf, "", pos, nil } +// ReadBytes reads a file and returns the content as a string +func ReadBytes(fn string, delim []string) (string, string, int, error) { + file, err := os.Open(fn) + + defer file.Close() + + if err != nil { + return "", "", 0, err + } + + reader := bufio.NewReader(file) + scanner := bufio.NewScanner(reader) + + scanner.Split(bufio.ScanRunes) + pos := 0 + buf := "" + + for scanner.Scan() { + char := scanner.Text() + if contains(delim, char) { + return buf, char, pos, nil + } + buf += char + pos++ + } + return buf, "", pos, nil +} + +// ReadBlocks reads a file and returns the content as a string func ReadBlocks(fn string, delim []string, fnc func(string) (string, error)) (string, error) { file, err := os.Open(fn) @@ -142,6 +178,32 @@ func ReadBlocks(fn string, delim []string, fnc func(string) (string, error)) (st return ret, nil } +// ReadLines reads a file and returns the content as lines in a string slice +func ReadLines(fn string) ([]string, error) { + var file *os.File + var err error + + file, err = os.Open(fn) + + if err != nil { + return nil, err + } + defer file.Close() + + reader := bufio.NewReader(file) + scanner := bufio.NewScanner(reader) + + scanner.Split(bufio.ScanLines) + + var lines []string + + for scanner.Scan() { + lines = append(lines, scanner.Text()) + } + return lines, nil +} + +// Write writes a string to a file func Write(fn, str string, append bool) error { var file *os.File var err error @@ -167,6 +229,45 @@ func Write(fn, str string, append bool) error { return nil } +// WriteLines writes a string slice to a file +func WriteLines(fn string, lines []string, append bool) error { + var file *os.File + var err error + + if append { + file, err = os.OpenFile(fn, os.O_CREATE|os.O_APPEND|os.O_WRONLY, os.FileMode(0666)) + } else { + file, err = os.OpenFile(fn, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, os.FileMode(0666)) + } + + if err != nil { + return err + } + defer file.Close() + + for _, line := range lines { + if _, err = file.WriteString(line + ""); err != nil { + return err + } + } + + if err = file.Sync(); err != nil { + return err + } + return nil +} + +// Append appends a string to a file +func Append(fn, str string) error { + return Write(fn, str, true) +} + +// AppendLines appends a string slice to a file +func AppendLines(fn string, lines []string) error { + return WriteLines(fn, lines, true) +} + +// Size returns the size of a file func Size(fn string) (int64, error) { file, err := os.Open(fn) @@ -185,19 +286,22 @@ func Size(fn string) (int64, error) { return fi.Size(), nil } +// Clean removes a file func Clean(fn string) error { return Write(fn, "", false) } -func Rename(from, to string) error { - err := os.Rename(from, to) +// Remove removes a file +func Remove(fn string) error { + return os.Remove(fn) +} - if err != nil { - return err - } - return nil +// Rename renames a file +func Rename(from, to string) error { + return os.Rename(from, to) } +// Copy copies a file func Copy(from, to string) error { r, err := os.Open(from) if err != nil { @@ -218,15 +322,12 @@ func Copy(from, to string) error { return nil } +// Delete deletes a file func Delete(fn string) error { - err := os.Remove(fn) - - if err != nil { - return err - } - return nil + return os.Remove(fn) } +// ReadDir reads a directory and returns the content as a string slice func ReadDir(dn string) ([]string, error) { var flist []string dn, err := GetAbsolutePath(dn) @@ -243,6 +344,7 @@ func ReadDir(dn string) ([]string, error) { return flist, nil } +// Each iterates over a directory and calls a function for each file func Each(dirname string, recursive bool, fnc func(string, string, string, bool, os.FileInfo)) error { file, err := os.Open(dirname) @@ -274,6 +376,7 @@ func Each(dirname string, recursive bool, fnc func(string, string, string, bool, var HomeDir string +// SetHomeDir sets the home directory func SetHomeDir() string { if HomeDir == "#" || runtime.GOOS == "windows" { home := os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH") @@ -287,11 +390,13 @@ func SetHomeDir() string { return os.Getenv("HOME") } +// FakeHomeDir sets the home directory to a fake value func FakeHomeDir(dir string) string { HomeDir = dir return dir } +// GetHomeDir returns the home directory func GetHomeDir() string { if HomeDir == "" { return SetHomeDir() diff --git a/foreach/foreach.go b/foreach/foreach.go index 0747028..fb8d457 100755 --- a/foreach/foreach.go +++ b/foreach/foreach.go @@ -5,16 +5,33 @@ import ( "encoding/json" "os" "reflect" + "simonwaldherr.de/go/golibs/file" "simonwaldherr.de/go/golibs/node" "simonwaldherr.de/go/golibs/structs" - //"encoding/xml" ) +// Array runs a function for each element of an array +func Array(arr interface{}, handler func(int, interface{}, int)) { + v := reflect.ValueOf(arr) + t := reflect.TypeOf(arr) + + structs.ReflectHelper(v, t, 0, func(name string, vtype string, value interface{}, depth int) { + handler(depth, value, depth) + }) +} + +// Dir runs a function for each file in a directory +func Dir(dirname string, recursive bool, fnc func(string, string, string, bool, os.FileInfo)) error { + return file.Each(dirname, recursive, fnc) +} + +// File runs a function for each file in a directory func File(dirname string, recursive bool, fnc func(string, string, string, bool, os.FileInfo)) error { return file.Each(dirname, recursive, fnc) } +// JSON runs a function for each element of a JSON string func JSON(str string, handler func(*string, *int, *interface{}, int)) error { var j interface{} err := json.Unmarshal([]byte(str), &j) @@ -25,6 +42,7 @@ func JSON(str string, handler func(*string, *int, *interface{}, int)) error { return err } +// Struct runs a function for each element of a struct func Struct(sstruct interface{}, handler func(string, string, interface{}, int)) { v := reflect.ValueOf(sstruct) t := reflect.TypeOf(sstruct) @@ -32,13 +50,13 @@ func Struct(sstruct interface{}, handler func(string, string, interface{}, int)) structs.ReflectHelper(v, t, 0, handler) } -/* +// XML runs a function for each element of a XML string func XML(str string, handler func(*string, *int, *interface{}, int)) error { var x interface{} - err := xml.Unmarshal([]byte(str), &x) + err := json.Unmarshal([]byte(str), &x) if err == nil { - Node(&x, handler) + node.Node(&x, handler) return nil } return err -}*/ +} diff --git a/gopath/gopath_test.go b/gopath/gopath_test.go index 8a2b2f4..cc2eb6f 100755 --- a/gopath/gopath_test.go +++ b/gopath/gopath_test.go @@ -10,6 +10,7 @@ var archlist = map[string]bool{ "386": true, "amd64": true, "arm": true, + "arm64": true, } var oslist = map[string]bool{ diff --git a/gps/gps.go b/gps/gps.go new file mode 100644 index 00000000..edc5779 --- /dev/null +++ b/gps/gps.go @@ -0,0 +1,35 @@ +package gps + +import ( + "math" +) + +type Coordinate struct { + Latitude float64 + Longitude float64 +} + +// Distance calculates the distance between two coordinates using the Haversine formula. +func Distance(c1, c2 Coordinate) float64 { + const earthRadius = 6371 // in kilometers + + lat1 := toRadians(c1.Latitude) + lat2 := toRadians(c2.Latitude) + + deltaLat := toRadians(c2.Latitude - c1.Latitude) + deltaLon := toRadians(c2.Longitude - c1.Longitude) + + a := math.Sin(deltaLat/2)*math.Sin(deltaLat/2) + + math.Cos(lat1)*math.Cos(lat2)* + math.Sin(deltaLon/2)*math.Sin(deltaLon/2) + c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a)) + + distance := earthRadius * c + + return distance +} + +// toRadians converts a degree value to radians. +func toRadians(degrees float64) float64 { + return degrees * (math.Pi / 180) +} diff --git a/gps/gps_test.go b/gps/gps_test.go new file mode 100644 index 00000000..109db1f --- /dev/null +++ b/gps/gps_test.go @@ -0,0 +1,38 @@ +package gps + +import ( + "math" + "testing" +) + +func TestDistance(t *testing.T) { + tests := []struct { + c1 Coordinate + c2 Coordinate + expected float64 + }{ + { + Coordinate{Latitude: 51.5074, Longitude: -0.1278}, // London, UK + Coordinate{Latitude: 40.7128, Longitude: -74.0060}, // New York City, USA + 5570.22, + }, + { + Coordinate{Latitude: 37.7749, Longitude: -122.4194}, // San Francisco, USA + Coordinate{Latitude: 48.8566, Longitude: 2.3522}, // Paris, France + 8953.39, + }, + { + Coordinate{Latitude: 40.4168, Longitude: -3.7038}, // Madrid, Spain + Coordinate{Latitude: 31.2304, Longitude: 121.4737}, // Shanghai, China + 10255.48, + }, + } + + for _, test := range tests { + distance := Distance(test.c1, test.c2) + + if math.Abs(distance-test.expected) > 0.1 { + t.Errorf("Distance between %v and %v was %.2f km, expected %.2f km", test.c1, test.c2, distance, test.expected) + } + } +} diff --git a/graphics/example_test.go b/graphics/example_test.go new file mode 100644 index 00000000..7ddb4c8 --- /dev/null +++ b/graphics/example_test.go @@ -0,0 +1,32 @@ +package graphics_test + +import ( + "simonwaldherr.de/go/golibs/graphics" + //"../graphics" +) + +func Example() { + img := graphics.LoadImage("img.jpg") + img_gray := graphics.Grayscale(img) + + img_rnn, _ := graphics.NearestNeighbor(img, 80, 24) + + img_each, _ := graphics.EachPixelOfImage(img, func(r, g, b, a uint8) (uint8, uint8, uint8, uint8) { + return g, b, r, a + }) + + img_edge := graphics.Edgedetect(img) + + img_invert, _ := graphics.Invert(img) + + img_threshold := graphics.Threshold(img, 128) + + graphics.SaveImage(img_gray, "img_gray.png") + graphics.SaveImage(img_rnn, "img_rnn.png") + graphics.SaveImage(img_each, "img_each.png") + graphics.SaveImage(img_edge, "img_edge.png") + graphics.SaveImage(img_invert, "img_invert.png") + graphics.SaveImage(img_threshold, "img_threshold.png") + + // Output: +} diff --git a/graphics/graphics.go b/graphics/graphics.go index 5d1b94d..dccf4cb 100755 --- a/graphics/graphics.go +++ b/graphics/graphics.go @@ -6,8 +6,10 @@ import ( "image/color" "image/draw" _ "image/gif" + "image/jpeg" _ "image/jpeg" _ "image/png" + "log" "math" "os" ) @@ -15,6 +17,22 @@ import ( var edfX = [3][3]int8{{-1, 0, 1}, {-2, 0, 2}, {-1, 0, 1}} var edfY = [3][3]int8{{-1, -2, -1}, {0, 0, 0}, {1, 2, 1}} +// Invert inverts an image +func Invert(img image.Image) (image.Image, error) { + b := img.Bounds() + newImg := image.NewRGBA(b) + + for y := b.Min.Y; y < b.Max.Y; y++ { + for x := b.Min.X; x < b.Max.X; x++ { + pixel := img.At(x, y) + r, g, b, a := pixel.RGBA() + newImg.Set(x, y, color.RGBA{uint8(255 - r), uint8(255 - g), uint8(255 - b), uint8(a)}) + } + } + return newImg, nil +} + +// EachPixel applies a function to each pixel of an image func EachPixel(file *os.File, f func(uint8, uint8, uint8, uint8) (uint8, uint8, uint8, uint8)) (image.Image, error) { src, _, err := image.Decode(file) @@ -22,6 +40,11 @@ func EachPixel(file *os.File, f func(uint8, uint8, uint8, uint8) (uint8, uint8, return nil, err } + return EachPixelOfImage(src, f) +} + +// EachPixelOfImage applies a function to each pixel of an image +func EachPixelOfImage(src image.Image, f func(uint8, uint8, uint8, uint8) (uint8, uint8, uint8, uint8)) (image.Image, error) { bsrc := src.Bounds() img := image.NewRGBA(bsrc) draw.Draw(img, bsrc, src, bsrc.Min, draw.Src) @@ -40,6 +63,7 @@ func EachPixel(file *os.File, f func(uint8, uint8, uint8, uint8) (uint8, uint8, return img, nil } +// ResizeNearestNeighbor resizes an image using the nearest neighbor algorithm func ResizeNearestNeighbor(file *os.File, newWidth, newHeight int) (*image.NRGBA, error) { img, _, err := image.Decode(file) if err != nil { @@ -48,6 +72,7 @@ func ResizeNearestNeighbor(file *os.File, newWidth, newHeight int) (*image.NRGBA return NearestNeighbor(img, newWidth, newHeight) } +// NearestNeighbor resizes an image using the nearest neighbor algorithm func NearestNeighbor(img image.Image, newWidth, newHeight int) (*image.NRGBA, error) { w := img.Bounds().Max.X h := img.Bounds().Max.Y @@ -66,6 +91,7 @@ func NearestNeighbor(img image.Image, newWidth, newHeight int) (*image.NRGBA, er return newimg, nil } +// Grayscale converts an image to grayscale func Grayscale(img image.Image) image.Image { min := img.Bounds().Min max := img.Bounds().Max @@ -80,6 +106,7 @@ func Grayscale(img image.Image) image.Image { return grayImg } +// Edgedetect detects edges in an image func Edgedetect(img image.Image) image.Image { img = Grayscale(img) max := img.Bounds().Max @@ -101,6 +128,7 @@ func Edgedetect(img image.Image) image.Image { return edImg } +// detectEdgeAroundPixel detects the edge around a pixel func detectEdgeAroundPixel(img image.Image, x int, y int) (float64, float64) { var pX, pY int curX := x - 1 @@ -119,3 +147,59 @@ func detectEdgeAroundPixel(img image.Image, x int, y int) (float64, float64) { return math.Abs(float64(pX)), math.Abs(float64(pY)) } + +// Threshold thresholds an image +func Threshold(img image.Image, threshold uint8) image.Image { + img = Grayscale(img) + max := img.Bounds().Max + min := img.Bounds().Min + + thImg := image.NewGray(image.Rect(max.X, max.Y, min.X, min.Y)) + + width := max.X + height := max.Y + var pixel color.Color + for x := 0; x < width; x++ { + for y := 0; y < height; y++ { + p := img.At(x, y) + if r, _, _, _ := p.RGBA(); uint8(r) > threshold { + pixel = color.Gray{Y: 255} + } else { + pixel = color.Gray{Y: 0} + } + thImg.SetGray(x, y, pixel.(color.Gray)) + } + } + return thImg +} + +// GrayAt returns the grayscale value of a pixel +func GrayAt(img image.Image, x, y int) uint8 { + p := img.At(x, y) + r, _, _, _ := p.RGBA() + return uint8(r) +} + +// SaveImage saves an image to a file +func SaveImage(img image.Image, filename string) { + f, err := os.Create(filename) + if err != nil { + log.Fatal(err) + } + defer f.Close() + jpeg.Encode(f, img, nil) +} + +// LoadImage loads an image from a file +func LoadImage(filename string) image.Image { + f, err := os.Open(filename) + if err != nil { + log.Fatal(err) + } + defer f.Close() + img, _, err := image.Decode(f) + if err != nil { + log.Fatal(err) + } + return img +} diff --git a/graphics/img.jpg b/graphics/img.jpg new file mode 100644 index 00000000..c1f32c8 Binary files /dev/null and b/graphics/img.jpg differ diff --git a/log/log.go b/log/log.go index 5ef234d..4882aba 100755 --- a/log/log.go +++ b/log/log.go @@ -1,3 +1,4 @@ +// log package is a wrapper of log package in standard library. package log import ( diff --git a/pre-commit b/pre-commit index 456faca..fcb79b9 100755 --- a/pre-commit +++ b/pre-commit @@ -1,7 +1,7 @@ #!/bin/sh export GO15VENDOREXPERIMENT=1 -declare -a libs=(ansi arg as bitmask cache cachedfile channel csv file foreach gopath graphics http log re regex rss ssl stack structs xmath xtime) +declare -a libs=(ansi arg as bitmask cache cachedfile channel csv file foreach gopath gps graphics http log node re regex rss ssl stack structs xmath xtime yoloDB) echo "COVERAGE TEST" > coverage.log @@ -52,7 +52,13 @@ do fi if [ "x$CHEADER" != "x" ]; then - rpl "$CHEADER" "$NHEADER" README.md + #//replace "$CHEADER" "$NHEADER" README.md + ./go_replace_tool README.md README_new.md "$CHEADER" "$NHEADER" + rm README.md + mv README_new.md README.md + + #rpl "$CHEADER" "$NHEADER" README.md + fi done @@ -73,7 +79,10 @@ kill $(lsof -t -i:6060) mv localhost:6060 godoc -rpl "$(cat README.md | grep " go test ./... ")" "$BADGEBUILD" README.md +#rpl "$(cat README.md | grep " go test ./... ")" "$BADGEBUILD" README.md +./go_replace_tool README.md README_new.md "$(cat README.md | grep " go test ./... ")" "$BADGEBUILD" +rm README.md +mv README_new.md README.md cp ./file/test.txt ./cachedfile/test.txt cp ./file/test.txt ./examples/test.txt diff --git a/re/re.go b/re/re.go index 5c5b47f..007a549 100755 --- a/re/re.go +++ b/re/re.go @@ -9,6 +9,7 @@ import ( var MaxAttempts = 10 +// Try tries to execute a function several times func Try(retrys int, fn func() (err error)) error { defer func() { if r := recover(); r != nil { @@ -30,6 +31,7 @@ func Try(retrys int, fn func() (err error)) error { return err } +// Do executes a function as long as the stop channel is not closed func Do(wait time.Duration, fn func(chan<- interface{})) (<-chan interface{}, chan<- bool) { stop := make(chan bool, 1) ret := make(chan interface{}) diff --git a/regex/regex.go b/regex/regex.go index 00dfa3b..9fc6727 100755 --- a/regex/regex.go +++ b/regex/regex.go @@ -8,11 +8,13 @@ import ( var regexArray = make(map[string]*regexp.Regexp) +// CheckRegex checks if the regex is valid func CheckRegex(regex string) error { _, err := regexp.Compile(regex) return err } +// CacheRegex caches the compiled regex func CacheRegex(regex string) error { var err error if _, ok := regexArray[regex]; ok { @@ -24,6 +26,7 @@ func CacheRegex(regex string) error { return err } +// MatchString reports whether the string src contains any match of the regular expression. func MatchString(src, regex string) (bool, error) { var err error if re, ok := regexArray[regex]; ok { @@ -97,10 +100,12 @@ func FindAllStringSubmatch(src, regex string) ([][]string, error) { return [][]string{}, err } +// Count returns the number of cached regex func Count() int { return len(regexArray) } +// Cleanup cleans the cache func Cleanup() { regexArray = make(map[string]*regexp.Regexp) } diff --git a/ssh/ssh.go b/ssh/ssh.go index a965fc1..0032314 100644 --- a/ssh/ssh.go +++ b/ssh/ssh.go @@ -8,6 +8,7 @@ import ( "golang.org/x/crypto/ssh" ) +// SecureShell connects to a remote host via SSH and returns a session func SecureShell(user string, host string, port string, keyfile string) *ssh.Session { var client *ssh.Client var session *ssh.Session @@ -28,6 +29,7 @@ func SecureShell(user string, host string, port string, keyfile string) *ssh.Ses return session } +// connectToHost connects to a remote host via SSH and returns a session func connectToHost(user, host string) (*ssh.Client, *ssh.Session, error) { var pass string fmt.Print("SSH-Password: ") @@ -53,6 +55,7 @@ func connectToHost(user, host string) (*ssh.Client, *ssh.Session, error) { return client, session, nil } +// connectToHostWithPublickey connects to a remote host via SSH and returns a session func connectToHostWithPublickey(user, host, publickeyfile string) (*ssh.Client, *ssh.Session, error) { key, err := ioutil.ReadFile(publickeyfile) if err != nil { diff --git a/stack/stack.go b/stack/stack.go index ff8030e..3cc680a 100755 --- a/stack/stack.go +++ b/stack/stack.go @@ -32,12 +32,14 @@ func Lifo() *Stack { } } +// FiFo returns a pointer to a new stack. func Fifo() *Stack { return &Stack{ stype: FiFo, } } +// Unset resets the stack func (s *Stack) Unset() { *s = Stack{ nodes: []interface{}{}, @@ -46,6 +48,7 @@ func (s *Stack) Unset() { } } +// ToFifo converts a LIFO stack to a FIFO stack func (s *Stack) ToFifo() *Stack { var x *Stack array := Fifo() @@ -71,6 +74,7 @@ func (s *Stack) ToFifo() *Stack { return array } +// ToLifo converts a FIFO stack to a LIFO stack func (s *Stack) ToLifo() *Stack { var x *Stack array := Lifo() @@ -96,6 +100,7 @@ func (s *Stack) ToLifo() *Stack { return array } +// Val returns the stack as a slice of interfaces func (s *Stack) Val() []interface{} { var a *Stack var r []interface{} @@ -126,6 +131,7 @@ func (s *Stack) Push(n interface{}) { } } +// Add adds a value to the Stack func (s *Stack) Add(n interface{}) { s.Push(n) } @@ -152,6 +158,7 @@ func (s *Stack) Pop() interface{} { return "" } +// Get returns the last added value and decrease the position counter. func (s *Stack) Get() interface{} { return s.Pop() } diff --git a/structs/structs.go b/structs/structs.go index e3db4ab..898e688 100644 --- a/structs/structs.go +++ b/structs/structs.go @@ -4,6 +4,7 @@ import ( "reflect" ) +// Reflect returns a map of the struct func Reflect(sstruct interface{}) map[string]interface{} { attrs := make(map[string]interface{}) v := reflect.ValueOf(sstruct) @@ -16,6 +17,7 @@ func Reflect(sstruct interface{}) map[string]interface{} { return attrs } +// ReflectHelper is a helper function for Reflect func ReflectHelper(v reflect.Value, t reflect.Type, depth int, handler func(string, string, interface{}, int)) map[string]interface{} { attrs := make(map[string]interface{}) for i := 0; i < v.NumField(); i++ { diff --git a/xmath/math.go b/xmath/math.go index 61157f6..e68f21d 100755 --- a/xmath/math.go +++ b/xmath/math.go @@ -5,8 +5,9 @@ package xmath import ( "math" "reflect" - "simonwaldherr.de/go/golibs/as" "sort" + + "simonwaldherr.de/go/golibs/as" ) // Sqrt calculates the square root of n. @@ -155,6 +156,7 @@ const ( Default ) +// Mean returns the mean from an interface of Values as float64. func Mean(val interface{}, t Meantype) float64 { switch t { case ArithmeticMean: diff --git a/xtime/time.go b/xtime/time.go index c312c5c..991f960 100755 --- a/xtime/time.go +++ b/xtime/time.go @@ -1,9 +1,11 @@ +// xt is a collection of time-related functions and types package xtime import ( "time" ) +// conv is a map of strftime format specifiers var conv = map[rune]string{ 'a': "Mon", 'A': "Monday", @@ -59,23 +61,33 @@ func FmtNow(format string) string { return StrfTime(format, t) } +// TimeRange is a simple struct to represent a time range type TimeRange struct { Start time.Time End time.Time } +// Duration returns the duration of the time range +func (tr TimeRange) Duration() time.Duration { + return tr.End.Sub(tr.Start) +} + +// Overlaps returns true if the time range overlaps with another time range func (tr TimeRange) Overlaps(other TimeRange) bool { return tr.Start.Before(other.End) && tr.End.After(other.Start) } +// Contains returns true if the time range contains another time range func (tr TimeRange) Contains(other TimeRange) bool { return tr.Start.Before(other.Start) && tr.End.After(other.End) } +// Within returns true if the time range is within another time range func (tr TimeRange) Within(other TimeRange) bool { return other.Contains(tr) } +// And returns the intersection of two time ranges func And(tr1, tr2 TimeRange) TimeRange { if tr1.Overlaps(tr2) { return TimeRange{ @@ -86,6 +98,7 @@ func And(tr1, tr2 TimeRange) TimeRange { return TimeRange{} } +// Or returns the union of two time ranges func Or(tr1, tr2 TimeRange) TimeRange { if tr1.Overlaps(tr2) { return TimeRange{ @@ -99,6 +112,7 @@ func Or(tr1, tr2 TimeRange) TimeRange { } } +// Not returns the inverse of a time range func Not(tr TimeRange, fullRange TimeRange) TimeRange { return TimeRange{ Start: fullRange.Start, @@ -106,6 +120,7 @@ func Not(tr TimeRange, fullRange TimeRange) TimeRange { }.Subtract(tr) } +// Subtract returns the difference of two time ranges func (tr TimeRange) Subtract(other TimeRange) TimeRange { if !tr.Overlaps(other) { return tr diff --git a/yoloDB/example_test.go b/yoloDB/example_test.go new file mode 100644 index 00000000..fd468c7 --- /dev/null +++ b/yoloDB/example_test.go @@ -0,0 +1,49 @@ +package yolodb_test + +import ( + "fmt" + + yoloDB "simonwaldherr.de/go/golibs/yoloDB" +) + +func Example() { + // Erstellen einer neuen Datenbank + db := yoloDB.NewDatabase() + + // Erstellen einer neuen Tabelle + columns := []*yoloDB.Column{ + {Name: "id", Typ: "int"}, + {Name: "name", Typ: "string"}, + {Name: "age", Typ: "int"}, + } + err := db.CreateTable("users", columns) + if err != nil { + panic(err) + } + + // Einfügen von Datensätzen in die Tabelle + data := map[string]interface{}{"id": 1, "name": "John Doe", "age": 30} + err = db.Insert("users", data) + if err != nil { + panic(err) + } + data = map[string]interface{}{"id": 2, "name": "Jane Doe", "age": 32} + err = db.Insert("users", data) + if err != nil { + panic(err) + } + + // Auswählen von Datensätzen aus der Tabelle + results, err := db.Select("users", []string{"name"}, []string{"age > 30"}) + if err != nil { + panic(err) + } + fmt.Println("Results:") + for _, record := range results { + fmt.Println(record) + } + + // Output: + // Results: + // map[name:Jane Doe] +} diff --git a/yoloDB/yolodb.go b/yoloDB/yolodb.go new file mode 100644 index 00000000..5b1a3b0 --- /dev/null +++ b/yoloDB/yolodb.go @@ -0,0 +1,484 @@ +// yolodb is a simple in-memory database that supports basic CRUD operations. +package yolodb + +import ( + "errors" + "fmt" + "reflect" + "strconv" + "strings" +) + +// Database is the main struct of the database. +type Database struct { + tables map[string]*Table +} + +// Table is a struct that holds the table information. +type Table struct { + name string + columns []*Column + records []*Record + nextID int + indexMap map[string]map[interface{}][]int +} + +// Column is a struct that holds the column information. +type Column struct { + Name string + Typ string +} + +// Record is a struct that holds the record information. +type Record struct { + id int + data []interface{} +} + +// NewDatabase returns a pointer to a new Database. +func NewDatabase() *Database { + return &Database{ + tables: make(map[string]*Table), + } +} + +// CreateTable creates a new table in the database. +func (db *Database) CreateTable(name string, columns []*Column) error { + if _, ok := db.tables[name]; ok { + return fmt.Errorf("Table %s already exists", name) + } + + for _, col := range columns { + if col.Typ != "int" && col.Typ != "string" { + return fmt.Errorf("Invalid column type %s", col.Typ) + } + } + + db.tables[name] = &Table{ + name: name, + columns: columns, + records: []*Record{}, + nextID: 1, + indexMap: make(map[string]map[interface{}][]int), + } + + return nil +} + +// Insert inserts a new record into the table. +func (db *Database) Insert(name string, data map[string]interface{}) error { + table, ok := db.tables[name] + if !ok { + return fmt.Errorf("Table %s not found", name) + } + + if len(data) != len(table.columns) { + return errors.New("Data does not match table schema") + } + + recordData := make([]interface{}, len(table.columns)) + for i, col := range table.columns { + if val, ok := data[col.Name]; ok { + if reflect.TypeOf(val).String() != col.Typ { + return fmt.Errorf("Invalid type for column %s", col.Name) + } + recordData[i] = val + } else { + return fmt.Errorf("Missing value for column %s", col.Name) + } + } + + record := &Record{ + id: table.nextID, + data: recordData, + } + + for i, col := range table.columns { + if _, ok := table.indexMap[col.Name]; !ok { + table.indexMap[col.Name] = make(map[interface{}][]int) + } + table.indexMap[col.Name][recordData[i]] = append(table.indexMap[col.Name][recordData[i]], table.nextID) + } + + table.records = append(table.records, record) + table.nextID++ + + return nil +} + +// Select selects records from the table. +func (db *Database) Select(name string, columns []string, whereClauses []string) ([]map[string]interface{}, error) { + table, ok := db.tables[name] + if !ok { + return nil, fmt.Errorf("Table %s not found", name) + } + + var results []map[string]interface{} + + columnSet := make(map[string]bool) + for _, col := range columns { + columnSet[col] = true + } + + var indexMap map[string]map[interface{}][]int + if len(whereClauses) > 0 { + indexMap = make(map[string]map[interface{}][]int) + for _, clause := range whereClauses { + parts := strings.Split(clause, " ") + if len(parts) != 3 { + return nil, fmt.Errorf("Invalid where clause: %s", clause) + } + + colName := parts[0] + op := parts[1] + valStr := parts[2] + val, err := db.parseValue(valStr, table.getColumnType(colName)) + if err != nil { + return nil, err + } + + if _, ok := indexMap[colName]; !ok { + if _, ok := table.indexMap[colName]; !ok { + return nil, fmt.Errorf("Column %s is not indexed", colName) + } + indexMap[colName] = table.indexMap[colName] + } + + var ids []int + switch op { + case "=": + if vals, ok := indexMap[colName][val]; ok { + ids = vals + } + case ">": + for key, vals := range indexMap[colName] { + if key.(int) > val.(int) { + ids = append(ids, vals...) + } + } + case "<": + for key, vals := range indexMap[colName] { + if key.(int) < val.(int) { + ids = append(ids, vals...) + } + } + } + + newIndexMap := make(map[interface{}][]int) + for _, id := range ids { + record := table.getRecordByID(id) + if _, ok := indexMap[colName][record.data[table.getColumnIndex(colName)]]; ok { + newIndexMap[record.data[table.getColumnIndex(colName)]] = append(newIndexMap[record.data[table.getColumnIndex(colName)]], id) + } + } + + indexMap[colName] = newIndexMap + } + } else { + indexMap = table.indexMap + } + + for _, record := range table.records { + if len(whereClauses) > 0 { + match := true + for _, clause := range whereClauses { + parts := strings.Split(clause, " ") + colName := parts[0] + op := parts[1] + valStr := parts[2] + val, err := db.parseValue(valStr, table.getColumnType(colName)) + if err != nil { + return nil, err + } + colIndex := table.getColumnIndex(colName) + if op == "=" && record.data[colIndex] != val { + match = false + break + } + if op == ">" && record.data[colIndex].(int) <= val.(int) { + match = false + break + } + if op == "<" && record.data[colIndex].(int) >= val.(int) { + match = false + break + } + } + if !match { + continue + } + } + + row := make(map[string]interface{}) + for i, col := range table.columns { + if columnSet[col.Name] { + row[col.Name] = record.data[i] + } + } + results = append(results, row) + } + + return results, nil +} + +// Update updates records in the table. +func (db *Database) Update(name string, data map[string]interface{}, whereClauses []string) error { + table, ok := db.tables[name] + if !ok { + return fmt.Errorf("Table %s not found", name) + } + + if len(data) != len(table.columns) { + return errors.New("Data does not match table schema") + } + + indexMap := make(map[string]map[interface{}][]int) + for _, clause := range whereClauses { + parts := strings.Split(clause, " ") + if len(parts) != 3 { + return fmt.Errorf("Invalid where clause: %s", clause) + } + + colName := parts[0] + op := parts[1] + valStr := parts[2] + val, err := db.parseValue(valStr, table.getColumnType(colName)) + if err != nil { + return err + } + + if _, ok := indexMap[colName]; !ok { + if _, ok := table.indexMap[colName]; !ok { + return fmt.Errorf("Column %s is not indexed", colName) + } + indexMap[colName] = table.indexMap[colName] + } + + var ids []int + switch op { + case "=": + if vals, ok := indexMap[colName][val]; ok { + ids = vals + } + case ">": + for key, vals := range indexMap[colName] { + if key.(int) > val.(int) { + ids = append(ids, vals...) + } + } + case "<": + for key, vals := range indexMap[colName] { + if key.(int) < val.(int) { + ids = append(ids, vals...) + } + } + } + + newIndexMap := make(map[interface{}][]int) + for _, id := range ids { + record := table.getRecordByID(id) + if _, ok := indexMap[colName][record.data[table.getColumnIndex(colName)]]; ok { + newIndexMap[record.data[table.getColumnIndex(colName)]] = append(newIndexMap[record.data[table.getColumnIndex(colName)]], id) + } + } + + indexMap[colName] = newIndexMap + } + + for _, record := range table.records { + if len(whereClauses) > 0 { + match := true + for _, clause := range whereClauses { + parts := strings.Split(clause, " ") + colName := parts[0] + op := parts[1] + valStr := parts[2] + val, err := db.parseValue(valStr, table.getColumnType(colName)) + if err != nil { + return err + } + colIndex := table.getColumnIndex(colName) + if op == "=" && record.data[colIndex] != val { + match = false + break + } + if op == ">" && record.data[colIndex].(int) <= val.(int) { + match = false + break + } + if op == "<" && record.data[colIndex].(int) >= val.(int) { + match = false + break + } + + } + if !match { + continue + } + } + + for colName, val := range data { + //colIndex := table.getColumnIndex(colName.(string)) + colIndex := table.getColumnIndex(colName) + if reflect.TypeOf(val).String() != table.columns[colIndex].Typ { + return fmt.Errorf("Invalid type for column %s", colName) + } + record.data[colIndex] = val + if _, ok := table.indexMap[colName]; ok { + if _, ok := table.indexMap[colName][record.data[colIndex]]; !ok { + table.indexMap[colName][record.data[colIndex]] = []int{} + } + table.indexMap[colName][record.data[colIndex]] = append(table.indexMap[colName][record.data[colIndex]], record.id) + } + } + } + + return nil +} + +// Delete deletes records from the table. +func (db *Database) Delete(name string, whereClauses []string) error { + table, ok := db.tables[name] + if !ok { + return fmt.Errorf("Table %s not found", name) + } + + indexMap := make(map[string]map[interface{}][]int) + for _, clause := range whereClauses { + parts := strings.Split(clause, " ") + if len(parts) != 3 { + return fmt.Errorf("Invalid where clause: %s", clause) + } + + colName := parts[0] + op := parts[1] + valStr := parts[2] + val, err := db.parseValue(valStr, table.getColumnType(colName)) + if err != nil { + return err + } + + if _, ok := indexMap[colName]; !ok { + if _, ok := table.indexMap[colName]; !ok { + return fmt.Errorf("Column %s is not indexed", colName) + } + indexMap[colName] = table.indexMap[colName] + } + + var ids []int + switch op { + case "=": + if vals, ok := indexMap[colName][val]; ok { + ids = vals + } + case ">": + for key, vals := range indexMap[colName] { + if key.(int) > val.(int) { + ids = append(ids, vals...) + } + } + case "<": + for key, vals := range indexMap[colName] { + if key.(int) < val.(int) { + ids = append(ids, vals...) + } + } + } + + newIndexMap := make(map[interface{}][]int) + for _, id := range ids { + record := table.getRecordByID(id) + if _, ok := indexMap[colName][record.data[table.getColumnIndex(colName)]]; ok { + newIndexMap[record.data[table.getColumnIndex(colName)]] = append(newIndexMap[record.data[table.getColumnIndex(colName)]], id) + } + } + + indexMap[colName] = newIndexMap + } + + newRecords := []*Record{} + for _, record := range table.records { + if len(whereClauses) > 0 { + match := true + for _, clause := range whereClauses { + parts := strings.Split(clause, " ") + colName := parts[0] + op := parts[1] + valStr := parts[2] + val, err := db.parseValue(valStr, table.getColumnType(colName)) + if err != nil { + return err + } + colIndex := table.getColumnIndex(colName) + if op == "=" && record.data[colIndex] != val { + match = false + break + } + if op == ">" && record.data[colIndex].(int) <= val.(int) { + match = false + break + } + if op == "<" && record.data[colIndex].(int) >= val.(int) { + match = false + break + } + } + if !match { + newRecords = append(newRecords, record) + continue + } + } + + for i, col := range table.columns { + if _, ok := table.indexMap[col.Name]; ok { + delete(table.indexMap[col.Name], record.data[i]) + } + } + } + + table.records = newRecords + + return nil +} + +// parseValue parses a value string into the appropriate type. +func (db *Database) parseValue(valStr string, typ string) (interface{}, error) { + switch typ { + case "int": + return strconv.Atoi(valStr) + case "string": + return valStr, nil + default: + return nil, fmt.Errorf("Invalid column type %s", typ) + } +} + +// getRecordByID returns the record with the given id. +func (t *Table) getRecordByID(id int) *Record { + for _, record := range t.records { + if record.id == id { + return record + } + } + return nil +} + +// getColumnType returns the type of the column with the given name. +func (t *Table) getColumnType(name string) string { + for _, col := range t.columns { + if col.Name == name { + return col.Typ + } + } + return "" +} + +// getColumnIndex returns the index of the column with the given name. +func (t *Table) getColumnIndex(name string) int { + for i, col := range t.columns { + if col.Name == name { + return i + } + } + return -1 +} diff --git a/yoloDB/yolodb_test.go b/yoloDB/yolodb_test.go new file mode 100644 index 00000000..6c798e5 --- /dev/null +++ b/yoloDB/yolodb_test.go @@ -0,0 +1,25 @@ +package yolodb + +import ( + "testing" +) + +func Test_NewDatabase(t *testing.T) { + db := NewDatabase() + if db == nil { + t.Fatalf("NewDatabase() returned nil") + } +} + +func Test_CreateTable(t *testing.T) { + db := NewDatabase() + columns := []*Column{ + {Name: "id", Typ: "int"}, + {Name: "name", Typ: "string"}, + {Name: "age", Typ: "int"}, + } + err := db.CreateTable("users", columns) + if err != nil { + t.Fatalf("CreateTable() failed: %v", err) + } +}