Code Monkey home page Code Monkey logo

gosl's Introduction

DSYiM

  • I am Gon.
  • At work, I am a software engineer.
  • At home, I am an amateur artist.
  • I like to write programs in Java, Go, and Python.
  • I love to learn new programming languages.
  • I believe naming is the most difficult part of programming.

gosl's People

Contributors

gonyyi avatar

Watchers

 avatar

gosl's Issues

Add methods `Enable(bool)` to `Logger`

There's a time when I only need a log for certain area only.
At this point, it has to set with SetOutput(Writer, bool) each time.
But, instead, how about simple Logger.Enable(bool).

Example:

var l gosl.Logger{}
l = l.SetOutput(os.Stdout, true)
l.WriteString("hello"...)
...

// No need to record
l = l.SetOutput(nil, true)
...
...

// Need to record
l = l.SetOutput(os.Stdout, true)
...

But if there's a Logger.Enable(bool) Logger,

var l gosl.Logger{}
l = l.SetOutput(os.Stdout, true)
l.WriteString("hello"...)
...

// No need to record
l = l.Enable(false)
...
...

// Need to record
l = l.Enable(true)
...

gosl.Mutex change

Currently Mutex has different usage than MuInt:

// Mutex - cannot initialize in the same line because `Mutex` type is `chan struct{}`
var mu gosl.Mutex
mu = mu.Init()

// MuInt - can initialize in the same line
mui := gosl.MuInt{}.Init()

Try to make Mutex same as MuInt

Atoi, MustAtoi bug when larger numbers are used

println(100, gosl.MustAtoi("9223372036854775807", -1)) // 9223372036854775807 (o)
println(101, gosl.MustAtoi("-9223372036854775807", -1)) // -9223372036854775807 (o)
println(102, gosl.MustAtoi("99223372036854775807", -1)) // 6989651668307017727 (x)

Bytes to Integer

func IntAsBytes(i int64) [8]byte {
	// Little endian
	return [8]byte{
		byte(0xff & i),
		byte(0xff & (i >> 8)),
		byte(0xff & (i >> 16)),
		byte(0xff & (i >> 24)),
		byte(0xff & (i >> 32)),
		byte(0xff & (i >> 40)),
		byte(0xff & (i >> 48)),
		byte(0xff & (i >> 56)),
	}
}
func BytesAsInt(b [8]byte) (out int64) {
	// Little endian
	return int64(b[0]) | int64(b[1])<<8 | int64(b[2])<<16 | int64(b[3])<<24 |
		int64(b[4])<<32 | int64(b[5])<<40 | int64(b[6])<<48 | int64(b[7])<<56
}

Add some commonly used interface

  • Logger
  • Datastore
  • ...

eg.

// Datastore is an interface for key-value storage
type Datastore interface {
	Get(dst, bucket, key []byte) ([]byte, error)
	Set(bucket, key, value []byte) error
	Delete(bucket, key []byte) error
	Iterate(bucket, keyPrefix []byte, fn func(key []byte, value []byte)) error
	Update(bucket, key []byte, fn func(oldValue []byte) (newValue []byte)) error
	Upsert(bucket, key []byte, value []byte) error // set if not exist
	Close() error // close the database
}

Add `*Logger.Enabled()bool`

By having this, it can reduce time taking for additional formatting

// Enabled will return if logger is currently enabled
func (l Logger) Enabled() bool {
	return l.enable
}

IsNumber doesn't account for the sign

Currently, IsNumber() only expects 0-9. However, it should account for negative sign as well (and maybe positive too)

Fix will be as below.

// IsNumber updated
func IsNumber(s string) bool {
	if len(s) == 0 {
		return false
	}
	for i, v := range s {
		// Number should consist of -, +, and 0-9
		// And only first character can be - or +.
		// Otherwise, it should be between 0-9.
		if i == 0 && (v == '-' || v == '+') {
			continue
		}
		if v < '0' || v > '9' {
			return false
		}
	}
	return true
}

ElmKey

Maybe rename whole Elem to Elm??
Think about naming...

// ElemByKey takes string and search for the key, and return the value.
// Eg. ElemByKey(`id=123,name=Gon,age=40`, "age", '=', ',') --> "40"
func ElemByKey(s string, key string, sep, delim rune) string {
	// Get first delim
	cur := 0
	foundKey := false
	lastIdx := len(s) - 1
	var prevC rune
	for idx, c := range s {
		if foundKey {
			if idx == lastIdx {
				return s[cur : idx+1] // when [1,2], to select 1,2, s[1:3]; need [:n+1].. therefore
			}
			if c == delim {
				return s[cur:idx]
			}
			continue
		}
		if c == sep || c == delim {
			if c == sep && s[cur:idx] == key { // sep separates key and value, `abc=123, def=456` --> "="
				foundKey = true
			}
			// delimiter splits diff fields, `abc=123, def=456` --> ","
			cur = idx
			if idx != lastIdx {
				cur += 1
			}
			prevC = c
			continue
		}

		if prevC == delim {
			if c == ' ' {
				continue
			}
			cur = idx
		}
		prevC = c
	}
	return ""
}

TEST

	t.Run("ElemByKey", func(t *testing.T) {
		s := `id=123, name=Gon, age=40,name2=Jon,blank=,age2=100`
		gosl.Test(t, "123", gosl.ElemByKey(s, "id", '=', ','))
		gosl.Test(t, "Gon", gosl.ElemByKey(s, "name", '=', ','))
		gosl.Test(t, "40", gosl.ElemByKey(s, "age", '=', ','))
		gosl.Test(t, "Jon", gosl.ElemByKey(s, "name2", '=', ','))
		gosl.Test(t, "100", gosl.ElemByKey(s, "age2", '=', ','))
		gosl.Test(t, "", gosl.ElemByKey(s, "blank", '=', ','))
	})

BENCH

func Benchmark_String_ElemByKey(b *testing.B) {
	b.Run("ElemByKey", func(b *testing.B) {
		b.ReportAllocs()
		s := `id=123, name=Gon, age=40, someLong=,name2=Jon,age2=100`
		var out string
		for i := 0; i < b.N; i++ {
			out = gosl.ElemByKey(s, "age", '=', ',')
		}
		_ = out
		//println(out)
	})
}

Enable `*Buf.Read()`

File: gosl.go

	EOF error = NewError("EOF") // EOF can be updated by io.EOF or any other eg. `gosl.EOF = io.EOF`

File: buf.go (can be different file)

// Reader - to avoid importing "io"
type Reader interface {
	// Read reads a data to (fixed size) byte p and return how many bytes were read.
	// This will return EOF error when there's no more data to read.
	Read(p []byte) (n int, err error)
}

File: buf.go

// Read for io.Reader interface
// This will return custom EOF error and it can be set by modifying
func (b *Buf) Read(p []byte) (n int, err error) {
	n = copy(p, *b)
	*b = (*b)[n:]
	if n != 0 {
		return n, nil
	}
	return 0, EOF
}

File: buf_test.go

t.Run("Read()", func(t *testing.T) {
		tstr := "123456789_123456789_"

		t.Run("basic", func(t *testing.T) {
			buf = buf.Set(tstr)
			gosl.EOF = io.EOF
			out, err := io.ReadAll(&buf)
			gosl.Test(t, nil, err)
			gosl.Test(t, tstr, string(out))
			gosl.Test(t, 0, buf.Len())
		})

		t.Run("indivisible", func(t *testing.T) {
			b1 := buf.Set(tstr)
			b2 := bytes.NewBufferString(tstr)
			_, _ = b1, b2

			tmp1 := make([]byte, 9)
			tmp2 := make([]byte, 9)
			for {
				n1, err1 := io.ReadFull(&b1, tmp1)
				n2, err2 := io.ReadFull(b2, tmp2)
				gosl.Test(t, n1, n2)
				gosl.Test(t, err1, err2)
				if err1 != nil || err2 != nil {
					break
				}
			}
		})

		t.Run("divisible", func(t *testing.T) {
			b1 := buf.Set(tstr)
			b2 := bytes.NewBufferString(tstr)
			_, _ = b1, b2

			// Divisible size buffer
			tmp1 := make([]byte, 10)
			tmp2 := make([]byte, 10)
			for {
				n1, err1 := io.ReadFull(&b1, tmp1)
				n2, err2 := io.ReadFull(b2, tmp2)
				gosl.Test(t, n1, n2)
				gosl.Test(t, err1, err2)
				if err1 != nil || err2 != nil {
					break
				}
			}
		})

		t.Run("larger buffer than Buf", func(t *testing.T) {
			b1 := buf.Set(tstr)
			b2 := bytes.NewBufferString(tstr)
			_, _ = b1, b2

			// Divisible size buffer
			tmp1 := make([]byte, 30)
			tmp2 := make([]byte, 30)
			for {
				n1, err1 := io.ReadFull(&b1, tmp1)
				n2, err2 := io.ReadFull(b2, tmp2)
				gosl.Test(t, n1, n2)
				gosl.Test(t, err1, err2)
				if err1 != nil || err2 != nil {
					break
				}
			}
		})
	})

Add `DeduplicateStrings([]string)[]string`

Not sure about the naming.. But considering,

  • DedupStrings()
  • DeduplicateStrings()
  • UniqueStrings()
package main

import (
	"github.com/gonyyi/gosl"
)

// DeduplicateStrings will deduplicate string slice
// First, it will sort, then exam from the left to make sure every values are different from previous.
// NOTE: during the dedup process, this will alter original slice -- this will sort it.
func DeduplicateStrings(p []string) []string {
	// when there's one or no element, no need to dedup
	if len(p) < 2 {
		return p
	}

	// sort the string
	gosl.SortStrings(p, nil)

	// starting with the 2nd element as 1st will be the baseline.
	// whenever new one's found, set its index to cur; and return up to cur
	cur := 0
	for i := 1; i < len(p); i++ {
		// if element is same as previous, skip
		if p[i-1] == p[i] {
			continue
		}
		cur += 1
		p[cur] = p[i]
	}
	return p[:cur+1]
}

func main() {
	a := []string{"a", "d", "b", "x", "b", "d"}
	a = DeduplicateStrings(a)

	for _, v := range a {
		print(v + " ")
	}
}

Add few commonly used const status codes

Few of the http status code commonly used.

const (
	StatusProcessing           = 102 // currently working on the request
	StatusOK                   = 200
	StatusFound                = 302
	StatusInputInvalid         = 400
	StatusBadCredential        = 401
	StatusForbidden            = 403
	StatusNotFound             = 404
	StatusNotAccepted          = 406
	StatusConflict             = 409
	StatusDeleted              = 410
	StatusInputTooLarge        = 413
	StatusInputInvalidRange    = 416
	StatusLocked               = 423
	StatusUpgradeRequired      = 426
	StatusPreconditionRequired = 428
	StatusLegalIssues          = 451
	StatusInternalError        = 500
	StatusServiceUnavailable   = 503
	StatusServiceTimeout       = 504
	StatusNotSupportedVersion  = 505
	StatusOutOfSpace           = 507
)

Update Limiter

Updated version

package limiter

// (c) Gon Y. Yi 2021-2022 <https://gonyyi.com/copyright>
// Last Update: 01/13/2022

// CONCURRENCY LIMITER v1.0.0
// --------------------------
// Usage:
//     l := limiter.NewLimiter(10, 80) // Create Limiter with 10 concurrent workers and 80 queues
//     for someCondition {
//         l.Run(func(){  // Add a job using *Limiter.Run(fn)
//             someCode() // Code that needs to run concurrently
//         })
//     }
//     l.Stop(false) // Stop the limiter. Anything after this will be skipped.
//     for l.IsActive() == false { // Wait until it's completely stopped
//         time.Sleep(time.Second))
//     }
//     l.Close() // Close the Limiter
//

// NewLimiter will return a *Limiter
func NewLimiter(worker, queue uint16) *Limiter {
	l := &Limiter{}
	if worker == 0 {
		worker = 1
	}
	if queue == 0 {
		queue = 1
	}
	l, _ = l.Init(worker, queue)
	return l
}

// Limiter is a queue based concurrent runner.
type Limiter struct {
	worker chan struct{} // worker limits how many concurrent
	queue  chan func()   // queue for jobs
	mu     chan struct{} // mutex
	status bool          // only when status is true, new job can be added to queue.
}

// Init initialize Limiter
// If queue size, job may hold until enough queue is available.
func (l *Limiter) Init(workers, queue uint16) (lim *Limiter, ok bool) {
	// do not allow unbuffered channel.
	if workers == 0 || queue == 0 {
		return nil, false
	}

	// l.mu will be nil if the Limiter (1) is a fresh object, or (2) has called Closed()
	if l.mu != nil {
		return l, false // when Closed() is not called, *Limiter.monitor() maybe still running.
	}

	l.worker = make(chan struct{}, workers)
	l.queue = make(chan func(), queue)
	l.mu = make(chan struct{}, 1) // mutex, let only 1 at a time
	l.status = true               // false -> true
	go l.monitor()                // start monitoring in background. this will be cancelled only when Close() is called.
	return l, true
}

// ifStatusIs will compare status with *Limiter.status. If same, this will return true.
// Also, if status and new values are differ, it will switch status value to new value.
func (l *Limiter) ifStatusIs(status, new bool) (match bool) {
	l.mu <- struct{}{}         // lock
	match = l.status == status // match (output) will be true if a func argument status is same as l.status.
	if match && status != new {
		l.status = new // when given status and new are different, update status AFTER calculate the match (above)
	}
	<-l.mu // unlock
	return
}

// monitor monitors queue, and when new queue arrives, it will hand it to worker if available.
// otherwise, it will wait a worker become available.
// monitor will stop when *Limiter.queue is closed.
func (l *Limiter) monitor() {
loop:
	for {
		select {
		case f, ok := <-l.queue: // as soon as one gets out, new one can be there. and until ifStatusIs, one can be there as well.
			if ok {
				l.worker <- struct{}{} // WAIT until worker is available ** THIS CAN HIDE 1 JOB THAT GOES TO WORKER
				go func() {
					f()
					<-l.worker // NO ISSUE even when empty and closed EXCEPT WHEN cap(worker)=0
				}()
			} else { // if queue is closed, it will exit the monitor
				break loop
			}
		}
	}
}

// Run adds a job func() to queue. If limiter is no longer accepting, it will return false.
func (l *Limiter) Run(f func()) (ok bool) {
	// don't let nil to get in as a func
	if f == nil {
		return false
	}

	// Add new jobs to the queue if status says its available
	if l.ifStatusIs(true, true) {
		l.queue <- f
		return true
	}

	return ok
}

// Stop will make limiter stop taking jobs.
// - allow == false: All jobs in the queue will be cancelled and return how many were cancelled.
// - allow == true:  this will let all jobs in the queue to be finished.
func (l *Limiter) Stop(allow bool) (cancelled int) {
	if l.ifStatusIs(true, false) { // change accept status to false, so worker can't take a job
		if allow { // allow queue to be finished
			return 0
		}
		// drain all jobs in the queue
		cancelled = 0
		for len(l.queue) > 0 {
			<-l.queue
			cancelled += 1
		}

		return cancelled // return how many jobs in queue has been cancelled (drained)
	}
	return 0
}

// Status shows current limiter status. (state: currently taking a job)
func (l *Limiter) Status() (state bool, activeWorkers, activeQueue int) {
	if l.mu != nil {
		return l.ifStatusIs(true, true), len(l.worker), len(l.queue)
	}
	return false, 0, 0
}

// IsActive will return true if there's something running.
// This can be used as a part of wait-all-jobs.
func (l *Limiter) IsActive() bool {
	if s, w, q := l.Status(); s == false && w+q == 0 {
		return false
	}
	return true
}

// Close will attempt to close the limiter. If a job is currently running, it will return false.
func (l *Limiter) Close() (ok bool) {
	l.Stop(false)     // if already stopped, this will ignore
	if l.IsActive() { // if the limiter is still active, return false
		return false
	}

	close(l.queue)  // this will also close the monitor()
	close(l.worker) // DO NOT DRAIN THE WORKER: when all jobs are done, its size should be 0 anyway.
	close(l.mu)
	l.mu = nil // only after closing all channels, mu will set nil in case Init() is called later.
	return true
}

Bug: NewBuffer()

// THIS GETS ERROR as buf.Reset() returns a pointer
buf := gosl.NewBuffer(1024)
buf = buf.Reset()  

Add `SortAny()`

This function will sort any slice without any memory allocation.

Desired name: SortAny or SortSlice

package main

// SortAny is designed to sort any slice with no memory allocation.
// The usage is bit different than Go's `sort.Slice()` function.
// - pSize: size of slice that needs to be sorted
// - swap: a function that will swap the slice
// - less: a function that will return true when index i of slice is less than j's.
//
// Example:
// 	SortAny(
//		len(a), // size of the slice
//		func(i1, i2 int) { a[i1], a[i2] = a[i2], a[i1] },       // swap
//		func(i, j int) bool { return a[i].Score > a[j].Score }, // less
//	)
func SortAny(pSize int, swap func(i, j int), less func(i, j int) bool) {
	// This function requires both swap and less function.
	if pSize < 2 ||  swap == nil || less == nil {
		return
	}
	for {
		changed := false
		for i := 0; i < pSize-1; i++ {
			if !less(i, i+1) { // 2nd one is bigger
				if !less(i+1, i) {
					continue // equal
				}
				// swap
				swap(i, i+1)
				changed = true
			}
		}
		// Nothing changed, no more need to run
		if changed == false {
			break
		}
	}
}

type Alien struct {
	StudentID int
	Name      string
	Score     int
}

func main() {
	a := []Alien{
		{StudentID: 4, Name: "Jordon", Score: 11},
		{StudentID: 1, Name: "Aaron", Score: 13},
		{StudentID: 2, Name: "Gon", Score: 11},
		{StudentID: 3, Name: "Aaron", Score: 17},
		{StudentID: 5, Name: "Jordan", Score: 20},
	}

	// Sort by highest score
	// If want multi sort, modify `less` function such as:
	//   // Sort by highest score, and alphabetical 
	//   func(i,j int) bool {
	//     if a[i].Score == a[j].Score { return a[i].Name > a[j].Name }
	//     return a[i].Score > a[j].Score
	//   }
	SortAny(
		len(a), // size of the slice
		func(i1, i2 int) { a[i1], a[i2] = a[i2], a[i1] },       // swap
		func(i,j int) bool {
			if a[i].Score == a[j].Score { return a[i].Name > a[j].Name }
			return a[i].Score > a[j].Score
		},
	)
	
	for _, v := range a {
		println(v.StudentID, v.Score, v.Name)
	}

}

Add Datastore interface

// Datastore is an interface for key-value storage
// When Set()'s value is nil, it delete the entry.
type Datastore interface {
	Get(dst, bucket, key []byte) ([]byte, error)
	Set(bucket, key, value []byte) error
	Close() error
}

// DatastoreExt will have few more operators for convenience
type DatastoreExt interface {
	Datastore
	Has(bucket, key []byte) (bool, error)
	Delete(bucket, key []byte) error
	Iterate(bucket, keyPrefix []byte, fn func(key []byte, value []byte)) error
	Update(bucket, key []byte, fn func(oldValue []byte) (newValue []byte)) error
	Upsert(bucket, key []byte, value []byte) error
}

Add `AppendIntf()` - number format with comma

For the function name, either AppendIntFormat or AppendIntf

type Unit uint8

const(
   IntComma = iota + 1 // 1000 --> 1,000
   IntBytes // 1024 --> 1KB
   IntUnit  // 1000 --> 1K
)

func AppendIntFormat([]byte, int, Unit)[]byte{
   ...
}

eg. 1000000 --> 1,000,000

Create `NewMuInt()`

Do same for other Mutexs

// Currently
mui := gosl.MuInt{}.Init()

// New
mui := gosl.NewMuInt()

And NewMuInt():

func NewMuInt() MuInt {
    return MuInt{}.Int()
}

Atoi() bug

When argument is blank string, it will panic.

Add following to beginning of Atoi()

if len(s) == 0 {
	return 0, false
}

Clean up those aren't absolutely needed.

This became too big. Time to clean up.

  1. mutex.go + pool.go --> sync.go (8f222ee)
  2. Any func in string.go to have String prefix ?
    a. Itoa, Atoi, MustAtoi ?
  3. logger.go + write.go --> logger.go (8f222ee)

Goal

  1. For a function, copy and paste a single file should work -- eg. any uses byte function should be within byte file.
  2. Combine buf.go -> bytes.go.

Tasks

  • All the test files should be in /test folder
  • Remove Global Buffers, and combine buf.go -> bytes.go
  • Remove the Logger{}, and create LineWriter{} instead.
  • Maybe instead of Split(), write something that can be generalized??? Hmm..
    names := "Gon,Jon,Don"
    
    gosl.StringSplit(
        names,
        ',', // rune comma 
        func( idx, beg, end ){ 
            if idx == 2 {
                println(names[beg:end])  // should be Don
            }
        })

Add `NewBufferPool(size int) BufferPool`

This will require some changes, however, current default buffer pool does not allow a user to change the size of buffer.
Therefore need one that can be customized.

Usage

bp := gosl.NewBufferPool(1024) // create buffer pool with buffer size 1k
buf := bp.Get() // get a buffer
buf.Free() // returns a buffer to pool 

FilterAny() for any slice

Func

func FilterAny(pSize int, cut func(idx int), keep func(idx int) bool) (removed int) {
	for i := pSize - 1; i >= 0; i-- { // since need an index, subtract by 1
		if keep(i) == false {
			cut(i)
			removed += 1
		}
	}
	return removed
}

Test

func TestFilterAny(t *testing.T) {
	t.Run("basic", func(t *testing.T) {
		SHOW := false

		prn := func(s []string) {
			for i := 0; i < len(s); i++ {
				println("\t", i, s[i])
			}
		}
		_ = prn

		var s = []string{"GON", "IS", "HERE", "123", "HAHAHA", "BLAH", "", "OK", "HAH"}

		if SHOW {
			println("Original")
			prn(s)
		}

		removed := FilterAny(
			len(s),
			func(idx int) {
				s = append(s[:idx], s[idx+1:]...)
			},
			func(idx int) bool {
				if l := len(s[idx]); l == 3 || l == 0 {
					return false
				}
				return true
			},
		)

		if SHOW {
			println("Filtered")
			prn(s)
			println("Removed: ", removed)
		}

		buf := make(gosl.Buf, 0, 128)
		buf = buf.WriteStrings(s, ',')

		gosl.Test(t, 4, removed)
		gosl.Test(t, "IS,HERE,HAHAHA,BLAH,OK", buf.String())
	})
}

GetBuffer()

  1. Utilize DefaultBufferSize when initialize.
  2. Create SetBuffer(int) function that will set DefaultBufferSize

Mutex, MuInt, Pool

Add Status() that returns if channel is not-init, active or closed.

Apply to all where channel is being used.

Status

  • -1: un-initialized
  • 0: closed
  • 1: active
// Status will return channel status
// -1 Not init
//  0 Closed
//  1 Active
func (m Mutex) Status() int {
	// Not initialized
	if m == nil {
		return -1
	}

	// Active with data
	if len(m) > 0 {
		return 1
	}

	// Either active or closed
	select {
	case _, ok := <-m: // NOTE: this only runs when the chan is closed OR has something.
		if !ok {
			// Closed
			return 0
		}
	default:
	}
	// Active without data
	return 1
}

Add `NewLogger(Writer)`

Currently Gosl doesn't have a NewLogger(Writer), but only has SetOutput(Writer, bool).
How about creating a NewLogger(Writer) for convinience?

For instance, let's assume we have a code like below, where constructor will require 3 lines.

func NewStatus(w io.Writer) *status {
	s := &status{}
	s.log = s.log.SetOutput(w, true) // this can't set without `NewLogger(Writer)`
	return s
}

type status struct {
	log  gosl.Logger
	time time.Time
}

If we have a NewLogger(Writer), it can be simplified as below:

func NewStatus(w io.Writer) *status {
	return &status{
		log: NewLogger(w),
	}
}

Add type `Ver` back

Textbox (https://github.com/gonyyi/textbox) is very simple and often used for every code.
Maybe add it to Gosl?

Usage suggestions

  • TextBox(string) string
  • TextBox(string, Writer)

Maybe integrate with a type Ver?

Name suggestion: Whoami, Ver, Name, Info
In last release, Version was removed. But maybe can be integrated together?

const Version gosl.Ver = "Gosl 1.1.0"
...
Version.PrintBox() // print box to stdout
n, err := Version.WriteBox(io.Writer)
s := Version.BoxString()
// Version.Append([]byte) []byte
dst = Version.Append(dst) 

If Ver's coming back, any way to include source, date, etc into it?
Maybe store as a single string and parse it?

Eg. Gosl; v=1.1.0; c=Copyright 2022 Gon Yi; a=Gon Yi; d=2020-01-06; u=gonyyi.com; u=github.com/gonyyi where multiple u= in this example will indicate multiple items or lines.

  • Semicolon delimited, but the first item to be name of the program.
  • v: version
  • a: author
  • c: copyright message
  • d: date
  • u: URL

Usage

  • Basic
    • v.String()string
    • v.Bytes([]byte)[]byte
    • v.Println()
    • v.Name() string
    • v.Author() string
    • v.Date() string
    • v.Box() Box // convert version to Box

??

  • Box
    • b.String(...string) string
    • v.BoxPrintln(...string)
    • v.BoxWrite(Writer)
    • v.BoxBytes([]byte) []byte

Add a type `IntTime`

package main

const tMin = 100
const tHr = tMin * 100
const tDay = tHr * 100
const tMo = tDay * 100
const tYr = tMo * 100

type IntTime int64

func (t IntTime) Date() int {
	return int(int64(t) / tDay)
}
func (t IntTime) Year() int {
	return int(int64(t) / tYr % 10000)
}
func (t IntTime) Month() int {
	return int(int64(t) / tMo % 100)
}
func (t IntTime) Day() int {
	return int(int64(t) / tDay % 100)
}
func (t IntTime) Time() int {
	return int(int64(t) % tDay / 1000)
}
func (t IntTime) Hour() int {
	return int(int64(t) / tHr % 100)
}
func (t IntTime) Min() int {
	return int(int64(t) / tMin % 100)
}

func (t IntTime) Sec() int {
	return int(int64(t) % 100)
}

func (t IntTime) SetDate(year, month, date int) IntTime {
	tmp := int64(t)
	if 1900 < year && month < 3000 {
		tmp += -(int64(t.Year()) * int64(tYr)) + int64(year*tYr)
	}
	if 0 < month && month < 13 {
		tmp += -(int64(t.Month()) * int64(tMo)) + int64(month*tMo)
	}
	if 0 < date && date < 32 {
		tmp += -(int64(t.Day()) * int64(tDay)) + int64(date*tDay)
	}
	return IntTime(tmp)
}

func (t IntTime) SetTime(hour, min, sec int) IntTime {
	tmp := int64(t)
	if -1 < hour && hour < 24 {
		tmp += -(int64(t.Hour()) * int64(tHr)) + int64(hour*tHr)
	}
	if -1 < min && min < 60 {
		tmp += -(int64(t.Min()) * int64(tMin)) + int64(min*tMin)
	}
	if -1 < sec && sec < 60 {
		tmp += -int64(t.Sec()) + int64(sec)
	}
	return IntTime(tmp)
}

func (t IntTime) stringAdd(dst []byte, length, i int, suffix byte) []byte {
	if length == 4 {
		dst = append(dst, byte('0'+(i%10000)/1000))
		dst = append(dst, byte('0'+(i%1000)/100))
	}
	dst = append(dst, byte('0'+(i%100)/10))
	dst = append(dst, byte('0'+(i%10)/1))
	if suffix != 0 {
		dst = append(dst, suffix)
	}
	return dst
}

func (t IntTime) parseNPower(n int) (out int) {
	out = 1
	for i := 0; i < n; i++ {
		out = out * 10
	}
	return out
}

func (t IntTime) parseInt(p string) (out int, ok bool) {
	for i := 0; i < len(p); i++ {
		if '0' <= p[i] && p[i] <= '9' {
			out = out + int(p[i]-'0')*t.parseNPower(len(p)-i-1)
		} else {
			return 0, false
		}
	}
	return out, true
}

func (t IntTime) Parse(s string) (newTime IntTime, ok bool) {
	if len(s) != 19 {
		return 0, false
	}
	// 0123456789_12345678
	// 2006/01/02 15:04:05
	if s[4] != '/' || s[7] != '/' || s[10] != ' ' || s[13] != ':' || s[16] != ':' {
		return 0, false
	}

	ok, out, tmp := false, 0, 0
	if tmp, ok = t.parseInt(s[0:4]); ok {
		out += tmp * tYr
	} else {
		return 0, false
	}
	if tmp, ok = t.parseInt(s[5:7]); ok {
		out += tmp * tMo
	} else {
		return 0, false
	}
	if tmp, ok = t.parseInt(s[8:10]); ok {
		out += tmp * tDay
	} else {
		return 0, false
	}
	if tmp, ok = t.parseInt(s[11:13]); ok {
		out += tmp * tHr
	} else {
		return 0, false
	}
	if tmp, ok = t.parseInt(s[14:16]); ok {
		out += tmp * tMin
	} else {
		return 0, false
	}
	if tmp, ok = t.parseInt(s[17:19]); ok {
		out += tmp
	} else {
		return 0, false
	}
	return IntTime(out), true 
}

func (t IntTime) String() string {
	out := make([]byte, 0, 19)
	out = t.stringAdd(out, 4, t.Year(), '/')
	out = t.stringAdd(out, 2, t.Month(), '/')
	out = t.stringAdd(out, 2, t.Day(), ' ')

	out = t.stringAdd(out, 2, t.Hour(), ':')
	out = t.stringAdd(out, 2, t.Min(), ':')
	out = t.stringAdd(out, 2, t.Sec(), 0)
	return string(out)
}

func main() {
	t := IntTime(20060102150405)
	println(t.String()) // 2006/01/02 15:04:05

	t2 := t.SetDate(2020, 10, 31).SetTime(5, 6, 0)
	println(t2.String()) // 2020/10/31 05:06:00

	println("t > t2:", t > t2) // false
	println("t < t2:", t < t2) // true

	t2 = t2.SetDate(2006, 1, 2).SetTime(15, 4, 5)
	println("t == t2:", t == t2) // true

	t2, ok := t2.Parse("1981/10/02 09:10:11")
	if ok {
		println(t2.String()) // true
	} else {
		println("NO") // true
	}

	println(t.String(), t2.String())
	println(t > t2, t < t2, t == t2)
}

v0.7.1 bug on writer_test.go

Currently writes to standard out.
Need to update as below

	t.Run("Close()", func(t *testing.T) {
		w1 = w1.Reset()
		var fw = fakeCloserWriter{out: &w1}
		var lw = gosl.NewLineWriter(&fw)
		//var lw = gosl.LineWriter{}.SetOutput(&fw)
		_ = lw
		lw.WriteString("abc") // this writes to buf w1

		gosl.Test(t, false, fw.closeCalled)
		err := lw.Close()
		gosl.Test(t, nil, err)
		gosl.Test(t, true, fw.closeCalled)
	})

Add Any{}, Nothing{}

To make the code cleaner, add following:

type Any interface{}
type Nothing struct{}

Bug with *Buf.WriteFloat()

  • FOR: *Buf.WriteFloat()
  • WHEN: Invalid float value such as 0 / 0 (NaN) were used as a param
  • RESULT: Some random values added to the buffer -&#39;..--).0-*(&#43;,))&#43;(0(.-&#39;..--).0-*(&#43;,))&#43;(0(

REPRODUCING THE BUG

package main

import (
	"github.com/gonyyi/gosl"
)

func main() {
	buf := make(gosl.Buf, 0, 1024)
	i := 1-1 // NaN happens at runtime but since compiler checks for NaN, I can't put 1/0 directly into the function
	buf = buf.Reset().WriteBytes('[').WriteInt(i).WriteString("] => ").WriteFloat(float64(i) / float64(i), 1)

	// Before fix: [0] => -'..--).0-*(+,))+(0(.-'..--).0-*(+,))+(0(
	// After fix:  [0] => NaN
	buf.Println()
}

SOLUTION

  • NaN (Not-a-Number) can be checked by n != n.
  • For valid number, this will return a true, but for NaN, it won't match with any.

Update the function BytesAppendFloat() from bytes.go

// BytesAppendFloat takes decimalPlace (0-4)
// Since float32's accuracy is so low, this will use float64 exclusively.
func BytesAppendFloat(dst []byte, value float64, decimal uint8) (out []byte) {
	// Handling NaN issue
	if value != value {
		return append(dst, "NaN"...)
	}
	// If panic, return dst
	defer IfPanic(func(i interface{}) { out = dst })

	// If decimal is not required, then treat it as an integer
	if decimal == 0 {
		return BytesAppendInt(dst, int(value))
	}

	// dec will be used for calculating decimal points (eg. 2 --> 100)
	var dec int = 1
	for i := 0; i < int(decimal); i++ {
		dec = dec * 10
	}

	// for simple calculation, pre-calculate signs.
	if value < 0 {
		dst = append(dst, '-')
		value = -value // flip the sign
	}

	// d for real number
	// r for below decimal point (reminder)
	d := int(value) // 123.456 --> 123
	r := int((value - float64(d)) * float64(dec))

	dst = BytesAppendInt(dst, d) // add real number
	dst = append(dst, '.')       // and decimal

	// Calculate how many leading zeros are needed below decimal place
	if dec > r { // this should be always true
		tmp := -1 // since last line will add at least 1 digit to it
		if r == 0 {
			tmp = int(decimal) - 1
		} else {
			for i := dec; r/i == 0; i = i / 10 {
				tmp += 1
			}
		}

		for i := 0; i < tmp; i++ {
			dst = append(dst, '0')
		}
	}

	dst = BytesAppendInt(dst, r) // add a dot and reminders
	return dst
}

`bufPoolItem` to support more methods

Now all the buf pool item from GetBuffer() has to saved to buf.Buf again as below.
And this became common bug when writing a code with buf pool item.

buf := gosl.GetBuffer()
buf.Buf = buf.Buf.WriteString(s.time.Format("2006/01/02 15:04:05"))

Add methods to simplify as below.

buf := gosl.GetBuffer()
buf.WriteString(s.time.Format("2006/01/02 15:04:05"))

Custom comparator bug with `SortSlice()`, and `SortString()`

Error Message

panic: runtime error: index out of range [2] with length 2

goroutine 1 [running]:
main.main.func2(0x102d23a, 0x1066eb0)
        .../test.go:29 +0x67
github.com/gonyyi/gosl.SortStrings({0xc000112ee0, 0x3, 0x2}, 0xc000112ea0)
        ...github.com/gonyyi/[email protected]/sort.go:43 +0x5e
main.main()
        .../test.go:28 +0x2b9

Affected

This error is triggered when two conditions are met.

  1. Two (2) or less items in the slice
  2. And uses comparator (func(idx1, idx2 int) bool)

This bug affects sort functions below.

  • SortSlice()
  • SortString()

Cuase

gosl/sort.go

Lines 14 to 18 in 7f3f8cc

func SortSlice(dst []interface{}, compare func(idx1, idx2 int) bool) (ok bool) {
// if compare func is not exist, or invalid, return false
if dst == nil || compare == nil || compare(1, 2) && compare(2, 1) {
return false
}

gosl/sort.go

Lines 41 to 45 in 7f3f8cc

func SortStrings(dst []string, compare func(idx1, idx2 int) bool) (ok bool) {
// if compare func is not exist, or invalid, return false
if dst == nil || (compare != nil && compare(1, 2) && compare(2, 1)) {
return false
}

When a comparator is given, it example the comparator by examing it to avoid a bug.
However, this became a bug itself adversally..

Add `ByteCount([]byte, byte) int`, `Count(string, string) int`

This will be used to check if number of delimiter is correct

// Count counts string lookup value from string s, returns total
func Count(s string, lookup string) int {
	count := 0
	for i:=0; i<len(s)-len(lookup)+1; i++ {
		if s[i:i+len(lookup)] == lookup {
			count += 1
		}
	}
	return count
}

// ByteCount counts byte `c` value from bytes `p`, returns total
func ByteCount(p []byte, c byte) int {
	count := 0
	for _, v := range p {
		if v == c {
			count += 1
		}
	}
	return count
}

Add `Contains(string, string)bool`

Gosl has HasPrefix and HasSuffix, but doesn't have Contains.

Function name to be either Contain() or Contains()

if gosl.Contain("hello Gon", "Gon") {
    println("Greeting for Gon")
}

Check for stringer on `gosl.Test()`

For the case below, since gosl.Buf meets stringer interface, gosl.Test() should able to compare, but currently giving a type error.

buf := make(gosl.Buf, 0, 1024)
buf = buf.WriterString("gonsama")
gosl.Test(t, "true", buf)

When ok returns false as below, retry with stringer

// case string:
act, ok := actual.(string)
if !ok {
	buf = buf.WriteString(exp).WriteString(" (string)").
		WriteString("\n\tACT => (err) Unexpected-Type\n")
	print(buf.String())
	tx.Fail()
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.