0x4a52466c696e74 2 years ago
commit
b96f29b529
56 changed files with 4209 additions and 0 deletions
  1. 1 0
      .gitignore
  2. 13 0
      .travis.yml
  3. 21 0
      LICENSE
  4. 16 0
      README.md
  5. 16 0
      README.ru.md
  6. 100 0
      bufio/reader.go
  7. 27 0
      bufio/z_test.go
  8. 235 0
      cache/cachemap.go
  9. 77 0
      cache/z_test.go
  10. 230 0
      containers/concurrent/list.go
  11. 39 0
      containers/concurrent/queue.go
  12. 64 0
      containers/concurrent/z_test.go
  13. 53 0
      containers/queue.go
  14. 92 0
      containers/stack.go
  15. 19 0
      containers/z_test.go
  16. 2 0
      database/nosql/README.md
  17. 76 0
      database/nosql/README.ru.md
  18. 8 0
      database/nosql/doc.go
  19. 72 0
      database/nosql/example_basic_test.go
  20. 67 0
      database/nosql/nosql.go
  21. 33 0
      database/nosql/z_test.go
  22. 5 0
      go.mod
  23. 2 0
      go.sum
  24. 373 0
      json/decoder.go
  25. 39 0
      json/json.go
  26. 256 0
      json/map.go
  27. 13 0
      json/test_array.json
  28. 6 0
      json/test_object.json
  29. 195 0
      json/z_test.go
  30. 50 0
      sip/asterisk/ami/action.go
  31. 413 0
      sip/asterisk/ami/client.go
  32. 11 0
      sip/asterisk/ami/conf.go
  33. 26 0
      sip/asterisk/ami/event.go
  34. 18 0
      sip/asterisk/ami/event_listener.go
  35. 141 0
      sip/asterisk/ami/originate.go
  36. 59 0
      sip/asterisk/ami/request.go
  37. 22 0
      sip/asterisk/ami/response.go
  38. 74 0
      sip/asterisk/ami/z_test.go
  39. 53 0
      smtp/emailer/emailer.go
  40. 170 0
      store/store.go
  41. 168 0
      store/string.go
  42. 73 0
      store/z_test.go
  43. 50 0
      text/checker/checker.go
  44. 25 0
      text/config/config.go
  45. 80 0
      text/config/ini/config.go
  46. 56 0
      text/config/ini/parser.go
  47. 50 0
      text/config/ini/section.go
  48. 15 0
      text/config/ini/test.ini
  49. 15 0
      text/config/ini/tmp.ini
  50. 49 0
      text/config/ini/z_test.go
  51. 38 0
      text/config/parser.go
  52. 1 0
      text/config/z_test.go
  53. 240 0
      text/translit/translit.go
  54. 25 0
      value/def/default.go
  55. 122 0
      value/value.go
  56. 15 0
      value/z_test.go

+ 1 - 0
.gitignore

@@ -0,0 +1 @@
+*.config

+ 13 - 0
.travis.yml

@@ -0,0 +1,13 @@
+--- 
+dist: xenial
+env: 
+  - GO111MODULE=on GOPROXY=https://proxy.golang.org
+go: 
+  - 1.10.x
+  - 1.11.x
+  - 1.12.x
+  - 1.13.x
+language: go
+os: linux
+script: 
+  - "go test -cpu=1,2 -v -tags integration ./..."

+ 21 - 0
LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2020 fcg-xvii
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 16 - 0
README.md

@@ -0,0 +1,16 @@
+<p align="center">
+  <span>English</span> |
+  <a href="README.ru.md#go-tools">Русский</a>
+  
+</p>
+
+[![Build Status](https://travis-ci.org/fcg-xvii/go-tools.svg?branch=master)](https://travis-ci.org/fcg-xvii/go-tools) 
+ [![go.dev reference](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/github.com/fcg-xvii/go-tools) 
+ [![Go Report Card](https://goreportcard.com/badge/github.com/fcg-xvii/go-tools)](https://goreportcard.com/report/github.com/fcg-xvii/go-tools) 
+
+# go-tools
+
+
+## License
+
+The MIT License (MIT), see [LICENSE](LICENSE).

+ 16 - 0
README.ru.md

@@ -0,0 +1,16 @@
+<p align="center">
+  <span>Русский</span> |
+  <a href="README.md#go-tools">English</a> 
+</p>
+
+[![Build Status](https://travis-ci.org/fcg-xvii/go-tools.svg?branch=master)](https://travis-ci.org/fcg-xvii/go-tools) 
+ [![go.dev reference](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/github.com/fcg-xvii/go-tools) 
+ [![Go Report Card](https://goreportcard.com/badge/github.com/fcg-xvii/go-tools)](https://goreportcard.com/report/github.com/fcg-xvii/go-tools)
+
+# go-tools
+> Библиотека вспомогательных инструментов для разработки приложений на golang
+
+
+## Лицензия
+
+The MIT License (MIT), подробнее [LICENSE](LICENSE).

+ 100 - 0
bufio/reader.go

@@ -0,0 +1,100 @@
+package bufio
+
+import (
+	"bytes"
+	"io"
+	"log"
+)
+
+var (
+	ReadBufferSize = 1024
+)
+
+func DelimRemove(data, delim []byte) []byte {
+	if bytes.HasSuffix(data, delim) {
+		data = data[:len(data)-len(delim)+1]
+	}
+	return data
+}
+
+func NewReader(r io.Reader) *Reader {
+	return &Reader{
+		r: r,
+	}
+}
+
+type Reader struct {
+	r    io.Reader
+	buf  bytes.Buffer
+	seek int
+}
+
+func (s *Reader) fromBuf(data []byte) (res []byte) {
+	if s.buf.Len() > 0 {
+		res = append(s.buf.Bytes(), data...)
+		s.buf.Reset()
+		s.seek = 0
+	} else {
+		res = data
+	}
+	return
+}
+
+func (s *Reader) toBuf(data []byte) {
+	s.seek = s.buf.Len()
+	s.buf.Write(data)
+}
+
+func (s *Reader) scanBuf(delim []byte) (res []byte, check bool) {
+	buf := s.buf.Bytes()[s.seek:]
+	if index := bytes.Index(buf, delim); index >= 0 {
+		rSize := s.seek + index + len(delim)
+		check, res = true, make([]byte, rSize)
+		s.buf.Read(res)
+		s.seek = 0
+	} else {
+		s.seek = s.buf.Len()
+	}
+	return
+}
+
+func (s *Reader) scanNeeded() bool {
+	return s.seek < s.buf.Len()
+}
+
+func (s *Reader) readBufferSize(delim []byte) int {
+	if ReadBufferSize >= len(delim) {
+		return ReadBufferSize
+	}
+	return len(delim)
+}
+
+func (s *Reader) ReadBytes(delim []byte) (res []byte, err error) {
+	var check bool
+	// scan internal buffer
+	if s.scanNeeded() {
+		log.Println("NEEDED")
+		if res, check = s.scanBuf(delim); check {
+			return
+		}
+	}
+	// read from external buffer
+	buf, count := make([]byte, s.readBufferSize(delim)), 0
+	for {
+		if count, err = s.r.Read(buf); count > 0 {
+			s.buf.Write(buf[:count])
+			for s.scanNeeded() {
+				if res, check = s.scanBuf(delim); check {
+					return
+				}
+			}
+		}
+		if err != nil {
+			if err == io.EOF && s.buf.Len() > 0 {
+				res, check = s.buf.Bytes(), true
+				s.buf.Reset()
+			}
+			return
+		}
+	}
+}

+ 27 - 0
bufio/z_test.go

@@ -0,0 +1,27 @@
+package bufio
+
+import (
+	"bytes"
+	"log"
+	"testing"
+)
+
+var (
+	dataBuf bytes.Buffer
+)
+
+func init() {
+	dataBuf.Write([]byte("{ one }\r\n{ two }\r\n{ three }"))
+}
+
+func TestDelim(t *testing.T) {
+	delim := []byte("}\r\n")
+	r := NewReader(&dataBuf)
+	for {
+		data, err := r.ReadBytes(delim)
+		log.Println(string(data), err, string(DelimRemove(data, delim)))
+		if err != nil {
+			break
+		}
+	}
+}

+ 235 - 0
cache/cachemap.go

@@ -0,0 +1,235 @@
+package cache
+
+import (
+	"runtime"
+	"sync"
+	"time"
+)
+
+type CallCreate func(key interface{}) (value interface{}, created bool)
+type CallCreateNew func(key interface{}) (rKey, value interface{}, created bool)
+type CallCheck func(key, value interface{}, exists bool) (rKey, rValue interface{}, created bool)
+
+func NewMap(liveDuration time.Duration, maxSize int) *CacheMap {
+	res := &CacheMap{&cacheMap{
+		locker:          new(sync.RWMutex),
+		items:           make(map[interface{}]*cacheMapItem),
+		liveDuration:    liveDuration,
+		maxSize:         maxSize,
+		stopCleanerChan: make(chan byte),
+	}}
+	runtime.SetFinalizer(res, destroyCacheMap)
+	return res
+}
+
+type CacheMap struct {
+	*cacheMap
+}
+
+type cacheMapItem struct {
+	value  interface{}
+	expire int64
+}
+
+type cacheMap struct {
+	locker           *sync.RWMutex
+	items            map[interface{}]*cacheMapItem
+	liveDuration     time.Duration
+	maxSize          int
+	cleanerWork      bool
+	stopCleanerChan  chan byte
+	cleanedEventChan chan map[interface{}]interface{}
+}
+
+func (s *cacheMap) CleanEvent() (eventChan <-chan map[interface{}]interface{}) {
+	s.locker.Lock()
+	if s.cleanedEventChan == nil {
+		s.cleanedEventChan = make(chan map[interface{}]interface{})
+	}
+	eventChan = s.cleanedEventChan
+	s.locker.Unlock()
+	return
+}
+
+func (s *cacheMap) runCleaner() {
+	ticker := time.NewTicker(s.liveDuration / 2)
+	for {
+		select {
+		case <-ticker.C:
+			{
+				now := time.Now().UnixNano()
+				s.locker.Lock()
+				cleaned := make(map[interface{}]interface{})
+				for key, val := range s.items {
+					if now > val.expire {
+						cleaned[key] = val.value
+						delete(s.items, key)
+					}
+				}
+				if len(cleaned) > 0 {
+					s.cleanedEventChan <- cleaned
+				}
+				if len(s.items) == 0 {
+					s.cleanerWork = false
+					ticker.Stop()
+					s.locker.Unlock()
+					return
+				}
+				s.locker.Unlock()
+			}
+		case <-s.stopCleanerChan:
+			{
+				s.cleanerWork = false
+				ticker.Stop()
+				return
+			}
+		}
+	}
+}
+
+func (s *cacheMap) delete(key interface{}) {
+	delete(s.items, key)
+	if len(s.items) == 0 && s.cleanerWork {
+		s.stopCleanerChan <- 0
+	}
+}
+
+// Delete removes cached object
+func (s *cacheMap) Delete(key interface{}) {
+	s.locker.Lock()
+	s.delete(key)
+	s.locker.Unlock()
+}
+
+func (s *cacheMap) set(key, value interface{}) {
+	s.items[key] = &cacheMapItem{
+		value:  value,
+		expire: time.Now().Add(s.liveDuration).UnixNano(),
+	}
+	if !s.cleanerWork {
+		s.cleanerWork = true
+		go s.runCleaner()
+	}
+}
+
+func (s *cacheMap) Set(key, value interface{}) {
+	s.locker.Lock()
+	s.set(key, value)
+	s.locker.Unlock()
+}
+
+func (s *cacheMap) SetMulti(m map[interface{}]interface{}) {
+	s.locker.Lock()
+	for key, val := range m {
+		s.set(key, val)
+	}
+	s.locker.Unlock()
+}
+
+func (s *CacheMap) DeleteMulti(keys []interface{}) {
+	s.locker.Lock()
+	for _, key := range keys {
+		delete(s.items, key)
+	}
+	s.locker.Unlock()
+}
+
+func (s *cacheMap) get(key interface{}) (res interface{}, check bool) {
+	var item *cacheMapItem
+	if item, check = s.items[key]; check {
+		res = item.value
+	}
+	return
+}
+
+func (s *cacheMap) Get(key interface{}) (res interface{}, check bool) {
+	s.locker.RLock()
+	res, check = s.get(key)
+	s.locker.RUnlock()
+	return
+}
+
+func (s *cacheMap) GetCreate(key interface{}, mCreate CallCreate) (val interface{}, check bool) {
+	if val, check = s.Get(key); !check {
+		s.locker.Lock()
+		if val, check = s.get(key); check {
+			s.locker.Unlock()
+			return
+		}
+
+		if val, check = mCreate(key); check {
+			s.set(key, val)
+		}
+		s.locker.Unlock()
+	}
+	return
+}
+
+func (s *cacheMap) GetCreateNew(key interface{}, mCreateNew CallCreateNew) (rKey, val interface{}, check bool) {
+	rKey = key
+	if val, check = s.Get(key); !check {
+		s.locker.Lock()
+		if val, check = s.get(key); check {
+			s.locker.Unlock()
+			return
+		}
+
+		if rKey, val, check = mCreateNew(key); check {
+			s.set(rKey, val)
+		}
+		s.locker.Unlock()
+	}
+	return
+}
+
+func (s *cacheMap) GetCheck(key interface{}, mCheck CallCheck) (res interface{}, check bool) {
+	s.locker.Lock()
+	res, check = s.get(key)
+	if rKey, rVal, rCheck := mCheck(key, res, check); rCheck {
+		s.set(rKey, rVal)
+		key, res, check = rKey, rVal, true
+	}
+	s.locker.Unlock()
+	return
+}
+
+// Each implements a map bypass for each key using the callback function. If the callback function returns false, then the cycle stops
+func (s *cacheMap) Each(callback func(interface{}, interface{}) bool) {
+	s.locker.RLock()
+	for key, val := range s.items {
+		if !callback(key, val.value) {
+			s.locker.RUnlock()
+			return
+		}
+	}
+	s.locker.RUnlock()
+}
+
+func (s *cacheMap) Len() (res int) {
+	s.locker.RLock()
+	res = len(s.items)
+	s.locker.RUnlock()
+	return
+}
+
+func (s *cacheMap) Keys() (res []interface{}) {
+	s.locker.RLock()
+	res = make([]interface{}, 0, len(s.items))
+	for key := range s.items {
+		res = append(res, key)
+	}
+	s.locker.RUnlock()
+	return
+}
+
+func (s *cacheMap) Clear() {
+	s.locker.Lock()
+	s.items = make(map[interface{}]*cacheMapItem)
+	s.locker.Unlock()
+}
+
+// for garbage collector
+func destroyCacheMap(m *CacheMap) {
+	close(m.stopCleanerChan)
+	close(m.cleanedEventChan)
+}

+ 77 - 0
cache/z_test.go

@@ -0,0 +1,77 @@
+package cache
+
+import (
+	"log"
+	"testing"
+	"time"
+)
+
+func TestMap(t *testing.T) {
+	m := NewMap(time.Second*5, 10)
+
+	m.Set("key", 10)
+
+	log.Println(m.Get("key"))
+	m.Delete("key")
+	log.Println(m.Get("key"))
+	log.Println(m.Get("key1"))
+
+	go func() {
+		for e := range m.CleanEvent() {
+			log.Println("CLEANED", e)
+		}
+	}()
+
+	for i := 0; i < 5; i++ {
+		time.Sleep(time.Second * 10)
+		m.Set("key", "oneee")
+
+	}
+}
+
+func TestMapCheck(t *testing.T) {
+	m := NewMap(time.Hour, 0)
+
+	m.Set("key", 10)
+
+	log.Println(m.GetCheck("key", func(key, value interface{}, exists bool) (rKey, rVal interface{}, needUpdate bool) {
+		log.Println("MCHECK", key, value, exists)
+		rVal = "Cool!!!"
+		rKey, needUpdate = "key1", true
+		return
+	}))
+
+	log.Println("===================")
+	log.Println(m.Get("key"))
+	log.Println(m.Get("key1"))
+
+	log.Println(m.GetCreateNew("key", func(key interface{}) (rKey, val interface{}, created bool) {
+		return "oooone", 1000, true
+	}))
+}
+
+func TestMapEach(t *testing.T) {
+	m := NewMap(time.Second*5, 10)
+	m.SetMulti(map[interface{}]interface{}{
+		1: "one",
+		2: "two",
+		3: "three",
+	})
+
+	m.Each(func(key, val interface{}) bool {
+		log.Println(key, val)
+		return false
+	})
+}
+
+func TestKeys(t *testing.T) {
+	m := NewMap(time.Second*5, 20)
+	m.SetMulti(map[interface{}]interface{}{
+		"one": 1,
+		"two": 2,
+	})
+	log.Println(m.Keys())
+	m.Clear()
+	log.Println(m.Keys())
+
+}

+ 230 - 0
containers/concurrent/list.go

@@ -0,0 +1,230 @@
+package concurrent
+
+import (
+	"sync"
+)
+
+func initElem(v interface{}) *Element {
+	return &Element{
+		m:   new(sync.RWMutex),
+		val: v,
+	}
+}
+
+type Element struct {
+	m    *sync.RWMutex
+	prev *Element
+	next *Element
+	val  interface{}
+}
+
+func (s *Element) SetVal(val interface{}) {
+	s.m.Lock()
+	s.val = val
+	s.m.Unlock()
+}
+
+func (s *Element) Prev() (elem *Element) {
+	s.m.RLock()
+	elem = s.prev
+	s.m.RUnlock()
+	return
+}
+
+func (s *Element) Next() (elem *Element) {
+	s.m.RLock()
+	elem = s.next
+	s.m.RUnlock()
+	return
+}
+
+func (s *Element) Val() (val interface{}) {
+	s.m.RLock()
+	val = s.val
+	s.m.RUnlock()
+	return
+}
+
+func (s *Element) destroy() {
+	s.m.Lock()
+	if s.prev != nil {
+		s.prev.next = s.next
+	}
+	if s.next != nil {
+		s.next.prev = s.prev
+	}
+	s.prev, s.next = nil, nil
+	s.m.Unlock()
+}
+
+func (s *Element) setNext(elem *Element) {
+	s.m.Lock()
+	if s.next != nil {
+		elem.next, s.next.prev = s.next, elem
+	}
+	s.next, elem.prev = elem, s
+	s.m.Unlock()
+}
+
+func (s *Element) setPrev(elem *Element) {
+	s.m.Lock()
+	if s.prev != nil {
+		elem.prev, s.prev.next = s.prev, elem
+	}
+	s.prev, elem.next = elem, s
+	s.m.Unlock()
+}
+
+func ListFromSlise(sl []interface{}) *List {
+	l := NewList()
+	for _, val := range sl {
+		l.PushBack(val)
+	}
+	return l
+}
+
+func NewList() *List {
+	return &List{
+		m: new(sync.RWMutex),
+	}
+}
+
+type List struct {
+	m     *sync.RWMutex
+	first *Element
+	last  *Element
+	size  int
+}
+
+func (s *List) PushBackIfNotExists(v interface{}) (elem *Element, added bool) {
+	s.m.Lock()
+	defer s.m.Unlock()
+	if s.search(v) == nil {
+		added = true
+		elem = s.pushBack(v)
+	}
+	return
+}
+
+func (s *List) PushBack(v interface{}) (elem *Element) {
+	s.m.Lock()
+	elem = s.pushBack(v)
+	s.m.Unlock()
+	return
+}
+
+func (s *List) pushBack(v interface{}) *Element {
+	elem := initElem(v)
+	if s.first == nil {
+		s.first, s.last = elem, elem
+	} else {
+		s.last.setNext(elem)
+		s.last = elem
+	}
+	s.size++
+	return elem
+}
+
+func (s *List) PushFront(v interface{}) (elem *Element) {
+	s.m.Lock()
+	elem = s.pushFront(v)
+	s.m.Unlock()
+	return
+}
+
+func (s *List) PushFrontIfNotExists(v interface{}) (elem *Element, added bool) {
+	s.m.Lock()
+	defer s.m.Unlock()
+	if s.search(v) == nil {
+		added = true
+		elem = s.pushFront(v)
+	}
+	return
+}
+
+func (s *List) pushFront(v interface{}) *Element {
+	elem := initElem(v)
+	if s.first == nil {
+		s.first, s.last = elem, elem
+	} else {
+		s.first.setPrev(elem)
+		s.first = elem
+	}
+	s.size++
+	return elem
+}
+
+func (s *List) Remove(elem *Element) {
+	if elem == nil {
+		return
+	}
+	s.m.Lock()
+	if elem == s.first {
+		s.first = elem.next
+	}
+	if elem == s.last {
+		s.last = elem.prev
+	}
+	elem.destroy()
+	s.size--
+	s.m.Unlock()
+	return
+}
+
+func (s *List) Size() (size int) {
+	s.m.RLock()
+	size = s.size
+	s.m.RUnlock()
+	return
+}
+
+func (s *List) First() (elem *Element) {
+	s.m.RLock()
+	elem = s.first
+	s.m.RUnlock()
+	return
+}
+
+func (s *List) Last() (elem *Element) {
+	s.m.RLock()
+	elem = s.last
+	s.m.RUnlock()
+	return
+}
+
+func (s *List) Index(index int) (elem *Element) {
+	i := 0
+	elem = s.First()
+	for elem != nil && i < index {
+		elem, i = elem.Next(), i+1
+	}
+	return
+}
+
+func (s *List) Slice() []interface{} {
+	s.m.RLock()
+	f, res := s.first, make([]interface{}, 0, s.size)
+	s.m.RUnlock()
+	for f != nil {
+		res, f = append(res, f.Val()), f.Next()
+	}
+	return res
+}
+
+func (s *List) search(val interface{}) *Element {
+	for f := s.first; f != nil; f = f.Next() {
+		if f.Val() == val {
+			return f
+		}
+	}
+	return nil
+}
+
+func (s *List) Search(val interface{}) *Element {
+	for f := s.First(); f != nil; f = f.Next() {
+		if f.Val() == val {
+			return f
+		}
+	}
+	return nil
+}

+ 39 - 0
containers/concurrent/queue.go

@@ -0,0 +1,39 @@
+package concurrent
+
+import (
+	"sync"
+
+	"git.ali33.ru/fcg-xvii/go-tools/containers"
+)
+
+func NewQueue() *Queue {
+	return &Queue{
+		q: containers.NewQueue(),
+		m: new(sync.RWMutex),
+	}
+}
+
+type Queue struct {
+	q *containers.Queue
+	m *sync.RWMutex
+}
+
+func (s *Queue) Size() (size int) {
+	s.m.RLock()
+	size = s.q.Size()
+	s.m.RUnlock()
+	return
+}
+
+func (s *Queue) Push(val ...interface{}) {
+	s.m.Lock()
+	s.q.Push(val...)
+	s.m.Unlock()
+}
+
+func (s *Queue) Pop() (val interface{}, check bool) {
+	s.m.Lock()
+	val, check = s.q.Pop()
+	s.m.Unlock()
+	return
+}

+ 64 - 0
containers/concurrent/z_test.go

@@ -0,0 +1,64 @@
+package concurrent
+
+import (
+	"log"
+	"testing"
+)
+
+func TestQueue(t *testing.T) {
+	q := NewQueue()
+	q.Push("one", "two", "three")
+	q.Push("four")
+	q.Push("five")
+	t.Log(q)
+	for {
+		if val, check := q.Pop(); check {
+			t.Log(val, q.Size())
+		} else {
+			break
+		}
+	}
+	t.Log(q)
+}
+
+func showList(l *List) {
+}
+
+func TestList(t *testing.T) {
+	l := NewList()
+	log.Println(l)
+	for i := 0; i < 10; i++ {
+		l.PushFront(i)
+	}
+	log.Println(l.Slice(), l.Size())
+	elem := l.Index(0)
+	log.Println(elem)
+	l.Remove(elem)
+	log.Println(l.Slice())
+	for l.Size() > 0 {
+		l.Remove(l.First())
+		log.Println(l.Slice(), l.Size())
+	}
+	log.Println(l.first, l.last)
+	l.PushBack(100)
+	log.Println(l.Slice(), l.Size(), l.first, l.last)
+
+	sl := make([]interface{}, 10)
+	for i := range sl {
+		sl[i] = i * 100
+	}
+	log.Println(sl)
+	l = ListFromSlise(sl)
+	log.Println(l.Slice())
+
+	elem = l.Search(500)
+	log.Println(elem)
+	elem = l.Search(1500)
+	log.Println(1500, "search", elem)
+
+	elem, added := l.PushBackIfNotExists(500)
+	log.Println(500, elem, added)
+	elem, added = l.PushBackIfNotExists(1500)
+	log.Println(1500, elem, added)
+
+}

+ 53 - 0
containers/queue.go

@@ -0,0 +1,53 @@
+package containers
+
+type queueItem struct {
+	val  interface{}
+	next *queueItem
+}
+
+func NewQueue() *Queue {
+	return new(Queue)
+}
+
+type Queue struct {
+	start *queueItem
+	top   *queueItem
+	size  int
+}
+
+func (s *Queue) Size() int {
+	return s.size
+}
+
+func (s *Queue) Push(val ...interface{}) {
+	item := &queueItem{
+		val: val[0],
+	}
+	if s.top != nil {
+		s.top.next = item
+	} else {
+		s.start = item
+	}
+	s.top = item
+	s.size++
+	for i := 1; i < len(val); i++ {
+		it := &queueItem{
+			val: val[i],
+		}
+		item.next, item, s.top = it, it, it
+		s.size++
+	}
+}
+
+func (s *Queue) Pop() (val interface{}, check bool) {
+	if s.start == nil {
+		return
+	}
+	val, check = s.start.val, true
+	s.start = s.start.next
+	if s.start == nil {
+		s.top = nil
+	}
+	s.size--
+	return
+}

+ 92 - 0
containers/stack.go

@@ -0,0 +1,92 @@
+package containers
+
+import "fmt"
+
+// NewStack is constructor for stack
+func NewStack(length int) *Stack {
+	return &Stack{make([]interface{}, 0, length)}
+}
+
+// Sack struct
+type Stack struct {
+	list []interface{}
+}
+
+// Peek returns object on top
+func (s *Stack) Peek() interface{} {
+	if len(s.list) > 0 {
+		return s.list[len(s.list)-1]
+	}
+	return nil
+}
+
+// Push setup object in top
+func (s *Stack) Push(vals ...interface{}) {
+	s.list = append(s.list, vals...)
+}
+
+// Pop get and return object on top
+func (s *Stack) Pop() (res interface{}) {
+	if len(s.list) > 0 {
+		index := len(s.list) - 1
+		res = s.list[index]
+		s.list = s.list[:index]
+	}
+	return
+}
+
+// Len returns stack size
+func (s *Stack) Len() int {
+	return len(s.list)
+}
+
+// Cap returns stack capacity
+func (s *Stack) Cap() int {
+	return cap(s.list)
+}
+
+// String returns description string
+func (s *Stack) String() string {
+	res := fmt.Sprintf("Stack (len %v, cap %v)\n=====\n", len(s.list), cap(s.list))
+	for i := len(s.list) - 1; i >= 0; i-- {
+		res += fmt.Sprintf("%v: %v\n", i, s.list[i])
+	}
+	res += "=====\n"
+	return res
+}
+
+// PopAll get all elements and returns slice with elements
+func (s *Stack) PopAll() []interface{} {
+	res := make([]interface{}, len(s.list))
+	copy(res, s.list)
+	s.list = s.list[:0]
+	return res
+}
+
+// Забирает стек до индекса (если в стеке, например, 5 элементов, индекс = 2, из стека будет взято 3 элемента)
+func (s *Stack) PopAllIndex(index int) []interface{} {
+	res := make([]interface{}, len(s.list)-index)
+	copy(res, s.list[index:])
+	s.list = s.list[:index]
+	return res
+}
+
+// Разворачивает стек, забирает все элементы и возвращает их в срезе
+func (s *Stack) PopAllReverse() []interface{} {
+	res := make([]interface{}, len(s.list))
+	for i := 0; i < len(s.list); i++ {
+		res[i] = s.list[len(s.list)-i-1]
+	}
+	s.list = s.list[:0]
+	return res
+}
+
+// Забирает элементы из стека до индекса, разворачивает результат и возвращает его в срезе
+func (s *Stack) PopAllReverseIndex(index int) []interface{} {
+	res := make([]interface{}, len(s.list)-index)
+	for i := 0; i < len(s.list)-index; i++ {
+		res[i] = s.list[len(s.list)-i-1]
+	}
+	s.list = s.list[:index]
+	return res
+}

+ 19 - 0
containers/z_test.go

@@ -0,0 +1,19 @@
+package containers
+
+import "testing"
+
+func TestQueue(t *testing.T) {
+	var q Queue
+	q.Push("one", "two", "three")
+	q.Push("four")
+	q.Push("five")
+	t.Log(q)
+	for {
+		if val, check := q.Pop(); check {
+			t.Log(val, q.Size())
+		} else {
+			break
+		}
+	}
+	t.Log(q)
+}

+ 2 - 0
database/nosql/README.md

@@ -0,0 +1,2 @@
+# nosql
+> part of package go-tools

+ 76 - 0
database/nosql/README.ru.md

@@ -0,0 +1,76 @@
+<p align="center">
+  <span>Русский</span> |
+  <a href="README.md">English</a>
+</p>
+
+# nosql
+> Инструмент позволяет легко вызвать функцию SQL базы данных, принимающую в качестве входного параметра json объект.
+> Результирующим значением функции так же должен являться json объект. Всю работу формированию запроса, 
+> конвертацию параметров и работу с транзакциями инструмент берёт на себя, позволяя тем самым при минимальном
+> количестве кода реализовать полноценное REST API.
+
+### Базы данных
+В качестве базы данных может быть использована любая SQL база данных. Тестирование производилось на 
+<a href="https://postgrespro.ru">PostgreSQL</a>
+
+### Как использовать
+> В качестве примера будет выполнено подключение к базе данных PostgreSQL и вызвана функция подсчёта количества
+> элементов в массиве, переданном в качестве аргумента
+
+```golang
+  // В качестве базы данных используем PostgreSQL c расширение plv8 (https://plv8.github.io/)
+  // В базе данных необходимо определить функцию, которая будет подсчитывает количество элементов в массиве
+  // Аргументом функции является json объект { "input": array }, функция вернёт json объект 
+  // { "input": array, output: count }
+	//
+	// CREATE OR REPLACE FUNCTION public.arr_count(data jsonb)
+	// RETURNS jsonb AS
+	// $BODY$
+	//   if(typeof(data.input) != 'object' || !data.input.length) {
+	//	   plv8.elog(ERROR, 'Incoming data must be array')
+	//   }
+	//   data.output = data.input.length
+	//   return data
+	// $BODY$
+	// LANGUAGE plv8 IMMUTABLE STRICT
+
+	// dbConn read from config file before
+	// example dbConn string: postgres://postgres:postgres@127.0.0.1/postgres?sslmode=disable&port=5432
+
+  // Строка с параметрами подключения к базе данных
+  dbConn := postgres://postgres:postgres@127.0.0.1/postgres?sslmode=disable&port=5432
+
+	// подкоючение к базе данных
+	db, err := sql.Open("postgres", dbConn)
+	if err != nil {
+		fmt.Println(err)
+		return
+	}
+
+	// определение функции открытия транцакции.
+	openTX := func() (*sql.Tx, error) {
+		return db.Begin()
+	}
+
+	// создание объекта для вызова функций базы данных
+	api := nosql.New(openTX)
+
+	// инициализация параметров запроса к базе данных
+	data := map[string]interface{}{
+		"input": []int{1, 2, 3},
+	}
+
+	// вызов функции базы данных с параметром
+	result, err := api.Call("public.arr_count", data)
+	if err == nil {
+		fmt.Println(err)
+		return
+	}
+
+	// запрос выполнен.
+	fmt.Println(result)
+```
+
+## Лицензия
+
+The MIT License (MIT), подробнее [LICENSE](LICENSE).

+ 8 - 0
database/nosql/doc.go

@@ -0,0 +1,8 @@
+// Package nosql makes from SQL database to NoSQL.
+// Source database must support json type
+//
+// This tool allows to call database functions with input json parameter of different types - raw bytes ([]byte) and object (interface{}) and also returns raw bytes or object
+// depending of use
+//
+// Can be used with any sql database that supports json type.
+package nosql

+ 72 - 0
database/nosql/example_basic_test.go

@@ -0,0 +1,72 @@
+package nosql_test
+
+import (
+	"database/sql"
+	"fmt"
+	"io/ioutil"
+	"log"
+
+	"git.ali33.ru/fcg-xvii/go-tools/database/nosql"
+	_ "github.com/lib/pq"
+)
+
+var (
+	dbConn string
+)
+
+func init() {
+	// read postgres connection string from file z_data.config
+	connSource, _ := ioutil.ReadFile("z_data.config")
+	dbConn = string(connSource)
+	log.Println("DB connection string", dbConn)
+}
+
+func Example_basic() {
+	// As a database will use postgres and plv8 (https://plv8.github.io/)
+	// On the database side, you must create a function that counts the number of elements in the input array
+	// As input argument function accept json object { "input": array } and will return object { "input": array, output: count }
+	//
+	// CREATE OR REPLACE FUNCTION public.arr_count(data jsonb)
+	// RETURNS jsonb AS
+	// $BODY$
+	//   if(typeof(data.input) != 'object' || !data.input.length) {
+	//	   plv8.elog(ERROR, 'Incoming data must be array')
+	//   }
+	//   data.output = data.input.length
+	//   return data
+	// $BODY$
+	// LANGUAGE plv8 IMMUTABLE STRICT
+
+	// dbConn read from config file before
+	// example dbConn string: postgres://postgres:postgres@127.0.0.1/postgres?sslmode=disable&port=5432
+
+	// open the postgres database
+	db, err := sql.Open("postgres", dbConn)
+	if err != nil {
+		fmt.Println(err)
+		return
+	}
+
+	// setup open new database transaction method
+	openTX := func() (*sql.Tx, error) {
+		return db.Begin()
+	}
+
+	// create api object
+	api := nosql.New(openTX)
+
+	// setup request data
+	data := map[string]interface{}{
+		"input": []int{1, 2, 3},
+	}
+
+	// call the function on database side
+	result, err := api.Call("public.arr_count", data)
+	if err == nil {
+		fmt.Println(err)
+		return
+	}
+
+	// request completed
+	fmt.Println(result)
+}

+ 67 - 0
database/nosql/nosql.go

@@ -0,0 +1,67 @@
+package nosql
+
+import (
+	"database/sql"
+	"encoding/json"
+	"fmt"
+)
+
+// OpenTXMethod is callback functon to open transaction in NoSQL object
+type OpenTXMethod func() (*sql.Tx, error)
+
+// NoSQL object
+type NoSQL struct {
+	openMethod OpenTXMethod
+}
+
+// New is NoSQL object constructor
+func New(openMethod OpenTXMethod) *NoSQL {
+	return &NoSQL{openMethod}
+}
+
+// CallJSON accepts raw json bytes and returns result raw json bytes
+func (_self *NoSQL) CallJSON(function string, rawJSON []byte) (resRawJSON []byte, err error) {
+	// open tx
+	var tx *sql.Tx
+	if tx, err = _self.openMethod(); err == nil {
+		// execute query and scan result
+		row := tx.QueryRow(fmt.Sprintf("select * from %v($1)", function), rawJSON)
+		if err = row.Scan(&resRawJSON); err != nil {
+			tx.Rollback()
+			return
+		}
+		err = tx.Commit()
+	}
+	return
+}
+
+// CallObjParam accepts interface{} object and returns result raw json bytes
+func (_self *NoSQL) CallObjParam(function string, data interface{}) (resRawJSON []byte, err error) {
+	// convert incoming object to raw json
+	var raw []byte
+	if raw, err = json.Marshal(data); err != nil {
+		return
+	}
+	return _self.CallJSON(function, raw)
+}
+
+// Call accepts interface{} object and returns result interface{}
+func (_self *NoSQL) Call(function string, data interface{}) (res interface{}, err error) {
+	var resRawJSON []byte
+	if resRawJSON, err = _self.CallObjParam(function, data); err == nil {
+		// convert raw result data to obj
+		if len(resRawJSON) > 0 {
+			err = json.Unmarshal(resRawJSON, &res)
+		}
+	}
+	return
+}
+
+func (_self *NoSQL) CallObject(function string, data interface{}, resultObject interface{}) (err error) {
+	var resRawJSON []byte
+	if resRawJSON, err = _self.CallObjParam(function, data); err == nil {
+		// convert raw result data to result object
+		err = json.Unmarshal(resRawJSON, resultObject)
+	}
+	return
+}

+ 33 - 0
database/nosql/z_test.go

@@ -0,0 +1,33 @@
+package nosql
+
+import (
+	"database/sql"
+	"io/ioutil"
+	"testing"
+
+	_ "github.com/lib/pq"
+)
+
+var (
+	dbConn string
+)
+
+func init() {
+	// read postgres connection string from file z_data.config
+	connSource, _ := ioutil.ReadFile("z_data.config")
+	dbConn = string(connSource)
+}
+
+func TestNoSQL(t *testing.T) {
+	if db, err := sql.Open("postgres", dbConn); err == nil {
+		ex := New(func() (*sql.Tx, error) {
+			return db.Begin()
+		})
+		res, err := ex.Call("public.arr_count", map[string]interface{}{
+			"input": []int{0, 1, 2},
+		})
+		t.Log(res, err)
+	} else {
+		t.Error(err)
+	}
+}

+ 5 - 0
go.mod

@@ -0,0 +1,5 @@
+module git.ali33.ru/fcg-xvii/go-tools
+
+go 1.17
+
+require github.com/lib/pq v1.10.4 // indirect

+ 2 - 0
go.sum

@@ -0,0 +1,2 @@
+github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk=
+github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=

+ 373 - 0
json/decoder.go

@@ -0,0 +1,373 @@
+package json
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"log"
+	"reflect"
+
+	"git.ali33.ru/fcg-xvii/go-tools/containers"
+)
+
+type JSONTokenType byte
+
+const (
+	JSON_INVALID JSONTokenType = iota
+	JSON_ARRAY
+	JSON_OBJECT
+	JSON_VALUE
+)
+
+func (s JSONTokenType) String() string {
+	switch s {
+	case JSON_INVALID:
+		return "JSON_INVALID"
+	case JSON_ARRAY:
+		return "JSON_ARRAY"
+	case JSON_OBJECT:
+		return "JSON_OBJECT"
+	case JSON_VALUE:
+		return "JSON_VALUE"
+	default:
+		return "JSON_UNDEFINED"
+	}
+}
+
+func Decode(r io.Reader, obj interface{}) error {
+	dec := InitJSONDecoder(r)
+	return dec.Decode(obj)
+}
+
+func DecodeBytes(src []byte, obj interface{}) error {
+	buf := bytes.NewBuffer(src)
+	dec := InitJSONDecoder(buf)
+	return dec.Decode(obj)
+}
+
+// JSON decode interfaces
+
+type Type byte
+
+const (
+	TypeUndefined Type = iota
+	TypeInterface
+	TypeObject
+	TypeSlice
+)
+
+type JSONInterface interface {
+	JSONDecode(*JSONDecoder) (isNil bool, err error)
+}
+
+type JSONObject interface {
+	JSONField(fieldName string, storeTemp Map) (fieldPtr interface{}, err error)
+}
+
+type JSONFinisher interface {
+	JSONFinish(storeTemp Map) error
+}
+
+func (s *JSONDecoder) JSONTypeCheck(rv *reflect.Value) (t Type) {
+	// check slice
+	iface := rv.Interface()
+	if _, check := iface.(JSONInterface); check {
+		// check JSONInterface
+		t = TypeInterface
+		return
+	} else if _, check = iface.(JSONObject); check {
+		// check JSONObject
+		t = TypeObject
+		return
+	}
+	return
+}
+
+///////////////////////////////////////////////
+
+func InitJSONDecoderFromSource(src []byte) *JSONDecoder {
+	r := bytes.NewReader(src)
+	return InitJSONDecoder(r)
+}
+
+func InitJSONDecoder(r io.Reader) *JSONDecoder {
+	return &JSONDecoder{
+		Decoder:  json.NewDecoder(r),
+		embedded: containers.NewStack(0),
+	}
+}
+
+type JSONDecoder struct {
+	*json.Decoder
+	token        json.Token
+	embedded     *containers.Stack
+	current      JSONTokenType
+	objectkey    bool
+	objectClosed bool
+	parentObj    reflect.Value
+	err          error
+	counter      int
+}
+
+func (s *JSONDecoder) buf() {
+	b, _ := ioutil.ReadAll(s.Buffered())
+	log.Println(">>>", string(b))
+}
+
+func (s *JSONDecoder) IsObjectKey() bool      { return s.objectkey }
+func (s *JSONDecoder) IsObjectClosed() bool   { return s.objectClosed }
+func (s *JSONDecoder) Current() JSONTokenType { return s.current }
+func (s *JSONDecoder) EmbeddedLevel() int     { return s.embedded.Len() }
+
+func (s *JSONDecoder) Token() (t json.Token, err error) {
+	s.objectClosed = false
+	if t, err = s.Decoder.Token(); err == nil {
+		if delim, check := t.(json.Delim); check {
+			s.objectkey = false
+			switch delim {
+			case '{':
+				s.embedded.Push(JSON_OBJECT)
+				s.current = JSON_OBJECT
+			case '[':
+				s.embedded.Push(JSON_ARRAY)
+				s.current = JSON_ARRAY
+			case '}', ']':
+				s.embedded.Pop()
+				s.objectClosed, s.current = true, JSON_INVALID
+				if s.embedded.Len() > 0 {
+					s.current = s.embedded.Peek().(JSONTokenType)
+				}
+			}
+		} else {
+			if s.current == JSON_OBJECT {
+				s.objectkey = !s.objectkey
+			}
+			s.current = JSON_VALUE
+		}
+	}
+	s.token = t
+	return
+}
+
+func (s *JSONDecoder) Next() error {
+	if _, err := s.Token(); err != nil {
+		return err
+	}
+	switch s.current {
+	case JSON_ARRAY, JSON_OBJECT:
+		{
+			stackLen := s.embedded.Len()
+			for s.embedded.Len() >= stackLen {
+				if _, err := s.Token(); err != nil {
+					return err
+				}
+			}
+			return nil
+		}
+	default:
+		return nil
+	}
+}
+
+func (s *JSONDecoder) DecodeRaw(v interface{}) error {
+	return s.Decoder.Decode(v)
+}
+
+func (s *JSONDecoder) Decode(v interface{}) (err error) {
+	rv := reflect.ValueOf(v)
+	err = s.decodeReflect(&rv)
+	return
+}
+
+func (s *JSONDecoder) decodeReflect(rv *reflect.Value) (err error) {
+	switch s.JSONTypeCheck(rv) {
+	case TypeInterface:
+		{
+			if rv.Kind() == reflect.Ptr && rv.IsNil() {
+				rv.Set(reflect.New(rv.Type().Elem()))
+			}
+			var isNil bool
+			if isNil, err = rv.Interface().(JSONInterface).JSONDecode(s); err == nil {
+				if isNil && rv.CanAddr() {
+					rv.Set(reflect.Zero(rv.Type()))
+				}
+			}
+			return
+		}
+	case TypeObject:
+		return s.decodeJSONObject(rv)
+	case TypeSlice:
+		return s.decodeSlice(rv)
+	default:
+		{
+			if rv.Kind() == reflect.Ptr {
+				ev := rv.Elem()
+				if !ev.IsValid() {
+					ev = reflect.Indirect(reflect.New(rv.Type()))
+					ev.Set(reflect.New(ev.Type().Elem()))
+					if err = s.decodeReflect(&ev); err == nil && !ev.IsNil() {
+						rv.Set(ev)
+					}
+					return
+				} else {
+					if ev.Kind() == reflect.Ptr {
+						return s.decodeReflect(&ev)
+					} else if ev.Kind() == reflect.Slice {
+						return s.decodeSlice(&ev)
+					} else if ev.Kind() == reflect.Struct {
+						return s.decodeRawObject(rv)
+					}
+				}
+			}
+			return s.Decoder.Decode(rv.Interface())
+		}
+	}
+}
+
+func fieldConvert(v reflect.Value, t reflect.Type, fieldName string) (val reflect.Value, err error) {
+	defer func() {
+		if r := recover(); r != nil {
+			err = fmt.Errorf("%v :: %v", fieldName, r.(string))
+		}
+	}()
+	val = v.Convert(t)
+	return
+}
+
+func (s *JSONDecoder) decodeRawObject(rv *reflect.Value) (err error) {
+	var t json.Token
+	// chek first token is object
+	if t, err = s.Token(); err != nil {
+		return
+	}
+	if s.current != JSON_OBJECT {
+		// check null token
+		if t == nil {
+			if rv.Kind() == reflect.Ptr && !rv.IsNil() {
+				rv.Set(reflect.Zero(rv.Type()))
+			}
+			return
+		}
+		// token is not object
+		return fmt.Errorf("EXPCTED OBJECT, NOT %T", t)
+	}
+	if rv.Kind() == reflect.Ptr {
+		if rv.IsNil() {
+			rv.Set(reflect.New(rv.Type().Elem()))
+		}
+		elem := rv.Elem()
+		rv = &elem
+	}
+	el := s.EmbeddedLevel()
+	for el <= s.EmbeddedLevel() {
+		if t, err = s.Token(); err != nil {
+			return
+		}
+		if s.Current() == JSON_VALUE && s.IsObjectKey() {
+			s.buf()
+			if f := rv.FieldByName(t.(string)); f.IsValid() {
+				rrv := reflect.New(f.Type())
+				if err = s.decodeReflect(&rrv); err != nil {
+					return
+				}
+				if !rrv.IsNil() {
+					f.Set(rrv.Elem())
+				}
+			} else {
+				var i interface{}
+				s.Decoder.Decode(&i)
+			}
+		}
+	}
+	return
+}
+
+// slice
+func (s *JSONDecoder) decodeSlice(rv *reflect.Value) (err error) {
+	var t json.Token
+	if t, err = s.Token(); err != nil {
+		return
+	}
+	if s.current != JSON_ARRAY {
+		if t == nil {
+			if !rv.IsNil() {
+				rv.Set(reflect.Zero(rv.Type()))
+			}
+			return
+		}
+		return fmt.Errorf("EXPCTED SLICE, NOT %T", t)
+	}
+	// check slice is nil
+	if rv.IsNil() {
+		rv.Set(reflect.MakeSlice(rv.Type(), 0, 0))
+	}
+	elemType := reflect.TypeOf(rv.Interface()).Elem()
+	for s.More() {
+		em := reflect.New(elemType)
+		if err = s.decodeReflect(&em); err != nil {
+			return
+		}
+		rv.Set(reflect.Append(*rv, em.Elem()))
+	}
+	if _, err = s.Token(); err != nil {
+		return
+	}
+	if d, check := s.token.(json.Delim); !check || d != ']' {
+		return fmt.Errorf("JSON PARSE ERROR :: EXPECTED ']', NOT %v", d)
+	}
+	return
+}
+
+////////////////////////////////////////
+
+func (s *JSONDecoder) decodeJSONObject(rv *reflect.Value) (err error) {
+	var t json.Token
+	// chek first token is object
+	if t, err = s.Token(); err != nil {
+		return
+	}
+	if s.current != JSON_OBJECT {
+		// check null token
+		if t == nil {
+			if rv.CanAddr() && !rv.IsNil() {
+				rv.Set(reflect.Zero(rv.Type()))
+			}
+			return
+		}
+		// token is not object
+		return fmt.Errorf("EXPCTED OBJECT, NOT %T", t)
+	}
+	// check null pounter in source object
+	if rv.Kind() == reflect.Ptr && rv.IsNil() {
+		// create new object
+		rv.Set(reflect.New(rv.Type().Elem()))
+	}
+	el, obj, store := s.EmbeddedLevel(), rv.Interface().(JSONObject), NewMap()
+	var fieldPtr interface{}
+	for el <= s.EmbeddedLevel() {
+		if t, err = s.Token(); err != nil {
+			return
+		}
+		if s.Current() == JSON_VALUE && s.IsObjectKey() {
+			if fieldPtr, err = obj.JSONField(t.(string), store); err != nil {
+				return
+			}
+			if fieldPtr != nil {
+				rv := reflect.ValueOf(fieldPtr)
+				if err = s.decodeReflect(&rv); err != nil {
+					return
+				}
+			} else {
+				if err = s.Next(); err != nil {
+					return
+				}
+			}
+		}
+	}
+	if finisher, check := rv.Interface().(JSONFinisher); check {
+		err = finisher.JSONFinish(store)
+	}
+	return
+}

+ 39 - 0
json/json.go

@@ -0,0 +1,39 @@
+package json
+
+import (
+	"encoding/json"
+	"io"
+	"log"
+	"os"
+)
+
+func Log(v interface{}) {
+	str, _ := json.MarshalIndent(v, "*", "\t")
+	log.Println(string(str))
+}
+
+func Marshal(v interface{}) ([]byte, error) {
+	return json.Marshal(v)
+}
+
+func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) {
+	return json.MarshalIndent(v, prefix, indent)
+}
+
+func Unmarshal(data []byte, v interface{}) error {
+	return json.Unmarshal(data, v)
+}
+
+func UnmarshalReader(r io.Reader, v interface{}) error {
+	return Decode(r, v)
+}
+
+func UnmarshalFile(fileName string, v interface{}) (err error) {
+	var f *os.File
+	if f, err = os.Open(fileName); err != nil {
+		return
+	}
+	err = Decode(f, v)
+	f.Close()
+	return
+}

+ 256 - 0
json/map.go

@@ -0,0 +1,256 @@
+package json
+
+import (
+	"encoding/json"
+	"fmt"
+	"reflect"
+	"strconv"
+)
+
+// Map type
+type Map map[string]interface{}
+
+// New init Map object
+func NewMap() Map {
+	return make(Map)
+}
+
+// FromInterface convert map[string]interface{} or Map interface to Map
+func MapFromInterface(iface interface{}) (res Map) {
+	switch val := iface.(type) {
+	case map[string]interface{}:
+		res = FromMap(val)
+	case Map:
+		res = val
+	default:
+		res = NewMap()
+	}
+	return
+}
+
+// FromMap convert map[string]interface{} to Map object
+func FromMap(m map[string]interface{}) Map {
+	return Map(m)
+}
+
+// Bool returns bool value by key
+func (s Map) Bool(key string, defaultVal bool) bool {
+	if res, check := s[key].(bool); check {
+		return res
+	}
+	return defaultVal
+}
+
+// KeyExists check value exists by key
+func (s Map) KeyExists(key string) bool {
+	_, check := s[key]
+	return check
+}
+
+// KeysExists check values exists by keys list.
+// Returns the first blank key found.
+// If all keys are defined, an empty string will be returned
+func (s Map) KeysExists(keys []string) string {
+	for _, key := range keys {
+		if _, check := s[key]; !check {
+			return key
+		}
+	}
+	return ""
+}
+
+func val(l, r interface{}) (res reflect.Value) {
+	lVal, rVal := reflect.ValueOf(l), reflect.ValueOf(r)
+	if lVal.Kind() == reflect.Ptr && rVal.Kind() != reflect.Ptr {
+		return val(lVal.Elem().Interface(), r)
+	}
+	defer func() {
+		if r := recover(); r != nil {
+			res = rVal
+		}
+	}()
+	res = lVal.Convert(rVal.Type())
+	return
+}
+
+// Int returns int64 value by key.
+// If key isn't defined will be returned defaultVal arg value
+func (s Map) Int(key string, defaultVal int64) int64 {
+	if iface, check := s[key]; check {
+		return val(iface, defaultVal).Int()
+	}
+	return defaultVal
+}
+
+func (s Map) Int32(key string, defaultVal int) int {
+	if iface, check := s[key]; check {
+		return val(iface, defaultVal).Interface().(int)
+	}
+	return defaultVal
+}
+
+// Value returns interface object with attempt to convert to defaultVal type.
+// If key isn't defined will be returned defaultVal arg value
+func (s Map) Value(key string, defaultVal interface{}) interface{} {
+
+	// check value exists by key
+	if iface, check := s[key]; check {
+
+		// check defaultVal is valid
+		dVal := reflect.ValueOf(defaultVal)
+		if !dVal.IsValid() {
+			// invalid, return arrived interface
+			return iface
+		}
+		// defaultVal is valid, attempt to convert found value to defaultVal type
+		lVal := reflect.ValueOf(iface)
+
+		switch {
+		case !lVal.IsValid():
+			return defaultVal // invalid found value, return defaultVal
+		case lVal.Kind() == dVal.Kind():
+			return iface // types of found value and defaultVal is match. return found value
+		case lVal.Type().ConvertibleTo(dVal.Type()):
+			return lVal.Convert(dVal.Type()).Interface() // found value type can be converted to defaultVal type. return converted found value
+		default:
+			{
+				// found value type can't be converted to defaultVal type. If found value is string, attempt to convert to number value
+				if lVal.Kind() == reflect.String {
+					switch dVal.Kind() {
+					case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+						val, err := strconv.Atoi(lVal.String())
+						if err != nil {
+							return defaultVal
+						}
+						lVal = reflect.ValueOf(val)
+						return lVal.Convert(dVal.Type())
+					case reflect.Float32, reflect.Float64:
+						val, err := strconv.ParseFloat(lVal.String(), 64)
+						if err != nil {
+							return defaultVal
+						}
+						lVal = reflect.ValueOf(val)
+						return lVal.Convert(dVal.Type())
+					}
+				}
+			}
+		}
+	}
+	return defaultVal
+}
+
+// ValueJSON returns json source object by key
+// If key isn't defined will be returned defaultVal arg value
+func (s Map) ValueJSON(key string, defaultVal []byte) (res []byte) {
+	res = defaultVal
+	if s.KeyExists(key) {
+		if r, err := json.Marshal(s[key]); err == nil {
+			res = r
+		}
+	}
+	return
+}
+
+// String returns string value by key
+// If key isn't defined will be returned defaultVal arg value
+func (s Map) String(key, defaultVal string) string {
+	if iface, check := s[key]; check {
+		return val(iface, defaultVal).String()
+		//return fmt.Sprint(iface)
+	}
+	return defaultVal
+}
+
+// Slce returns slice of interface{} by key
+// If key isn't defined or have a different type will be returned defaultVal arg value
+func (s Map) Slice(key string, defaultVal []interface{}) (res []interface{}) {
+	if arr, check := s[key].([]interface{}); check {
+		res = arr
+	} else {
+		res = defaultVal
+	}
+	return
+}
+
+// StringSlice returns string slice by key
+// If key isn't defined will be returned defaultVal arg value
+func (s Map) StringSlice(key string, defaultVal []string) (res []string) {
+	if arr, check := s[key].([]interface{}); check {
+		res = make([]string, len(arr))
+		for i, v := range arr {
+			res[i] = fmt.Sprint(v)
+		}
+	} else {
+		res = defaultVal
+	}
+	return
+}
+
+// Map returns Map object by key
+// If key isn't defined or have other type will be returned defaultVal arg value
+func (s Map) Map(key string, defaultVal Map) (res Map) {
+	val := s[key]
+	switch iface := val.(type) {
+	case Map:
+		res = iface
+	case map[string]interface{}:
+		res = Map(iface)
+	default:
+		res = defaultVal
+	}
+	return
+}
+
+// JSON Return JSON source of the self object
+func (s Map) JSON() (res []byte) {
+	res, _ = json.Marshal(s)
+	return
+}
+
+// ToMap returns map[string]interface{} of the self object
+func (s Map) ToMap() map[string]interface{} { return map[string]interface{}(s) }
+
+// Copy returns copied map (todo dipcopy)
+func (s Map) Copy() (res Map) {
+	res = make(Map)
+	for key, val := range s {
+		res[key] = val
+	}
+	return
+}
+
+func (s Map) IsEmpty() bool {
+	return len(s) == 0
+}
+
+func (s Map) Update(m Map) {
+	for key, val := range m {
+		s[key] = val
+	}
+}
+
+func (s Map) StorePtr(key string, val interface{}) interface{} {
+	s[key] = &val
+	return &val
+}
+
+func (s Map) Variable(key string, ptr interface{}) bool {
+	val, check := s[key]
+	if !check {
+		// value is not exists
+		return false
+	}
+	vVal := reflect.ValueOf(ptr)
+	if vVal.Kind() != reflect.Ptr {
+		// ptr is not a pointer
+		return false
+	}
+	vElem := vVal.Elem()
+	rVal := reflect.ValueOf(val)
+	if !rVal.CanConvert(vElem.Type()) {
+		// type is not converted
+		return false
+	}
+	vElem.Set(rVal.Convert(vElem.Type()))
+	return true
+}

+ 13 - 0
json/test_array.json

@@ -0,0 +1,13 @@
+[ 
+    {
+        "id": 1,
+        "name": "Test JSON object",
+        "intervals": [ [ 1, 2 ], [ 3, 4 ] ]
+    },
+    {
+        "id": 100,
+        "name": "Test JSON object",
+        "intervals": [ [ 100, 200 ] ]
+    },
+    null
+]

+ 6 - 0
json/test_object.json

@@ -0,0 +1,6 @@
+{
+    "id": 1,
+    "name": "Test JSON object",
+    "intervals": [ [ 11, 22 ], [ 33, 44 ] ],
+    "embedded": null
+}

+ 195 - 0
json/z_test.go

@@ -0,0 +1,195 @@
+package json
+
+import (
+	"errors"
+	"fmt"
+	"log"
+	"os"
+	"testing"
+)
+
+func TestJSON(t *testing.T) {
+	m := Map{
+		"jsrc": []int{1, 2, 3, 4},
+		"kate": nil,
+		"m1":   Map{"one": 1},
+		"m2":   map[string]interface{}{"one": 2},
+	}
+	t.Log(m, string(m.JSON()))
+	t.Log(string(m.ValueJSON("jsrc", []byte{})))
+	t.Log(string(m.ValueJSON("jsrc1", []byte("{ }"))))
+	t.Log(m.Map("m1", Map{}))
+	t.Log(m.Map("m2", Map{}))
+}
+
+func TestInterface(t *testing.T) {
+	m := MapFromInterface(Map{"one": 1})
+	log.Println(m)
+}
+
+type TimeInterval struct {
+	Start  int
+	Finish int
+}
+
+func (s *TimeInterval) JSONDecode(dec *JSONDecoder) (err error) {
+	var sl []int
+	if err = dec.Decode(&sl); err == nil {
+		if len(sl) != 2 {
+			return errors.New("TimeInterval: expected 2 int values")
+		}
+		s.Start, s.Finish = sl[0], sl[1]
+	}
+	return
+}
+
+type TimeIntervals []TimeInterval
+
+func (s *TimeIntervals) JSONDecode(dec *JSONDecoder) (isNil bool, err error) {
+	var sl [][]int
+	if err = dec.Decode(&sl); err == nil && sl != nil {
+		*s = make(TimeIntervals, 0, len(sl))
+		for i, interval := range sl {
+			if len(interval) != 2 {
+				return true, fmt.Errorf("Index %v - expected 2 elements list", i)
+			}
+			*s = append(*s, TimeInterval{
+				Start:  interval[0],
+				Finish: interval[1],
+			})
+		}
+	}
+	isNil = sl == nil
+	return
+}
+
+type NestedObject struct {
+	ID        int
+	Name      string
+	Embedded  *TObject
+	Intervals TimeIntervals
+}
+
+type TObject struct {
+	ID        int
+	Name      string
+	Embedded  *NestedObject
+	Intervals TimeIntervals
+}
+
+func (s *TObject) JSONField(fieldName string, storeTemp Map) (ptr interface{}, err error) {
+	switch fieldName {
+	case "id":
+		ptr = &s.ID
+	case "name":
+		storeTemp["idd"] = &s.Name
+		ptr = &s.Name
+	case "embedded":
+		ptr = &s.Embedded
+	case "intervals":
+		ptr = &s.Intervals
+	}
+	return
+}
+
+func (s *TObject) JSONFinish(storeTemp Map) error {
+	log.Println("FINISH...", storeTemp.String("idd", "!"))
+	return nil
+}
+
+func TestDecoder(t *testing.T) {
+	/*
+		//src := []byte("[ [ 1, 2 ], [ 3, 4 ] ]")
+		src := []byte("null")
+		var sl *TimeIntervals
+		err := DecodeBytes(src, &sl)
+		log.Println(sl, err)
+	*/
+	// object
+	fObj, err := os.Open("test_object.json")
+	if err != nil {
+		t.Error(err)
+	}
+
+	var obj TObject
+	if err := Decode(fObj, &obj); err != nil {
+		t.Error(err)
+	}
+	fObj.Close()
+	log.Println("OBJ", obj, err, obj.Embedded, obj.Intervals)
+	//log.Println("OBJ", obj, obj.embedded, obj.intervals)
+	// slice
+	fObj, err = os.Open("test_array.json")
+	if err != nil {
+		t.Error(err)
+	}
+
+	var arr []*TObject
+	if err := Decode(fObj, &arr); err != nil {
+		t.Error(err)
+	}
+	fObj.Close()
+	log.Println("ARR", arr)
+	for _, v := range arr {
+		log.Println(v)
+	}
+}
+
+/////////////////////////////////////////////////////////////////// project
+
+type LeadVerifySettings struct {
+	BotEnabled     bool
+	ManagerEnabled bool
+	CallDecryption bool
+	//CallLeadIntervals          []*TimeInterval
+	CallsMaxCount int
+	FirstDelay    int
+	//QualityCriteries           []string
+	RecallMinPeriod            int
+	ProductDescriptionFilename string
+	ScriptFilename             string
+}
+
+type Project struct {
+	ID                 int64
+	Name               string
+	LeadVerifySettings *LeadVerifySettings
+}
+
+func (s *Project) JSONField(fieldName string, storeTemp Map) (ptr interface{}, err error) {
+	switch fieldName {
+	case "id":
+		ptr = &s.ID
+	case "name":
+		ptr = &s.Name
+	case "lead_verify_settings":
+		ptr = &s.LeadVerifySettings
+	}
+	return
+}
+
+func TestProject(t *testing.T) {
+	src := `{"id": 1536, "name": "Первый проект 2", "lead_verify_settings": {"bot_enabled": false, "first_delay": 81000, "call_decryption": true, "calls_max_count": 100, "manager_enabled": false, "script_filename": null, "quality_criteries": ["Ок (качественный лид)", "номер не существует", "номер не отвечает/заблокирован/сбрасывает/молчание в трубке ХХХ дней", "номер заблокирован", "не автор заявки", "не наше ГЕО", "не заинтересован в продукте (у клиента другой вопрос)", "нет 18 лет", "несоответствие языка", "неразборчивая речь"], "recall_min_period": 9600, "call_lead_day_intervals": [[3600, 7200]], "product_description_filename": null}}`
+	log.Println(string(src))
+	var proj Project
+	err := DecodeBytes([]byte(src), &proj)
+	log.Println(proj, err)
+}
+
+func TestMap(t *testing.T) {
+	m := Map{
+		"one": float64(10.2),
+	}
+	m.StorePtr("two", "Coooool")
+	log.Println(m.Int32("one", 0), m.String("two", "!"))
+}
+
+func TestMapVariable(t *testing.T) {
+	m := Map{
+		"one": int64(10),
+		"two": "okko",
+	}
+	val := 10.05
+	check := m.Variable("one", &val)
+	t.Log(check, val)
+}

+ 50 - 0
sip/asterisk/ami/action.go

@@ -0,0 +1,50 @@
+package ami
+
+import (
+	"bytes"
+	"fmt"
+)
+
+type ActionData map[string]string
+
+func (s ActionData) raw() (res []byte) {
+	for key, val := range s {
+		res = append(res, []byte(fmt.Sprintf("%v: %v\r\n", key, val))...)
+	}
+	res = append(res, []byte("\r\n")...)
+	return
+}
+
+func (s ActionData) isEvent() bool {
+	_, check := s["Event"]
+	return check
+}
+
+func (s ActionData) ActionID() string {
+	return s["ActionID"]
+}
+
+func actionDataFromRaw(src []byte) (res ActionData) {
+	res, lines := make(ActionData), bytes.Split(src, []byte("\r\n"))
+	/// todo...
+	for _, line := range lines {
+		parts := bytes.SplitN(line, []byte(":"), 2)
+		if len(parts) == 2 {
+			res[string(bytes.TrimSpace(parts[0]))] = string(bytes.TrimSpace(parts[1]))
+		}
+	}
+	return
+}
+
+func actionsFromRaw(src []byte, accept func(ActionData)) (res []byte) {
+	if bytes.Index(src, []byte("\r\n\r\n")) < 0 {
+		return src
+	}
+	actionsRaw := bytes.Split(src, []byte("\r\n\r\n"))
+	for i := 0; i < len(actionsRaw)-1; i++ {
+		action := actionDataFromRaw(actionsRaw[i])
+		accept(action)
+	}
+	res = actionsRaw[len(actionsRaw)-1]
+	return
+}

+ 413 - 0
sip/asterisk/ami/client.go

@@ -0,0 +1,413 @@
+package ami
+
+import (
+	"container/list"
+	"context"
+	"errors"
+	"fmt"
+	"log"
+	"net"
+	"runtime"
+	"sync"
+	"time"
+)
+
+type State byte
+
+const (
+	StateStopped State = iota
+	StateConnection
+	StateConnected
+	StateAuth
+	StateAvailable
+	StateBusy
+)
+
+func (s State) String() string {
+	switch s {
+	case StateStopped:
+		return "Stopped"
+	case StateConnection:
+		return "Connection"
+	case StateConnected:
+		return "Connected"
+	case StateAuth:
+		return "Auth"
+	case StateAvailable:
+		return "Available"
+	case StateBusy:
+		return "Busy"
+	default:
+		return ""
+	}
+}
+
+func New(host, login, password string, ctxGlobal context.Context, stateChanged func(State, error)) (cl *Client) {
+	cl = &Client{
+		&client{
+			host:           host,
+			login:          login,
+			password:       password,
+			stateChanged:   stateChanged,
+			state:          StateStopped,
+			request:        make(chan Request),
+			response:       make(chan Response),
+			event:          make(chan Event),
+			requestsWork:   list.New(),
+			socketClosed:   make(chan error),
+			actionIDPrefix: fmt.Sprint(time.Now().UnixNano()),
+			eventListeners: make(map[int64]*EventListener),
+			locker:         new(sync.RWMutex),
+		},
+	}
+	if ctxGlobal == nil {
+		cl.ctx, cl.ctxCancel = context.WithCancel(context.Background())
+	} else {
+		cl.ctx, cl.ctxCancel = context.WithCancel(ctxGlobal)
+	}
+	go cl.eventListenersCleaner()
+	runtime.SetFinalizer(cl, destroyClient)
+	return
+}
+
+// Client object
+type Client struct {
+	*client
+}
+
+type client struct {
+	ctx             context.Context
+	ctxCancel       context.CancelFunc
+	host            string
+	login           string
+	password        string
+	conn            net.Conn
+	request         chan Request
+	response        chan Response
+	event           chan Event
+	clientSideEvent chan Event
+	stateChanged    func(State, error)
+	state           State
+	requestsWork    *list.List
+	socketClosed    chan error
+	actionIDPrefix  string
+	actionUUID      uint64
+	eventListeners  map[int64]*EventListener
+	locker          *sync.RWMutex
+}
+
+func (s *client) State() State {
+	return s.state
+}
+
+func (s *client) removeEventListener(uuid int64) {
+	s.locker.Lock()
+	if listener, check := s.eventListeners[uuid]; check {
+		listener.close()
+		delete(s.eventListeners, uuid)
+	}
+	s.locker.Unlock()
+}
+
+func (s *client) eventListenersCleaner() {
+	ctx, _ := context.WithCancel(s.ctx)
+	for {
+		select {
+		case <-time.After(time.Minute * 30):
+			{
+				now := time.Now()
+				s.locker.Lock()
+				for uuid, v := range s.eventListeners {
+					if now.After(v.timeActual) {
+						v.close()
+						delete(s.eventListeners, uuid)
+					}
+				}
+				s.locker.Unlock()
+			}
+		case <-ctx.Done():
+			{
+				for _, v := range s.eventListeners {
+					v.close()
+				}
+				return
+			}
+		}
+	}
+}
+
+func (s *client) registerEventListener(uuid int64) <-chan Event {
+	listener := &EventListener{
+		uuid:      uuid,
+		eventChan: make(chan Event),
+	}
+	s.locker.Lock()
+	s.eventListeners[uuid] = listener
+	s.locker.Unlock()
+	return listener.eventChan
+}
+
+func (s *client) initActionID() (res string) {
+	res = fmt.Sprintf("%v%v", s.actionIDPrefix, s.actionUUID)
+	if s.actionUUID < max_client_uuid {
+		s.actionUUID++
+	} else {
+		s.actionUUID = 0
+	}
+	return
+}
+
+func (s *client) requestByActionID(actionID string) (req Request, elem *list.Element, check bool) {
+	for elem = s.requestsWork.Front(); elem != nil; elem = elem.Next() {
+		req = elem.Value.(Request)
+		if req.ActionID() == actionID {
+			check = true
+			return
+		}
+	}
+	return
+}
+
+func (s *client) setState(state State, err error) {
+	oldState := s.state
+	s.state = state
+	if s.stateChanged != nil && (state != oldState || err != nil) {
+		s.stateChanged(state, err)
+	}
+	s.state = state
+}
+
+func (s *client) Event() chan Event {
+	if s.clientSideEvent == nil {
+		s.clientSideEvent = make(chan Event)
+	}
+	return s.clientSideEvent
+}
+
+func (s *client) eventAccepted(event Event) {
+	switch event.Name() {
+	case "FullyBooted":
+		{
+			if s.state == StateAuth {
+				for elem := s.requestsWork.Front(); elem != nil; elem.Next() {
+					s.sendQueueRequest()
+				}
+			}
+		}
+	}
+
+	// send event to client side
+	if s.clientSideEvent != nil {
+		s.clientSideEvent <- event
+	}
+
+	if event.uuid > 0 {
+		var check bool
+		var listener *EventListener
+		s.locker.RLock()
+		if listener, check = s.eventListeners[event.uuid]; check {
+			listener.incomingEvent(event)
+		}
+		s.locker.RUnlock()
+		if check && event.Name() == "Hangup" {
+			s.locker.Lock()
+			listener.close()
+			delete(s.eventListeners, event.uuid)
+			s.locker.Unlock()
+		}
+	}
+}
+
+// start open connection to asterisk ami server
+func (s *client) Start() {
+
+	var err error
+
+	// check state. StateStopped needed
+	if s.state != StateStopped {
+		err = errors.New("AMI start error: client already started")
+		s.stateChanged(s.state, err)
+		return
+	}
+
+	defer func() {
+		s.setState(StateStopped, err)
+	}()
+
+	s.setState(StateConnection, nil)
+
+	// connection and read ami greetings message
+	if s.conn, err = net.Dial("tcp", s.host); err != nil {
+		err = fmt.Errorf("AMI connection socket connection error: %v", err.Error())
+		return
+	}
+	s.setState(StateConnected, nil)
+
+	// socket connected. receive greetings text
+	if _, err = s.receiveSingle(); err != nil {
+		err = fmt.Errorf("AMI greetings receive error: %v", err.Error())
+		return
+	}
+
+	// greetings received, make attempt to auth
+	auth := InitRequest("Login")
+	auth.SetParam("UserName", s.login)
+	auth.SetParam("Secret", s.password)
+
+	actionCallback := func(action ActionData) {
+		if action.isEvent() {
+			s.eventAccepted(Event{action, 0})
+		} else {
+			response := Response{action}
+			if !response.IsError() {
+				s.setState(StateAuth, nil)
+			} else {
+				err = fmt.Errorf("AMI authentication error: %v", action["Message"])
+				return
+			}
+		}
+	}
+
+	if socketErr := s.sendSingleRequest(auth, actionCallback); socketErr != nil || err != nil {
+		if err == nil {
+			err = socketErr
+		}
+		return
+	}
+
+	go s.receiveLoop()
+
+loop:
+	for {
+		select {
+		case request := <-s.request:
+			{
+				actionID := s.initActionID()
+				request.ActionData["ActionID"] = actionID
+				s.requestsWork.PushFront(request)
+				if s.state == StateAuth {
+					if err := s.sendRequest(request); err != nil {
+						log.Println("SendRequestERROR")
+					}
+				}
+			}
+		case event := <-s.event:
+			s.eventAccepted(event)
+		case response := <-s.response:
+			{
+				if req, elem, check := s.requestByActionID(response.ActionID()); check {
+					req.chanResponse <- response
+					close(req.chanResponse)
+					s.requestsWork.Remove(elem)
+				}
+			}
+		case err = <-s.socketClosed:
+			break loop
+		}
+	}
+
+	return
+}
+
+func (s *client) sendQueueRequest() error {
+	for elem := s.requestsWork.Front(); elem != nil; elem.Next() {
+		req := elem.Value.(Request)
+		if req.sended {
+			s.requestsWork.Remove(elem)
+		} else if err := s.sendRequest(req); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func (s *client) receiveSingle() (data []byte, err error) {
+	count, buf := 0, make([]byte, 1024)
+	if count, err = s.conn.Read(buf); err == nil {
+		data = buf[:count]
+	}
+	return
+}
+
+func (s *client) sendSingleRequest(request Request, acceptCallback func(ActionData)) (err error) {
+	// send action
+	if err = s.sendRequest(request); err != nil {
+		return
+	}
+
+	// receive answer
+	var data []byte
+	for {
+		count, buf := 0, make([]byte, 1024)
+		if count, err = s.conn.Read(buf); err != nil {
+			return
+		}
+		if data = actionsFromRaw(append(data, buf[:count]...), acceptCallback); len(data) == 0 {
+			return
+		}
+	}
+}
+
+func (s *client) sendRequest(req Request) (err error) {
+	if _, err := s.conn.Write(req.raw()); err != nil {
+		err = fmt.Errorf("AMI socket send data error: %v", err.Error())
+	} else {
+		req.sended = true
+	}
+	return
+}
+
+func (s *client) receiveLoop() {
+	var (
+		data  []byte
+		count int
+		err   error
+	)
+	buf := make([]byte, 1024)
+	for {
+		if count, err = s.conn.Read(buf); err != nil {
+			err = fmt.Errorf("AMI socket receive data error: %v", err.Error())
+			s.socketClosed <- err
+			return
+		}
+		data = actionsFromRaw(
+			append(data, buf[:count]...),
+			func(action ActionData) {
+				if action.isEvent() {
+					s.event <- initEvent(action)
+				} else {
+					s.response <- Response{action}
+				}
+			},
+		)
+	}
+}
+
+func (s *client) Request(req Request, timeout time.Duration) (resp Response, accepted bool) {
+	req.chanResponse = make(chan Response)
+	s.request <- req
+	if timeout == 0 {
+		resp, accepted = <-req.chanResponse
+	} else {
+		select {
+		case resp, accepted = <-req.chanResponse:
+			accepted = true
+		case <-time.After(timeout):
+			break
+		}
+	}
+	return
+}
+
+// Close finish work with client
+func (s *client) Close() {
+	if s.state > StateStopped {
+		s.conn.Close()
+	}
+	s.ctxCancel()
+}
+
+// destructor for finalizer
+func destroyClient(cl *Client) {
+	cl.Close()
+}

+ 11 - 0
sip/asterisk/ami/conf.go

@@ -0,0 +1,11 @@
+package ami
+
+import "time"
+
+const (
+	max_client_uuid = ^uint64(0)
+)
+
+var (
+	RequestTimeoutDefault = time.Second * 20
+)

+ 26 - 0
sip/asterisk/ami/event.go

@@ -0,0 +1,26 @@
+package ami
+
+import "strconv"
+
+func initEvent(data ActionData) Event {
+	res := Event{
+		ActionData: data,
+	}
+	if src, check := data["Uniqueid"]; check {
+		res.uuid, _ = strconv.ParseInt(src, 10, 64)
+	}
+	return res
+}
+
+type Event struct {
+	ActionData
+	uuid int64
+}
+
+func (s Event) Name() string {
+	return s.ActionData["Event"]
+}
+
+func (s Event) UUID() int64 {
+	return s.uuid
+}

+ 18 - 0
sip/asterisk/ami/event_listener.go

@@ -0,0 +1,18 @@
+package ami
+
+import "time"
+
+type EventListener struct {
+	uuid       int64
+	eventChan  chan Event
+	timeActual time.Time
+}
+
+func (s *EventListener) incomingEvent(e Event) {
+	s.timeActual = time.Now().Add(time.Minute)
+	s.eventChan <- e
+}
+
+func (s *EventListener) close() {
+	close(s.eventChan)
+}

+ 141 - 0
sip/asterisk/ami/originate.go

@@ -0,0 +1,141 @@
+package ami
+
+import (
+	"errors"
+	"fmt"
+	"log"
+	"strconv"
+	"sync"
+	"time"
+
+	"git.ali33.ru/fcg-xvii/go-tools/json"
+)
+
+func (s *Client) Originate(req *OriginateRequest) (*Originate, error) {
+	if s.state != StateAuth {
+		return nil, errors.New("AMI IS NOT AUTH")
+	}
+	req.uuid = time.Now().UnixNano()
+	timeout := RequestTimeoutDefault
+	if req.Timeout > timeout {
+		timeout = req.Timeout + time.Millisecond*500
+	}
+
+	resp, check := s.Request(req.Request(), timeout)
+	if !check {
+		return nil, errors.New("Originate request timeout")
+	}
+	if resp.IsError() {
+		return nil, fmt.Errorf("Originate error: %v", resp.ErrorMessage())
+	}
+
+	res := initOriginate(req, s)
+
+	return res, nil
+}
+
+//////////////////////////////////////////////////////////////////
+
+type OriginateRequest struct {
+	Channel     string
+	Context     string
+	Exten       string
+	Priority    string
+	Timeout     time.Duration
+	CallerID    string
+	Variable    json.Map
+	Account     string
+	Application string
+	Data        string
+	uuid        int64
+}
+
+func (s *OriginateRequest) Request() (res Request) {
+	res = InitRequest("Originate")
+	res.SetParam("Channel", s.Channel)
+	res.SetParam("Context", s.Context)
+	res.SetParam("Exten", s.Exten)
+	if s.Timeout > 0 {
+		res.SetParam("Timeout", fmt.Sprintf("%v", int(s.Timeout/time.Millisecond)))
+	}
+	res.SetParam("Priority", s.Priority)
+	res.SetParam("CallerID", s.CallerID)
+	res.SetParam("Account", s.Account)
+	res.SetParam("Application", s.Application)
+	res.SetParam("Data", s.Data)
+	res.SetParam("Async", "true")
+	res.SetParam("ChannelID", fmt.Sprint(s.uuid))
+	res.SetVariables(s.Variable)
+	return res
+}
+
+/////////////////////////////////////////////////////////////////
+
+func initOriginate(req *OriginateRequest, client *Client) *Originate {
+	res := &Originate{
+		OriginateRequest: req,
+		eventChan:        client.registerEventListener(req.uuid),
+		locker:           new(sync.RWMutex),
+		client:           client,
+	}
+	go res.listenEvents()
+	return res
+}
+
+type Originate struct {
+	*OriginateRequest
+	eventChan      <-chan Event
+	userEventChan  chan Event
+	locker         *sync.RWMutex
+	finished       bool
+	err            error
+	client         *Client
+	responseReason byte
+	hangupCause    byte
+}
+
+func (s *Originate) listenEvents() {
+	for {
+		e, ok := <-s.eventChan
+		if !ok {
+			s.finished = true
+			close(s.userEventChan)
+			return
+		}
+		s.locker.RLock()
+		if s.userEventChan != nil {
+			s.userEventChan <- e
+		}
+		s.locker.RUnlock()
+		switch e.Name() {
+		case "OriginateResponse":
+			if reason, check := e.ActionData["Reason"]; check {
+				reasonVal, _ := strconv.ParseInt(reason, 10, 32)
+				s.responseReason = byte(reasonVal)
+			}
+			log.Println("RREASON", s.responseReason)
+			//if s.responseReason != 4 {
+			//s.client.removeEventListener(s.uuid)
+			//}
+		case "Hangup":
+			if cause, check := e.ActionData["Cause"]; check {
+				causeVal, _ := strconv.ParseInt(cause, 10, 32)
+				s.hangupCause = byte(causeVal)
+			}
+		}
+	}
+}
+
+func (s *Originate) IsFinished() bool {
+	return s.finished
+}
+
+func (s *Originate) Events() (res <-chan Event) {
+	s.locker.Lock()
+	if s.userEventChan == nil {
+		s.userEventChan = make(chan Event)
+	}
+	res = s.userEventChan
+	s.locker.Unlock()
+	return
+}

+ 59 - 0
sip/asterisk/ami/request.go

@@ -0,0 +1,59 @@
+package ami
+
+import (
+	"fmt"
+
+	"git.ali33.ru/fcg-xvii/go-tools/json"
+)
+
+func InitRequest(action string) Request {
+	return Request{
+		ActionData: ActionData{
+			"Action": action,
+		},
+	}
+}
+
+type Request struct {
+	ActionData
+	Variables    json.Map
+	chanResponse chan Response
+	sended       bool
+}
+
+func (s *Request) SetParam(key, value string) {
+	if len(value) > 0 {
+		s.ActionData[key] = value
+	}
+}
+
+func (s *Request) SetVariable(key, value string) {
+	if s.Variables == nil {
+		s.Variables = json.NewMap()
+	}
+	s.Variables[key] = value
+}
+
+func (s *Request) SetVariables(m json.Map) {
+	if s.Variables == nil {
+		s.Variables = json.NewMap()
+	}
+	for key, val := range m {
+		s.Variables[key] = val
+	}
+}
+
+func (s *Request) raw() []byte {
+	if len(s.Variables) > 0 {
+		vars, count := "", 0
+		for key, val := range s.Variables {
+			vars += fmt.Sprintf("%v=%v", key, val)
+			if count < len(s.Variables)-1 {
+				vars += "," // todo 1.5 or lower splitter is '|'
+			}
+			count++
+		}
+		s.ActionData["Variable"] = vars
+	}
+	return s.ActionData.raw()
+}

+ 22 - 0
sip/asterisk/ami/response.go

@@ -0,0 +1,22 @@
+package ami
+
+func initResponseError(err error) Response {
+	return Response{
+		ActionData{
+			"Action":  "Error",
+			"Message": err.Error(),
+		},
+	}
+}
+
+type Response struct {
+	ActionData
+}
+
+func (s Response) IsError() bool {
+	return s.ActionData["Response"] == "Error"
+}
+
+func (s Response) ErrorMessage() string {
+	return s.ActionData["Message"]
+}

+ 74 - 0
sip/asterisk/ami/z_test.go

@@ -0,0 +1,74 @@
+package ami
+
+import (
+	"log"
+	"os"
+	"testing"
+	"time"
+
+	"git.ali33.ru/fcg-xvii/go-tools/text/config"
+	_ "git.ali33.ru/fcg-xvii/go-tools/text/config/ini"
+)
+
+var (
+	host, login, password string
+)
+
+func init() {
+	// setup auth vars, z_auth.config file example :
+	// 127.0.0.1:5038::admin::mypassword
+	//config.SplitFileToVals("z_auth.config", "::", &host, &login, &password)
+
+	f, err := os.Open("z_auth.config")
+	if err == nil {
+		conf, err := config.FromReader("ini", f)
+		if err == nil {
+			conf.ValueSetup("host", &host)
+			conf.ValueSetup("login", &login)
+			conf.ValueSetup("password", &password)
+		}
+		f.Close()
+	}
+}
+
+func TestClient(t *testing.T) {
+	if host == "" {
+		return
+	}
+	log.Println(host, login, password)
+
+	var cl *Client
+	cl = New(host, login, password, nil, func(state State, err error) {
+		log.Println("STATE_CHANGED", state, err)
+		switch state {
+		case StateStopped:
+			{
+				time.Sleep(time.Second * 5)
+				log.Println("Reconnect...")
+				//go cl.Start()
+			}
+		}
+	})
+	go cl.Start()
+
+	for {
+		log.Println("Originate...")
+		rOrig := &OriginateRequest{
+			Channel: "SIP/user1/89774708408",
+			Context: "admefine-bot",
+			Exten:   "s",
+		}
+		originate, err := cl.Originate(rOrig)
+		if err != nil {
+			log.Println("Originate error", err)
+			continue
+		}
+		log.Println("Call start")
+		for event := range originate.Events() {
+			log.Println(event)
+		}
+		log.Println("Call finished...")
+		//time.Sleep(time.Second * 5)
+		cl.Close()
+	}
+}

+ 53 - 0
smtp/emailer/emailer.go

@@ -0,0 +1,53 @@
+// Package emailer for send email messages text or html formats
+package emailer
+
+import (
+	"encoding/base64"
+	"fmt"
+	"net/smtp"
+	"strings"
+	"time"
+)
+
+// encode header of email message
+func mailEncodeHeader(str string) string {
+	return "=?UTF-8?B?" + base64.StdEncoding.EncodeToString([]byte(str)) + "?="
+}
+
+// encode header email field
+func mailEncodeEmail(email string) string {
+	return mailEncodeHeader(email) + " <" + email + ">"
+}
+
+// join encoded emails (receivers) for header field "To"
+func mailJoinReceivers(receivers []string) string {
+	arr := make([]string, len(receivers))
+	for i, v := range receivers {
+		arr[i] = mailEncodeEmail(v)
+	}
+	return strings.Join(arr, ",")
+}
+
+// SendEmail send email message with text/plain mime type
+func SendEmail(subject, message string, receivers []string, userName, userPassword, host, identity string, port int16) (err error) {
+	auth := smtp.PlainAuth(identity, userName, userPassword, host)
+	msg := []byte("To: " + mailJoinReceivers(receivers) + "\r\n" +
+		"Date:" + time.Now().Format("Mon 2 Jan 2006 15:04:05 -0700") + "\r\n" +
+		"From: " + mailEncodeEmail(userName) + "\r\n" +
+		"Subject: " + mailEncodeHeader(subject) + "\r\n" +
+		"Content-Type: text/plain; charset=utf-8\r\n" +
+		"\r\n" + message + "\r\n")
+	return smtp.SendMail(fmt.Sprintf("%v:%v", host, port), auth, userName, receivers, msg)
+}
+
+// SendEmailHTML send email message with text/html mime type
+func SendEmailHTML(subject, message string, receivers []string, userName, userPassword, host, identity string, port int16) (err error) {
+	auth := smtp.PlainAuth(identity, userName, userPassword, host)
+	msg := []byte("To: " + mailJoinReceivers(receivers) + "\r\n" +
+		"Date:" + time.Now().Format("Mon 2 Jan 2006 15:04:05 -0700") + "\r\n" +
+		"From: " + mailEncodeEmail(userName) + "\r\n" +
+		"Subject: " + mailEncodeHeader(subject) + "\r\n" +
+		"Content-Type: text/html; charset=utf-8\r\n" +
+		"\r\n" + message + "\r\n")
+	return smtp.SendMail(fmt.Sprintf("%v:%v", host, port), auth, userName, receivers, msg)
+}

+ 170 - 0
store/store.go

@@ -0,0 +1,170 @@
+package store
+
+import (
+	"sync"
+)
+
+type CallCreate func(key interface{}) (value interface{}, created bool)
+type CallCreateMulti func(key interface{}) (m map[interface{}]interface{}, created bool)
+type CallCheck func(key, value interface{}, exists bool) (rKey, rValue interface{}, created bool)
+
+func FromMap(m map[interface{}]interface{}) *Store {
+	return &Store{
+		locker: new(sync.RWMutex),
+		items:  m,
+	}
+}
+
+func New() *Store {
+	return &Store{
+		locker: new(sync.RWMutex),
+		items:  make(map[interface{}]interface{}),
+	}
+}
+
+type Store struct {
+	locker *sync.RWMutex
+	items  map[interface{}]interface{}
+}
+
+func (s *Store) delete(key interface{}) {
+	delete(s.items, key)
+}
+
+func (s *Store) Delete(key interface{}) {
+	s.locker.Lock()
+	delete(s.items, key)
+	s.locker.Unlock()
+}
+
+func (s *Store) DeleteMulti(keys []interface{}) {
+	s.locker.Lock()
+	for _, key := range keys {
+		delete(s.items, key)
+	}
+	s.locker.Unlock()
+}
+
+func (s *Store) set(key, val interface{}) {
+	s.items[key] = val
+}
+
+func (s *Store) setMulti(m map[interface{}]interface{}) {
+	for key, val := range m {
+		s.items[key] = val
+	}
+}
+
+func (s *Store) Set(key, val interface{}) {
+	s.locker.Lock()
+	s.set(key, val)
+	s.locker.Unlock()
+}
+
+func (s *Store) SetMulti(m map[interface{}]interface{}) {
+	s.locker.Lock()
+	s.setMulti(m)
+	s.locker.Unlock()
+}
+
+func (s *Store) get(key interface{}) (val interface{}, check bool) {
+	val, check = s.items[key]
+	return
+}
+
+func (s *Store) Get(key interface{}) (val interface{}, check bool) {
+	s.locker.RLock()
+	val, check = s.get(key)
+	s.locker.RUnlock()
+	return
+}
+
+func (s *Store) GetCreate(key interface{}, mCreate CallCreate) (res interface{}, check bool) {
+	if res, check = s.Get(key); !check {
+		s.locker.Lock()
+		if res, check = s.get(key); check {
+			s.locker.Unlock()
+			return
+		}
+
+		if res, check = mCreate(key); check {
+			s.set(key, res)
+		}
+		s.locker.Unlock()
+	}
+	return
+}
+
+func (s *Store) GetCreateMulti(key interface{}, mCreateMulti CallCreateMulti) (res interface{}, check bool) {
+	if res, check = s.Get(key); !check {
+		s.locker.Lock()
+		if res, check = s.get(key); check {
+			s.locker.Unlock()
+			return
+		}
+
+		var m map[interface{}]interface{}
+		if m, check = mCreateMulti(key); check {
+			s.setMulti(m)
+			res, check = s.items[key]
+		}
+		s.locker.Unlock()
+	}
+	return
+}
+
+func (s *Store) GetCheck(key interface{}, mCheck CallCheck) (res interface{}, check bool) {
+	s.locker.Lock()
+	res, check = s.get(key)
+	if rKey, rVal, rCheck := mCheck(key, res, check); rCheck {
+		s.set(rKey, rVal)
+		key, res, check = rKey, rVal, true
+	}
+	s.locker.Unlock()
+	return
+}
+
+// Each implements a map bypass for each key using the callback function. If the callback function returns false, then the cycle stops
+func (s *Store) Each(callback func(interface{}, interface{}) bool) {
+	s.locker.RLock()
+	for key, val := range s.items {
+		if !callback(key, val) {
+			s.locker.RUnlock()
+			return
+		}
+	}
+	s.locker.RUnlock()
+}
+
+func (s *Store) Len() (res int) {
+	s.locker.RLock()
+	res = len(s.items)
+	s.locker.RUnlock()
+	return
+}
+
+func (s *Store) Keys() (res []interface{}) {
+	s.locker.RLock()
+	res = make([]interface{}, 0, len(s.items))
+	for key := range s.items {
+		res = append(res, key)
+	}
+	s.locker.RUnlock()
+	return
+}
+
+func (s *Store) Clear() {
+	s.locker.Lock()
+	s.items = make(map[interface{}]interface{})
+	s.locker.Unlock()
+}
+
+func (s *Store) Map() (res map[interface{}]interface{}) {
+	res = make(map[interface{}]interface{})
+	s.locker.RLock()
+	for key, val := range s.items {
+		res[key] = val
+	}
+	s.locker.RUnlock()
+	return
+}

+ 168 - 0
store/string.go

@@ -0,0 +1,168 @@
+package store
+
+import "sync"
+
+type StringCallCreate func(key string) (value interface{}, created bool)
+type StringCallCreateMulti func(key string) (map[string]interface{}, bool)
+type StringCallCheck func(key string, value interface{}, exists bool) (rKey string, rValue interface{}, created bool)
+
+func StringFromMap(m map[string]interface{}) *StoreString {
+	return &StoreString{
+		locker: new(sync.RWMutex),
+		items:  m,
+	}
+}
+
+func StringNew() *StoreString {
+	return &StoreString{
+		locker: new(sync.RWMutex),
+		items:  make(map[string]interface{}),
+	}
+}
+
+type StoreString struct {
+	locker *sync.RWMutex
+	items  map[string]interface{}
+}
+
+func (s *StoreString) delete(key string) {
+	delete(s.items, key)
+}
+
+func (s *StoreString) Delete(key string) {
+	s.locker.Lock()
+	delete(s.items, key)
+	s.locker.Unlock()
+}
+
+func (s *StoreString) DeleteMulti(keys []string) {
+	s.locker.Lock()
+	for _, key := range keys {
+		delete(s.items, key)
+	}
+	s.locker.Unlock()
+}
+
+func (s *StoreString) set(key string, val interface{}) {
+	s.items[key] = val
+}
+
+func (s *StoreString) setMulti(m map[string]interface{}) {
+	for key, val := range m {
+		s.set(key, val)
+	}
+}
+
+func (s *StoreString) Set(key string, val interface{}) {
+	s.locker.Lock()
+	s.set(key, val)
+	s.locker.Unlock()
+}
+
+func (s *StoreString) SetMulti(m map[string]interface{}) {
+	s.locker.Lock()
+	s.setMulti(m)
+	s.locker.Unlock()
+}
+
+func (s *StoreString) get(key string) (val interface{}, check bool) {
+	val, check = s.items[key]
+	return
+}
+
+func (s *StoreString) Get(key string) (val interface{}, check bool) {
+	s.locker.RLock()
+	val, check = s.get(key)
+	s.locker.RUnlock()
+	return
+}
+
+func (s *StoreString) GetCreate(key string, mCreate StringCallCreate) (res interface{}, check bool) {
+	if res, check = s.Get(key); !check {
+		s.locker.Lock()
+		if res, check = s.get(key); check {
+			s.locker.Unlock()
+			return
+		}
+
+		if res, check = mCreate(key); check {
+			s.set(key, res)
+		}
+		s.locker.Unlock()
+	}
+	return
+}
+
+func (s *StoreString) GetCreateMulti(key string, mCreateMulti StringCallCreateMulti) (res interface{}, check bool) {
+	if res, check = s.Get(key); !check {
+		s.locker.Lock()
+		if res, check = s.get(key); check {
+			s.locker.Unlock()
+			return
+		}
+
+		var m map[string]interface{}
+		if m, check = mCreateMulti(key); check {
+			s.setMulti(m)
+			res, check = s.items[key]
+		}
+		s.locker.Unlock()
+	}
+	return
+}
+
+func (s *StoreString) GetCheck(key string, mCheck StringCallCheck) (res interface{}, check bool) {
+	s.locker.Lock()
+	res, check = s.get(key)
+	if rKey, rVal, rCheck := mCheck(key, res, check); rCheck {
+		s.set(rKey, rVal)
+		key, res, check = rKey, rVal, true
+	}
+	s.locker.Unlock()
+	return
+}
+
+// Each implements a map bypass for each key using the callback function. If the callback function returns false, then the cycle stops
+func (s *StoreString) Each(callback func(string, interface{}) bool) {
+	s.locker.RLock()
+	for key, val := range s.items {
+		if !callback(key, val) {
+			s.locker.RUnlock()
+			return
+		}
+	}
+	s.locker.RUnlock()
+}
+
+func (s *StoreString) Len() (res int) {
+	s.locker.RLock()
+	res = len(s.items)
+	s.locker.RUnlock()
+	return
+}
+
+func (s *StoreString) Keys() (res []string) {
+	s.locker.RLock()
+	res = make([]string, 0, len(s.items))
+	for key := range s.items {
+		res = append(res, key)
+	}
+	s.locker.RUnlock()
+	return
+}
+
+func (s *StoreString) Clear() {
+	s.locker.Lock()
+	s.items = make(map[string]interface{})
+	s.locker.Unlock()
+}
+
+func (s *StoreString) Map() (res map[string]interface{}) {
+	res = make(map[string]interface{})
+	s.locker.RLock()
+	for key, val := range s.items {
+		res[key] = val
+	}
+	s.locker.RUnlock()
+	return
+}

+ 73 - 0
store/z_test.go

@@ -0,0 +1,73 @@
+package store
+
+import (
+	"log"
+	"testing"
+)
+
+func TestStore(t *testing.T) {
+	st := New()
+
+	st.Set("one", 10)
+
+	log.Println(st.Get("one"))
+	log.Println(st.Get("two"))
+
+	log.Println(st.GetCreate("two", func(key interface{}) (value interface{}, created bool) {
+		return 20, true
+	}))
+
+	st.Delete("two")
+
+	log.Println(st.GetCreate("two", func(key interface{}) (value interface{}, created bool) {
+		return 30, true
+	}))
+
+	log.Println(st.Map())
+
+	st = FromMap(map[interface{}]interface{}{
+		"one": 1,
+		"two": 2,
+	})
+
+	log.Println(st.Map())
+}
+
+func TestStoreString(t *testing.T) {
+	st := StringNew()
+
+	st.Set("one", 10)
+
+	log.Println(st.Get("one"))
+	log.Println(st.Get("two"))
+
+	log.Println(st.GetCreate("two", func(key string) (value interface{}, created bool) {
+		return 20, true
+	}))
+
+	st.Delete("two")
+
+	log.Println(st.GetCreate("two", func(key string) (value interface{}, created bool) {
+		return 30, true
+	}))
+
+	log.Println(st.Map())
+
+	st = StringFromMap(map[string]interface{}{
+		"one": 1,
+		"two": 2,
+	})
+
+	log.Println(st.Map())
+
+	val, check := st.GetCreateMulti("key1", func(string) (map[string]interface{}, bool) {
+		return map[string]interface{}{
+			"key100": 100,
+			"key2":   200,
+		}, true
+	})
+
+	log.Println(val, check)
+	log.Println("==================")
+	log.Println(st.items)
+}

+ 50 - 0
text/checker/checker.go

@@ -0,0 +1,50 @@
+package checker
+
+import (
+	"regexp"
+	"strings"
+)
+
+type Type byte
+
+const (
+	Undefined Type = iota
+	Email
+	Phone
+)
+
+func (s Type) String() string {
+	switch s {
+	case Email:
+		return "email"
+	case Phone:
+		return "phone"
+	default:
+		return "undefined"
+	}
+}
+
+var (
+	checkers = map[Type]*regexp.Regexp{
+		Email: regexp.MustCompile("^.*?@.*?\\.[\\w]+$"),
+		Phone: regexp.MustCompile("^\\+\\d{11}$"),
+	}
+)
+
+func Check(source string) Type {
+	source = strings.TrimSpace(source)
+	for i, v := range checkers {
+		if v.MatchString(source) {
+			return i
+		}
+	}
+	return Undefined
+}
+
+func CheckEmail(email string) bool {
+	return checkers[Email].MatchString(email)
+}
+
+func CheckPhone(phone string) bool {
+	return checkers[Phone].MatchString(phone)
+}

+ 25 - 0
text/config/config.go

@@ -0,0 +1,25 @@
+package config
+
+import (
+	"io"
+
+	"git.ali33.ru/fcg-xvii/go-tools/value"
+)
+
+type Section interface {
+	ValueSetup(name string, ptr interface{}) bool
+	Value(name string) (value.Value, bool)
+	ValueDefault(name string, defaultVal interface{}) interface{}
+	SetValue(name string, value interface{})
+	Save(io.Writer) error
+}
+
+type Config interface {
+	AppendSection(name string) (newSection Section)
+	Section(name string) (Section, bool)
+	Sections(name string) ([]Section, bool)
+	ValueSetup(name string, ptr interface{}) bool
+	Value(name string) (value.Value, bool)
+	ValueDefault(name string, defaultVal interface{}) interface{}
+	Save(io.Writer) error
+}

+ 80 - 0
text/config/ini/config.go

@@ -0,0 +1,80 @@
+package ini
+
+import (
+	"fmt"
+	"io"
+
+	"git.ali33.ru/fcg-xvii/go-tools/text/config"
+	"git.ali33.ru/fcg-xvii/go-tools/value"
+)
+
+func newConfig() *Config {
+	mainSection := []config.Section{
+		newSection(),
+	}
+	return &Config{
+		sections: map[string][]config.Section{
+			"main": mainSection,
+		},
+	}
+}
+
+type Config struct {
+	sections map[string][]config.Section
+}
+
+func (s *Config) Sections(name string) ([]config.Section, bool) {
+	sections, check := s.sections[name]
+	return sections, check
+}
+
+func (s *Config) Section(name string) (config.Section, bool) {
+	if sections, check := s.Sections(name); check {
+		return sections[0], true
+	}
+	return nil, false
+}
+
+func (s *Config) AppendSection(name string) config.Section {
+	section := newSection()
+	if sections, check := s.Sections(name); check {
+		sections = append(sections, section)
+		s.sections[name] = sections
+	} else {
+		s.sections[name] = []config.Section{section}
+	}
+	return section
+}
+
+func (s *Config) ValueSetup(name string, ptr interface{}) bool {
+	if main, check := s.Section("main"); check {
+		return main.ValueSetup(name, ptr)
+	}
+	return false
+}
+
+func (s *Config) Value(name string) (value.Value, bool) {
+	if main, check := s.Section("main"); check {
+		return main.Value(name)
+	}
+	return value.Value{}, false
+}
+
+func (s *Config) ValueDefault(name string, defaultVal interface{}) interface{} {
+	s.ValueSetup(name, &defaultVal)
+	return defaultVal
+}
+
+func (s *Config) Save(w io.Writer) (err error) {
+	for name, sections := range s.sections {
+		for _, section := range sections {
+			if _, err = w.Write([]byte(fmt.Sprintf("[%v]\n", name))); err != nil {
+				return
+			}
+			if err = section.Save(w); err != nil {
+				return
+			}
+		}
+	}
+	return
+}

+ 56 - 0
text/config/ini/parser.go

@@ -0,0 +1,56 @@
+package ini
+
+import (
+	"bufio"
+	"io"
+	"strings"
+
+	"git.ali33.ru/fcg-xvii/go-tools/text/config"
+)
+
+func init() {
+	config.RegisterParseMethod("ini", parser)
+}
+
+func parser(r io.Reader) (res config.Config, err error) {
+	res = newConfig()
+	buf := bufio.NewReader(r)
+	var line []byte
+	section, _ := res.Section("main")
+	for {
+		line, _, err = buf.ReadLine()
+		if s := string(line); len(s) > 0 {
+			s = strings.TrimSpace(s)
+			switch s[0] {
+			case '#':
+				// comment
+			case '[':
+				// section
+				if s[len(s)-1] == ']' {
+					sectionName := s[1 : len(s)-1]
+					if sectionName == "main" {
+						section, _ = res.Section("main")
+					} else {
+						section = res.AppendSection(sectionName)
+					}
+				}
+			default:
+				// value, check comment
+				if pos := strings.Index(s, "#"); pos > 0 {
+					s = s[:pos]
+				}
+				// check plitter position
+				if pos := strings.Index(s, "="); pos > 0 {
+					key, val := strings.TrimSpace(s[:pos]), strings.TrimSpace(s[pos+1:])
+					section.SetValue(key, val)
+				}
+			}
+		}
+		if err != nil {
+			if err == io.EOF {
+				err = nil
+			}
+			return
+		}
+	}
+}

+ 50 - 0
text/config/ini/section.go

@@ -0,0 +1,50 @@
+package ini
+
+import (
+	"fmt"
+	"io"
+
+	"git.ali33.ru/fcg-xvii/go-tools/text/config"
+	"git.ali33.ru/fcg-xvii/go-tools/value"
+)
+
+func newSection() config.Section {
+	return &Section{
+		store: make(map[string]value.Value),
+	}
+}
+
+type Section struct {
+	store map[string]value.Value
+}
+
+func (s *Section) ValueSetup(name string, ptr interface{}) bool {
+	if val, check := s.store[name]; check {
+		return val.Setup(ptr)
+	}
+	return false
+}
+
+func (s *Section) ValueDefault(name string, defaultVal interface{}) interface{} {
+	s.ValueSetup(name, &defaultVal)
+	return defaultVal
+}
+
+func (s *Section) Value(name string) (value.Value, bool) {
+	val, check := s.store[name]
+	return val, check
+}
+
+func (s *Section) SetValue(name string, val interface{}) {
+	s.store[name] = value.ValueOf(val)
+}
+
+func (s *Section) Save(w io.Writer) (err error) {
+	for name, val := range s.store {
+		if _, err = w.Write([]byte(fmt.Sprintf("%v = %v\n", name, val.String()))); err != nil {
+			return err
+		}
+	}
+	_, err = w.Write([]byte("\n"))
+	return
+}

+ 15 - 0
text/config/ini/test.ini

@@ -0,0 +1,15 @@
+#[main]
+one = 1002
+db_connection = http://localhost
+
+[cool]
+# Comment
+key = val # okko
+key1 = val1
+key2 = val2
+key3 =
+
+[cool]
+one = 1
+two = 2
+three = 3

+ 15 - 0
text/config/ini/tmp.ini

@@ -0,0 +1,15 @@
+[main]
+db_connection = http://localhost
+one = 1002
+
+[cool]
+key = val
+key1 = val1
+key2 = val2
+key3 = 
+
+[cool]
+one = 1
+two = 2
+three = 3
+

+ 49 - 0
text/config/ini/z_test.go

@@ -0,0 +1,49 @@
+package ini
+
+import (
+	"os"
+	"testing"
+
+	"git.ali33.ru/fcg-xvii/go-tools/text/config"
+)
+
+func TestINI(t *testing.T) {
+
+	// read config
+	conf, err := config.FromFile("ini", "test.ini")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// get value from config main section
+	mainVal, check := conf.Value("one")
+	t.Log(mainVal, check)
+	// int
+	var i int = 0
+	t.Log(mainVal.Setup(&i), i)
+	i = 0
+	t.Log(conf.ValueSetup("one", &i), i)
+
+	t.Log("value default", conf.ValueDefault("db_connection", "").(string))
+
+	// get section
+	main, check := conf.Section("main")
+	t.Log(main, check)
+
+	cool, check := conf.Section("cool")
+	t.Log(cool, check)
+
+	var str string = ""
+	t.Log(cool.ValueSetup("key1", &str), str)
+
+	cools, check := conf.Sections("cool")
+	t.Log(cools, check)
+
+	var f *os.File
+	f, err = os.OpenFile("tmp.ini", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600)
+	if err != nil {
+		t.Fatal(err)
+	}
+	conf.Save(f)
+	f.Close()
+}

+ 38 - 0
text/config/parser.go

@@ -0,0 +1,38 @@
+package config
+
+import (
+	"fmt"
+	"io"
+	"os"
+)
+
+type ParseMethod func(r io.Reader) (Config, error)
+
+var (
+	methods = make(map[string]ParseMethod)
+)
+
+func RegisterParseMethod(name string, method ParseMethod) {
+	methods[name] = method
+}
+
+func FromReader(methodName string, r io.Reader) (res Config, err error) {
+	method, check := methods[methodName]
+	if !check {
+		return nil, fmt.Errorf("UNEXPECTED METHOD [%v]", methodName)
+	}
+	return method(r)
+}
+
+func FromFile(methodName, filePath string) (res Config, err error) {
+	method, check := methods[methodName]
+	if !check {
+		return nil, fmt.Errorf("UNEXPECTED METHOD [%v]", methodName)
+	}
+	var f *os.File
+	if f, err = os.Open(filePath); err != nil {
+		return
+	}
+	defer f.Close()
+	return method(f)
+}

+ 1 - 0
text/config/z_test.go

@@ -0,0 +1 @@
+package config

+ 240 - 0
text/translit/translit.go

@@ -0,0 +1,240 @@
+package api
+
+import (
+	"bytes"
+)
+
+// ////////////////////////////////////////////////////////////////////////////////// //
+
+type specProc func(p, c, n rune, extMap map[string]string) (string, bool)
+
+// ////////////////////////////////////////////////////////////////////////////////// //
+
+var baseRuEn = map[string]string{
+	"а": "a", "А": "A", "Б": "B", "б": "b", "В": "V", "в": "v", "Г": "G", "г": "g",
+	"Д": "D", "д": "d", "З": "Z", "з": "z", "И": "I", "и": "i", "К": "K", "к": "k",
+	"Л": "L", "л": "l", "М": "M", "м": "m", "Н": "N", "н": "n", "О": "O", "о": "o",
+	"П": "P", "п": "p", "Р": "R", "р": "r", "С": "S", "с": "s", "Т": "T", "т": "t",
+	"У": "U", "у": "u", "Ф": "F", "ф": "f",
+}
+
+var scientificRuEn = map[string]string{
+	"Е": "E", "е": "e", "Ё": "Ë", "ё": "ë", "Ж": "Ž", "ж": "ž", "Й": "J", "й": "j",
+	"Х": "Ch", "х": "ch", "Ц": "C", "ц": "c", "Ч": "Č", "ч": "č", "Ш": "Š", "ш": "š",
+	"Щ": "Šč", "щ": "šč", "Ъ": "″", "ъ": "″", "Ы": "Y", "ы": "y", "Ь": "′", "ь": "′",
+	"Э": "È", "э": "è", "Ю": "Ju", "ю": "ju", "Я": "Ja", "я": "ja",
+}
+
+var iso9ARuEn = map[string]string{
+	"Е": "E", "е": "e", "Ё": "Ë", "ё": "ë", "Ж": "Ž", "ж": "ž", "Й": "J", "й": "j",
+	"Х": "H", "х": "h", "Ц": "C", "ц": "c", "Ч": "Č", "ч": "č", "Ш": "Š", "ш": "š",
+	"Щ": "Ŝ", "щ": "ŝ", "Ъ": "″", "ъ": "″", "Ы": "Y", "ы": "y", "Ь": "′", "ь": "′",
+	"Э": "È", "э": "è", "Ю": "Û", "ю": "û", "Я": "Â", "я": "â",
+}
+
+var iso9BRuEn = map[string]string{
+	"Е": "E", "е": "e", "Ё": "Yo", "ё": "yo", "Ж": "Zh", "ж": "zh", "Й": "J", "й": "j",
+	"Х": "X", "х": "x", "Ч": "Ch", "ч": "ch", "Ш": "Sh", "ш": "sh", "Щ": "Shh",
+	"щ": "shh", "Ъ": "``", "ъ": "``", "Ы": "Y`", "ы": "y`", "Ь": "`", "ь": "`",
+	"Э": "E`", "э": "e`", "Ю": "Yu", "ю": "yu", "Я": "Ya", "я": "ya",
+}
+
+var isoR91RuEn = map[string]string{
+	"Е": "E", "е": "e", "Ё": "Ë", "ё": "ë", "Ж": "Ž", "ж": "ž", "Й": "J", "й": "j",
+	"Х": "H", "х": "h", "Ц": "C", "ц": "c", "Ч": "Č", "ч": "č", "Ш": "Š", "ш": "š",
+	"Щ": "Ŝ", "щ": "ŝ", "Ъ": "″", "ъ": "″", "Ы": "Y", "ы": "y", "Ь": "′", "ь": "′",
+	"Э": "È", "э": "è", "Ю": "Û", "ю": "û", "Я": "Â", "я": "â",
+}
+
+var isoR92RuEn = map[string]string{
+	"Е": "E", "е": "e", "Ё": "Jo", "ё": "jo", "Ж": "Zh", "ж": "zh", "Й": "Jj", "й": "jj",
+	"Х": "Kh", "х": "kh", "Ц": "C", "ц": "c", "Ч": "Ch", "ч": "ch", "Ш": "Sh", "ш": "sh",
+	"Щ": "Shh", "щ": "shh", "Ъ": "″", "ъ": "″", "Ы": "Y", "ы": "y", "Ь": "′", "ь": "′",
+	"Э": "Eh", "э": "eh", "Ю": "Ju", "ю": "ju", "Я": "Ja", "я": "ja",
+}
+
+var bgnRuEn = map[string]string{
+	"Ж": "Zh", "ж": "zh", "И": "I", "и": "i", "Й": "Y", "й": "y", "Х": "Kh", "х": "kh",
+	"Ц": "Ts", "ц": "ts", "Ч": "Ch", "ч": "ch", "Ш": "Sh", "ш": "sh", "Щ": "Shch",
+	"щ": "shch", "Ъ": "″", "ъ": "″", "Ы": "Y", "ы": "y", "Ь": "′", "ь": "′", "Э": "E",
+	"э": "e", "Ю": "Yu", "ю": "yu", "Я": "Ya", "я": "ya",
+}
+
+var bsRuEn = map[string]string{
+	"Е": "e", "е": "e", "Ё": "ë", "ё": "ë", "Ж": "zh", "ж": "zh", "Й": "ĭ", "й": "ĭ",
+	"Х": "kh", "х": "kh", "Ц": "ts", "ц": "ts", "Ч": "ch", "ч": "ch", "Ш": "sh",
+	"ш": "sh", "Щ": "shch", "щ": "shch", "Ъ": "″", "ъ": "″", "Ы": "ȳ", "ы": "ȳ",
+	"Ь": "′", "ь": "′", "Э": "é", "э": "é", "Ю": "yu", "ю": "yu", "Я": "ya", "я": "ya",
+}
+
+var alalcRuEn = map[string]string{
+	"Е": "E", "е": "e", "Ё": "Ë", "ё": "ë", "Ж": "Zh", "ж": "zh", "Й": "Ĭ", "й": "ĭ",
+	"Х": "Kh", "х": "kh", "Ц": "T͡s", "ц": "t͡s", "Ч": "Ch", "ч": "ch", "Ш": "Sh",
+	"ш": "sh", "Щ": "Shch", "щ": "shch", "Ъ": "″", "ъ": "″", "Ы": "Y", "ы": "y",
+	"Ь": "′", "ь": "′", "Э": "Ė", "э": "ė", "Ю": "I͡u", "ю": "i͡u", "Я": "I͡a", "я": "i͡a",
+}
+
+var icaoRuEn = map[string]string{
+	"Е": "E", "е": "e", "Ё": "E", "ё": "e", "Ж": "Zh", "ж": "zh", "Й": "I", "й": "i",
+	"Х": "Kh", "х": "kh", "Ц": "Ts", "ц": "ts", "Ч": "Ch", "ч": "ch", "Ш": "Sh",
+	"ш": "sh", "Щ": "Shch", "щ": "shch", "Ъ": "Ie", "ъ": "ie", "Ы": "Y", "ы": "y",
+	"Ь": "", "ь": "", "Э": "E", "э": "e", "Ю": "Iu", "ю": "iu", "Я": "Ia", "я": "ia",
+}
+
+var iso9BSpeical = map[string]string{
+	"ц": "cz", "Ц": "Cz", "ц+": "с", "Ц+": "С",
+}
+
+var bgnSpecial = map[string]string{
+	"е": "e", "Е": "E", "ё": "ë", "Ё": "Ë",
+	"е+": "ye", "Е+": "Ye", "ё+": "yë", "Ё+": "Yë",
+}
+
+// ////////////////////////////////////////////////////////////////////////////////// //
+
+// EncodeToScientific encodes text with scientific mappings
+func EncodeToScientific(text string) string {
+	return encode(text, scientificRuEn, nil)
+}
+
+// EncodeToISO9A encodes text with ISO 9:1995/A ГОСТ 7.79-2000/A mappings
+func EncodeToISO9A(text string) string {
+	return encode(text, iso9ARuEn, nil)
+}
+
+// EncodeToISO9B encodes text with ISO 9:1995/B ГОСТ 7.79-2000/Б mappings
+func EncodeToISO9B(text string) string {
+	return encode(text, iso9BRuEn, iso9BSpec)
+}
+
+// EncodeToBGN encodes text with BGN mappings
+func EncodeToBGN(text string) string {
+	return encode(text, bgnRuEn, bgnSpec)
+}
+
+// EncodeToPCGN encodes text with PCGN mappings
+func EncodeToPCGN(text string) string {
+	return encode(text, bgnRuEn, bgnSpec)
+}
+
+// EncodeToALALC encodes text with ALA-LC mappings
+func EncodeToALALC(text string) string {
+	return encode(text, alalcRuEn, nil)
+}
+
+// EncodeToBS encodes text with BS 2979:1958 mappings
+func EncodeToBS(text string) string {
+	return encode(text, alalcRuEn, nil)
+}
+
+// EncodeToICAO encodes text with ICAO mappings
+func EncodeToICAO(text string) string {
+	return encode(text, icaoRuEn, nil)
+}
+
+// ////////////////////////////////////////////////////////////////////////////////// //
+
+func encode(text string, extMap map[string]string, proc specProc) string {
+	if text == "" {
+		return ""
+	}
+
+	var input = bytes.NewBufferString(text)
+	var output = bytes.NewBuffer(nil)
+
+	// Previous, next letter for special processor
+	var p, n rune
+	var rr string
+	var ok bool
+
+	for {
+		r, _, err := input.ReadRune()
+
+		if err != nil {
+			break
+		}
+
+		if !isRussianChar(r) {
+			output.WriteRune(r)
+			p = r
+			continue
+		}
+
+		if proc != nil {
+			n, _, _ = input.ReadRune()
+
+			input.UnreadRune()
+
+			rr, ok = proc(p, r, n, extMap)
+
+			if ok {
+				output.WriteString(rr)
+				continue
+			}
+		}
+
+		p = r
+
+		rr, ok = baseRuEn[string(r)]
+
+		if ok {
+			output.WriteString(rr)
+			continue
+		}
+
+		rr, ok = extMap[string(r)]
+
+		if ok {
+			output.WriteString(rr)
+		}
+	}
+
+	return output.String()
+}
+
+func iso9BSpec(p, c, n rune, extMap map[string]string) (string, bool) {
+	if c != 'ц' && c != 'Ц' {
+		return "", false
+	}
+
+	rr, ok := baseRuEn[string(n)]
+
+	if !ok {
+		rr = extMap[string(n)]
+	}
+
+	switch rr {
+	case "e", "i", "y", "j", "E", "I", "Y", "J":
+		return iso9BSpeical[string(c)+"+"], true
+	}
+
+	return iso9BSpeical[string(c)], true
+}
+
+func bgnSpec(p, c, n rune, extMap map[string]string) (string, bool) {
+	switch c {
+	case 'е', 'Е', 'ё', 'Ё':
+		// nop
+	default:
+		return "", false
+	}
+
+	switch p {
+	case 0, ' ',
+		'а', 'у', 'о', 'ы', 'и', 'э', 'я', 'ю',
+		'А', 'У', 'О', 'Ы', 'И', 'Э', 'Я', 'Ю':
+		return bgnSpecial[string(c)+"+"], true
+	}
+
+	return bgnSpecial[string(c)], true
+}
+
+func isRussianChar(r rune) bool {
+	switch {
+	case r >= 1040 && r <= 1103,
+		r == 1105, r == 1025:
+		return true
+	}
+
+	return false
+}

+ 25 - 0
value/def/default.go

@@ -0,0 +1,25 @@
+package def
+
+import (
+	"strconv"
+	"strings"
+)
+
+func String(val, defaultVal string) (res string) {
+	if len(val) == 0 {
+		res = defaultVal
+	}
+	return
+}
+
+func StringTrim(val, defaultVal string) string {
+	return String(strings.TrimSpace(val), defaultVal)
+}
+
+func Int(val string, defaultVal int) (res int) {
+	var err error
+	if res, err = strconv.Atoi(val); err != nil {
+		res = defaultVal
+	}
+	return
+}

+ 122 - 0
value/value.go

@@ -0,0 +1,122 @@
+package value
+
+import (
+	"encoding/json"
+	"fmt"
+	"reflect"
+	"strconv"
+	"strings"
+)
+
+func ValueOf(val interface{}) Value {
+	return Value{
+		val: val,
+	}
+}
+
+type Value struct {
+	val interface{}
+}
+
+func (s *Value) IsValid() bool {
+	return s.val != nil
+}
+
+func (s *Value) Setup(val interface{}) (res bool) {
+	rr := reflect.ValueOf(val)
+	if rr.Kind() != reflect.Ptr {
+		panic(fmt.Errorf("Expected ptr, given %v", rr.Type()))
+	}
+	rKind, rType := rr.Elem().Kind(), rr.Elem().Type()
+	if rKind == reflect.Interface {
+		rKind, rType = rr.Elem().Elem().Kind(), rr.Elem().Elem().Type()
+	}
+	if rKind == reflect.String {
+		rls := strings.TrimSpace(fmt.Sprint(s.val))
+		if len(rls) > 0 {
+			rr.Elem().Set(reflect.ValueOf(rls))
+			res = true
+		}
+	} else {
+		rl := reflect.ValueOf(s.val)
+		if rl.Kind() == reflect.String {
+			switch rKind {
+			case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+				{
+					if tmp, err := strconv.ParseInt(rl.String(), 10, 64); err == nil {
+						rr.Elem().Set(reflect.ValueOf(tmp).Convert(rType))
+						res = true
+					}
+				}
+			case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+				{
+					if tmp, err := strconv.ParseUint(rl.String(), 10, 64); err == nil {
+						rr.Elem().Set(reflect.ValueOf(tmp).Convert(rType))
+						res = true
+					}
+				}
+			case reflect.Float32, reflect.Float64:
+				{
+					if tmp, err := strconv.ParseFloat(rl.String(), 64); err == nil {
+						rr.Elem().Set(reflect.ValueOf(tmp).Convert(rType))
+						res = true
+					}
+				}
+			default:
+				// json
+				i := reflect.New(rr.Elem().Type()).Interface()
+				if err := json.Unmarshal([]byte(rl.String()), i); err == nil {
+					rr.Elem().Set(reflect.ValueOf(i).Elem())
+				}
+			}
+		} else {
+			var rVal reflect.Value
+			defer func() {
+				if r := recover(); r == nil {
+					rr.Elem().Set(rVal)
+					res = true
+				}
+			}()
+			rVal = rl.Convert(rType)
+		}
+	}
+	return
+}
+
+func (s *Value) String() string {
+	return fmt.Sprint(s.val)
+}
+
+func (s *Value) Int() int {
+	var i int
+	s.Setup(&i)
+	return i
+}
+
+func (s *Value) Int8() int8 {
+	var i int8
+	s.Setup(&i)
+	return i
+}
+
+func (s *Value) Int16() int16 {
+	var i int16
+	s.Setup(&i)
+	return i
+}
+
+func (s *Value) Int32() int32 {
+	return int32(s.Int())
+}
+
+func (s *Value) Float32() float32 {
+	var i float32
+	s.Setup(&i)
+	return i
+}
+
+func (s *Value) Float64() float64 {
+	var i float64
+	s.Setup(&i)
+	return i
+}

+ 15 - 0
value/z_test.go

@@ -0,0 +1,15 @@
+package value
+
+import (
+	"log"
+	"testing"
+)
+
+func TestValue(t *testing.T) {
+	val := ValueOf(`{ "one": "111" }`)
+	s := map[string]string{}
+	t.Log(val.Setup(&s), s)
+
+	val = ValueOf(100.55)
+	log.Println(val.Int())
+}