pax_global_header00006660000000000000000000000064131550570320014513gustar00rootroot0000000000000052 comment=b56169c6bd620eeb7cfc4b9b4027fc10d2934c84 golib-4.24.2/000077500000000000000000000000001315505703200127005ustar00rootroot00000000000000golib-4.24.2/.gitignore000066400000000000000000000004201315505703200146640ustar00rootroot00000000000000# Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.exe *.test *.prof *.swp golib-4.24.2/CHANGELOG.md000066400000000000000000000134141315505703200145140ustar00rootroot00000000000000# Tideland Go Library ## 2017-09-09 - Fixed a *GJP* documentation typo - Added a *TestLogger* to be used in unit tests - Changed *Loop* to not log anymore if no description is given ## 2017-07-20 - Fixed *GJP* handling of empty objects and arrays ## 2017-07-13 - Renamed the generic JSON parser to generic JSON processor ## 2017-06-30 - Added *WaitTested()* to *audit.Assertion* - Added *SetFailable()* to *audit.Assertion* to be used in sub-tests - Added *Builder* to *gjp* - Added *Matches()* to *stringex* - Added *Query()* to *gjp.Document* ## 2017-05-24 - Added generic JSON parser package *gjp* - Added iterator method *Do()* to *etc.Etc* - Added interface *Processor* as well as the type *ProcessorFunc* which implements this interface to *stringex* - Multiple functions in *stringex* construct different parametrized processors which can be used in chains, conditions, and loops ## 2017-03-19 - Readesigned *cache*; now individual instances with user definable loaders - Fixing of races in tests in *loop* and *monitoring* - Added *IncrCallstackOffset()* to *audit.Assertion* for correct logging in functions and packages providing own test functions based on *audit* - Code reorganisation in *audit* - Extended *Range()* assertion in *audit* by time and duration - Added *FailureDetail* returned by *ValidationFailer.Details()* in *audit* ## 2017-03-13 - Fixed flaky tests in *timex* package ## 2017-02-13 - *audit.Assertion* now is better in comparing strings with byte slices in *Contains* - output of *Contains* in case of failing tests is now more clear and also handles strings and byte slices better readable - fixed error in file and line determination for test output ## 2017-01-18 - *audit.Generator* now provides *OneDurationOf()* and *SleepOneOf()* ## 2017-01-13 - *collections.RingBuffer* now provides *Peek()* ## 2017-01-07 - *etc.Etc* now can write to an *io.Reader* ## 2016-12-11 - Added asserts for *range*, *case*, and *path exists* ## 2016-12-07 - Added *ValidationAssertion* to *audit* ## 2016-11-23 - Fixed an error in *identifier* generation ## 2016-11-22 - Added *Name()* to *Generator* in *audit* ## 2016-11-15 - Added *Parse()* and *Compare()* to *version* - Added package *library* for functions representing the whole Go Library; currently only containing *Version()* - Extended *etc* to retrieve values out of environment variables ## 2016-11-01 - Added *SetLevelString()* to *logger* for setting the level out of readable configuration data - Fixed a build problem on Windows for *logger* ## 2016-10-19 - *stringex* package now nows a simple *StringValuer* - *configuration* and *web* are now removed ## 2016-10-14 - Backend in *monitoring* is now lazy loading - Fixed splitting bug in *etc* ## 2016-10-13 - *Etc* now can handle templates substituted with values from other configuration variables or defaults ## 2016-10-07 - Added context handling to *etc* package - Added *HasPath()* to *Etc* - Added *DoAllDeep()* to the missing tree types in *collections* ## 2016-10-06 - Added *DoAllDeep()* to *KeyStringValueTree* in *collections* package - Other tree types will follow later - Added *Dump()* to *Etc* ## 2016-10-04 - Added *SplitFilter()* and *SplitMap()* to *stringex* for convenient splitting operations - *Etc.Apply()* is now more robust by using *SplitMap()* ## 2016-10-03 - Added *Root()* to the trees in *collections* ## 2016-10-02 - *KeyValueTree* and *KeyStringValueTree* in *collections* now can copy subtrees - Both now also support the retrieval and setting of keys - Added *Split()* to *Etc* ## 2016-09-29 - Added *etc* based on former *configuration* package - Marked *configuration* as deprecated - Marked *web* as deprecated after migration to https://github.com/tideland/gorest ## 2016-02-16 - Removal of the *cells* package after migration to https://github.com/tideland/gocells ## 2015-09-01 - Added filtering to *logger* package ## 2015-08-23 - The backend of the *monitoring* package is now pluggable - Beside the standard backend a null backend doing nothing has been added - So last changes in *cells* packge have been rolled back as the monitoring handling is now a global topic ## 2015-08-18 - Monitoring in *cells* package is now pluggable ## 2015-08-17 - Fixed race condition in *cells* package - Optimised time handling in *cells* package ## 2015-08-09 - Added `Collect()` and `DoAll()` to *errors* package ## 2015-08-02 - Added `BeginOf()` and `EndOf()` to *timex* package ## 2015-08-01 - Added `Set` and `StringSet` to *collections* package - Added `Retry()` to *timex* package ## 2015-07-28 - Added assertion `Retry()` to *audit* package ## 2015-07-26 - Added `CallbackBehavior` to *cells* package ## 2015-07-24 - Fixed *cells* package unsubscribing failure when stopping cell with bi-directional subscriptions; thanks to Jonathan Camp for his fix - Added expected value to compare with signal in `Wait()` assertion - Added test for configuration validation in configurator behavior ## 2015-07-23 - Added `ReadFile()` to *configuration* package - Added `SimpleProcessorBehavior` to *cells* package - Added `ConfiguratorBehavior` to *cells* package - Added assertion `Wait()` to *audit* package ## 2015-07-20 - Simplified *configuration* package for usage with `stringex.Defaulter` ## 2015-07-17 - Added *stringex* package ## 2015-07-10 - Added `KeyStringValueTreeBuilder` to *sml* package - Several minor fixes ## 2015-07-05 - Made time format in *logger* package standard backend changeable ## 2015-06-28 - Changed *configuration* package to be more powerful and convenient ## 2015-06-25 - Added new `SceneBehavior` to *cells* package ## 2015-06-25 - Done migration into new library - Added new *configuration* package ## 2015-06-05 - Started migration of existing packages into new library golib-4.24.2/LICENSE000066400000000000000000000027551315505703200137160ustar00rootroot00000000000000Copyright (c) 2009-2017, Frank Mueller / Tideland / Oldenburg / Germany All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - Neither the name of Tideland nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. golib-4.24.2/README.md000066400000000000000000000064201315505703200141610ustar00rootroot00000000000000# Tideland Go Library ## Description The *Tideland Go Library* contains a larger set of useful Google Go packages for different purposes. **ATTENTION:** The `cells` package has been migrated into an own repository at [https://github.com/tideland/gocells](https://github.com/tideland/gocells). **ATTENTION:** The `web` package is now deprecated. It has been migrated and extended into the repository [https://github.com/tideland/gorest](https://github.com/tideland/gorest). I hope you like them. ;) [![GitHub release](https://img.shields.io/github/release/tideland/golib.svg)](https://github.com/tideland/golib) [![GitHub license](https://img.shields.io/badge/license-New%20BSD-blue.svg)](https://raw.githubusercontent.com/tideland/golib/master/LICENSE) [![GoDoc](https://godoc.org/github.com/tideland/golib?status.svg)](https://godoc.org/github.com/tideland/golib) [![Sourcegraph](https://sourcegraph.com/github.com/tideland/golib/-/badge.svg)](https://sourcegraph.com/github.com/tideland/golib?badge) [![Go Report Card](https://goreportcard.com/badge/github.com/tideland/golib)](https://goreportcard.com/report/github.com/tideland/golib) ## Version Version 4.24.2 ## Packages ### Audit Support for unit tests with mutliple different assertion types and functions to generate test data. ### Cache Individual caches for types implementing the Cacheable interface. ### Collections Different additional collection types like ring buffer, stack, tree, and more. ### Errors Detailed error values. ### Etc Reading and parsing of SML-formatted configurations including substituion of templates. ### Feed Atom and RSS feed client. ### Generic JSON Processor Instead of unmarshalling a JSON into a struct parse it and provide access to the content by path and value converters to native types. Also processing and comparing is possible. ### Identifier Identifier generation, like UUIDs (v1, v3, v4, v5) or composed values. ### Logger Logging with different levels to different backends and powerful extensions. ### Loop Control of goroutines and their possible errors. Additional option of recovering in case of an error or a panic. Sentinels can monitor multiple loops and restart them all in case of an abnormal end of one of them. ### Map/Reduce Map/Reduce for data analysis. ### Monitoring Monitoring of execution times, stay-set indicators, and configurable system variables. ### Numerics Different functions for statistical analysis. ### Redis Client Client for the Redis database. ### Scene Context-based shared data access, e.g. for web sessions or in cells. ### Scroller Continuous filtered reading/writing of data. ### SML Simple Markup Language, looking lispy, only with curly braces. ### Sort Parallel Quicksort. ### Stringex Helpful functions around strings extending the original `strings` package and help processing strings. ### Timex Helpful functions around dates and times. ### Version Documentation of semantic versions. ## Contributors - Frank Mueller (https://github.com/themue / https://github.com/tideland) - Alex Browne (https://github.com/albrow) - Tim Heckman (https://github.com/theckman) - Benedikt Lang (https://github.com/blang) - Pellaeon Lin (https://github.com/pellaeon) ## License *Tideland Go Library* is distributed under the terms of the BSD 3-Clause license. golib-4.24.2/audit/000077500000000000000000000000001315505703200140065ustar00rootroot00000000000000golib-4.24.2/audit/asserts.go000066400000000000000000000347311315505703200160310ustar00rootroot00000000000000// Tideland Go Library - Audit // // Copyright (C) 2012-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package audit //-------------------- // IMPORTS //-------------------- import ( "fmt" "strings" "time" ) //-------------------- // TEST //-------------------- // Test represents the test inside an assert. type Test uint // Tests provided by the assertion. const ( Invalid Test = iota + 1 True False Nil NotNil Equal Different Contents About Range Substring Case Match ErrorMatch Implementor Assignable Unassignable Empty NotEmpty Length Panics PathExists Wait WaitTested Retry Fail ) //-------------------- // ASSERTION //-------------------- // MakeSigChan is a simple one-liner to create the buffered signal channel // for the wait assertion. func MakeSigChan() chan interface{} { return make(chan interface{}, 1) } // Assertion defines the available test methods. type Assertion interface { // SetFailable allows to change the failable possibly used inside // a failer. This way a testing.T of a sub-test can be injected. A // restore function is returned. // // t.Run(name, func(t *testing.T)) { // defer assert.SetFailable(t)() // ... // }) // // This way the returned restorer function will be called when // leaving the sub-test. SetFailable(f Failable) func() // IncrCallstackOffset allows test libraries using the audit // package internally to adjust the callstack offset. This // way test output shows the correct location. Deferring // the returned function restores the former offset. IncrCallstackOffset() func() // Logf can be used to display useful information during testing. Logf(format string, args ...interface{}) // True tests if obtained is true. True(obtained bool, msgs ...string) bool // False tests if obtained is false. False(obtained bool, msgs ...string) bool // Nil tests if obtained is nil. Nil(obtained interface{}, msgs ...string) bool // NotNil tests if obtained is not nil. NotNil(obtained interface{}, msgs ...string) bool // Equal tests if obtained and expected are equal. Equal(obtained, expected interface{}, msgs ...string) bool // Different tests if obtained and expected are different. Different(obtained, expected interface{}, msgs ...string) bool // Contents tests if the obtained data is part of the expected // string, array, or slice. Contents(obtained, full interface{}, msgs ...string) bool // About tests if obtained and expected are near to each other // (within the given extent). About(obtained, expected, extent float64, msgs ...string) bool // Range tests if obtained is larger or equal low and lower or // equal high. Allowed are byte, int and float64 for numbers, runes, // strings, times, and duration. In case of obtained arrays, // slices, and maps low and high have to be ints for testing // the length. Range(obtained, low, high interface{}, msgs ...string) bool // Substring tests if obtained is a substring of the full string. Substring(obtained, full string, msgs ...string) bool // Case tests if obtained string is uppercase or lowercase. Case(obtained string, upperCase bool, msgs ...string) bool // Match tests if the obtained string matches a regular expression. Match(obtained, regex string, msgs ...string) bool // ErrorMatch tests if the obtained error as string matches a // regular expression. ErrorMatch(obtained error, regex string, msgs ...string) bool // Implementor tests if obtained implements the expected // interface variable pointer. Implementor(obtained, expected interface{}, msgs ...string) bool // Assignable tests if the types of expected and obtained are assignable. Assignable(obtained, expected interface{}, msgs ...string) bool // Unassignable tests if the types of expected and obtained are // not assignable. Unassignable(obtained, expected interface{}, msgs ...string) bool // Empty tests if the len of the obtained string, array, slice // map, or channel is 0. Empty(obtained interface{}, msgs ...string) bool // NotEmpty tests if the len of the obtained string, array, slice // map, or channel is greater than 0. NotEmpty(obtained interface{}, msgs ...string) bool // Length tests if the len of the obtained string, array, slice // map, or channel is equal to the expected one. Length(obtained interface{}, expected int, msgs ...string) bool // Panics checks if the passed function panics. Panics(pf func(), msgs ...string) bool // PathExists checks if the passed path or file exists. PathExists(path string, msgs ...string) bool // Wait until a received signal or a timeout. The signal has // to be the expected value. Wait(sigc <-chan interface{}, expected interface{}, timeout time.Duration, msgs ...string) bool // WaitTested wait until a received signal or a timeout. The signal then // is tested by the passed function which has to return nil for a successful // assert. WaitTested(sigc <-chan interface{}, tester func(interface{}) error, timeout time.Duration, msgs ...string) bool // Retry calls the passed function and expects it to return true. Otherwise // it pauses for the given duration and retries the call the defined number. Retry(rf func() bool, retries int, pause time.Duration, msgs ...string) bool // Fail always fails. Fail(msgs ...string) bool } // NewAssertion creates a new Assertion instance. func NewAssertion(f Failer) Assertion { return &assertion{ failer: f, } } // assertion implements Assertion. type assertion struct { Tester failer Failer } // SetFailable implements Assertion. func (a *assertion) SetFailable(f Failable) func() { tf, ok := a.failer.(*testingFailer) if !ok { // Nothing to do. return func() {} } // It's a test assertion. old := tf.failable tf.failable = f return func() { tf.failable = old } } // Logf implements Assertion. func (a *assertion) IncrCallstackOffset() func() { return a.failer.IncrCallstackOffset() } // Logf implements Assertion. func (a *assertion) Logf(format string, args ...interface{}) { a.failer.Logf(format, args...) } // True implements Assertion. func (a *assertion) True(obtained bool, msgs ...string) bool { if !a.IsTrue(obtained) { return a.failer.Fail(True, obtained, true, msgs...) } return true } // False implements Assertion. func (a *assertion) False(obtained bool, msgs ...string) bool { if a.IsTrue(obtained) { return a.failer.Fail(False, obtained, false, msgs...) } return true } // Nil implements Assertion. func (a *assertion) Nil(obtained interface{}, msgs ...string) bool { if !a.IsNil(obtained) { return a.failer.Fail(Nil, obtained, nil, msgs...) } return true } // NotNil implements Assertion. func (a *assertion) NotNil(obtained interface{}, msgs ...string) bool { if a.IsNil(obtained) { return a.failer.Fail(NotNil, obtained, nil, msgs...) } return true } // Equal implements Assertion. func (a *assertion) Equal(obtained, expected interface{}, msgs ...string) bool { if !a.IsEqual(obtained, expected) { return a.failer.Fail(Equal, obtained, expected, msgs...) } return true } // Different implements Assertion. func (a *assertion) Different(obtained, expected interface{}, msgs ...string) bool { if a.IsEqual(obtained, expected) { return a.failer.Fail(Different, obtained, expected, msgs...) } return true } // Contents implements Assertion. func (a *assertion) Contents(part, full interface{}, msgs ...string) bool { contains, err := a.Contains(part, full) if err != nil { return a.failer.Fail(Contents, part, full, "type missmatch: "+err.Error()) } if !contains { return a.failer.Fail(Contents, part, full, msgs...) } return true } // About implements Assertion. func (a *assertion) About(obtained, expected, extent float64, msgs ...string) bool { if !a.IsAbout(obtained, expected, extent) { return a.failer.Fail(About, obtained, expected, msgs...) } return true } // Range implements Assertion. func (a *assertion) Range(obtained, low, high interface{}, msgs ...string) bool { expected := &lowHigh{low, high} inRange, err := a.IsInRange(obtained, low, high) if err != nil { return a.failer.Fail(Range, obtained, expected, "type missmatch: "+err.Error()) } if !inRange { return a.failer.Fail(Range, obtained, expected, msgs...) } return true } // Substring implements Assertion. func (a *assertion) Substring(obtained, full string, msgs ...string) bool { if !a.IsSubstring(obtained, full) { return a.failer.Fail(Substring, obtained, full, msgs...) } return true } // Case implements Assertion. func (a *assertion) Case(obtained string, upperCase bool, msgs ...string) bool { if !a.IsCase(obtained, upperCase) { if upperCase { return a.failer.Fail(Case, obtained, strings.ToUpper(obtained), msgs...) } return a.failer.Fail(Case, obtained, strings.ToLower(obtained), msgs...) } return true } // Match implements Assertion. func (a *assertion) Match(obtained, regex string, msgs ...string) bool { matches, err := a.IsMatching(obtained, regex) if err != nil { return a.failer.Fail(Match, obtained, regex, "can't compile regex: "+err.Error()) } if !matches { return a.failer.Fail(Match, obtained, regex, msgs...) } return true } // ErrorMatch implements Assertion. func (a *assertion) ErrorMatch(obtained error, regex string, msgs ...string) bool { if obtained == nil { return a.failer.Fail(ErrorMatch, nil, regex, "error is nil") } matches, err := a.IsMatching(obtained.Error(), regex) if err != nil { return a.failer.Fail(ErrorMatch, obtained, regex, "can't compile regex: "+err.Error()) } if !matches { return a.failer.Fail(ErrorMatch, obtained, regex, msgs...) } return true } // Implementor implements Assertion. func (a *assertion) Implementor(obtained, expected interface{}, msgs ...string) bool { implements, err := a.IsImplementor(obtained, expected) if err != nil { return a.failer.Fail(Implementor, obtained, expected, err.Error()) } if !implements { return a.failer.Fail(Implementor, obtained, expected, msgs...) } return implements } // Assignable implements Assertion. func (a *assertion) Assignable(obtained, expected interface{}, msgs ...string) bool { if !a.IsAssignable(obtained, expected) { return a.failer.Fail(Assignable, obtained, expected, msgs...) } return true } // Unassignable implements Assertion. func (a *assertion) Unassignable(obtained, expected interface{}, msgs ...string) bool { if a.IsAssignable(obtained, expected) { return a.failer.Fail(Unassignable, obtained, expected, msgs...) } return true } // Empty implements Assertion. func (a *assertion) Empty(obtained interface{}, msgs ...string) bool { length, err := a.Len(obtained) if err != nil { return a.failer.Fail(Empty, ValueDescription(obtained), 0, err.Error()) } if length > 0 { return a.failer.Fail(Empty, length, 0, msgs...) } return true } // NotEmpty implements Assertion. func (a *assertion) NotEmpty(obtained interface{}, msgs ...string) bool { length, err := a.Len(obtained) if err != nil { return a.failer.Fail(NotEmpty, ValueDescription(obtained), 0, err.Error()) } if length == 0 { return a.failer.Fail(NotEmpty, length, 0, msgs...) } return true } // Length implements Assertion. func (a *assertion) Length(obtained interface{}, expected int, msgs ...string) bool { length, err := a.Len(obtained) if err != nil { return a.failer.Fail(Length, ValueDescription(obtained), expected, err.Error()) } if length != expected { return a.failer.Fail(Length, length, expected, msgs...) } return true } // Panics implements Assertion. func (a *assertion) Panics(pf func(), msgs ...string) bool { if !a.HasPanic(pf) { return a.failer.Fail(Panics, ValueDescription(pf), nil, msgs...) } return true } // PathExists implements Assertion. func (a *assertion) PathExists(obtained string, msgs ...string) bool { valid, err := a.IsValidPath(obtained) if err != nil { return a.failer.Fail(PathExists, obtained, true, err.Error()) } if !valid { return a.failer.Fail(PathExists, obtained, true, msgs...) } return true } // Wait implements Assertion. func (a *assertion) Wait(sigc <-chan interface{}, expected interface{}, timeout time.Duration, msgs ...string) bool { select { case obtained := <-sigc: if !a.IsEqual(obtained, expected) { return a.failer.Fail(Wait, obtained, expected, msgs...) } return true case <-time.After(timeout): return a.failer.Fail(Wait, "timeout "+timeout.String(), "signal true", msgs...) } } // WaitTested implements Assertion. func (a *assertion) WaitTested(sigc <-chan interface{}, tester func(interface{}) error, timeout time.Duration, msgs ...string) bool { select { case obtained := <-sigc: err := tester(obtained) return a.Nil(err, msgs...) case <-time.After(timeout): return a.failer.Fail(Wait, "timeout "+timeout.String(), "signal true", msgs...) } } // Retry implements Assertion. func (a *assertion) Retry(rf func() bool, retries int, pause time.Duration, msgs ...string) bool { start := time.Now() for r := 0; r < retries; r++ { if rf() { return true } time.Sleep(pause) } needed := time.Now().Sub(start) info := fmt.Sprintf("timeout after %v and %d retries", needed, retries) return a.failer.Fail(Retry, info, "successful call", msgs...) } // Fail implements Assertion. func (a *assertion) Fail(msgs ...string) bool { return a.failer.Fail(Fail, nil, nil, msgs...) } //-------------------- // HELPER //-------------------- // lowHigh transports the expected borders of a range test. type lowHigh struct { low interface{} high interface{} } // lenable is an interface for the Len() mehod. type lenable interface { Len() int } // obexString constructs a descriptive sting matching // to test, obtained, and expected value. func obexString(test Test, obtained, expected interface{}) string { switch test { case True, False, Nil, NotNil, Empty, NotEmpty: return fmt.Sprintf("'%v'", obtained) case Implementor, Assignable, Unassignable: return fmt.Sprintf("'%v' <> '%v'", ValueDescription(obtained), ValueDescription(expected)) case Range: lh := expected.(*lowHigh) return fmt.Sprintf("not '%v' <= '%v' <= '%v'", lh.low, obtained, lh.high) case Fail: return "fail intended" default: return fmt.Sprintf("'%v' <> '%v'", obtained, expected) } } // failString constructs a fail string for panics or // validition errors. func failString(test Test, obex string, msgs ...string) string { var out string if test == Fail { out = fmt.Sprintf("assert failed: %s", obex) } else { out = fmt.Sprintf("assert '%s' failed: %s", test, obex) } jmsgs := strings.Join(msgs, " ") if len(jmsgs) > 0 { out += " (" + jmsgs + ")" } return out } // EOF golib-4.24.2/audit/asserts_test.go000066400000000000000000000420701315505703200170630ustar00rootroot00000000000000// Tideland Go Library - Audit - Unit Tests // // Copyright (C) 2012-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package audit_test //-------------------- // IMPORTS //-------------------- import ( "errors" "io" "strings" "testing" "time" "github.com/tideland/golib/audit" ) //-------------------- // TESTS //-------------------- // TestAssertTrue tests the True() assertion. func TestAssertTrue(t *testing.T) { successfulAssert := successfulAssertion(t) failingAssert := failingAssertion(t) successfulAssert.True(true, "should not fail") failingAssert.True(false, "should fail and be logged") } // TestAssertFalse tests the False() assertion. func TestAssertFalse(t *testing.T) { successfulAssert := successfulAssertion(t) failingAssert := failingAssertion(t) successfulAssert.False(false, "should not fail") failingAssert.False(true, "should fail and be logged") } // TestAssertNil tests the Nil() assertion. func TestAssertNil(t *testing.T) { successfulAssert := successfulAssertion(t) failingAssert := failingAssertion(t) successfulAssert.Nil(nil, "should not fail") failingAssert.Nil("not nil", "should fail and be logged") } // TestAssertNotNil tests the NotNil() assertion. func TestAssertNotNil(t *testing.T) { successfulAssert := successfulAssertion(t) failingAssert := failingAssertion(t) successfulAssert.NotNil("not nil", "should not fail") failingAssert.NotNil(nil, "should fail and be logged") } // TestAssertEqual tests the Equal() assertion. func TestAssertEqual(t *testing.T) { successfulAssert := successfulAssertion(t) failingAssert := failingAssertion(t) m := map[string]int{"one": 1, "two": 2, "three": 3} now := time.Now() nowStr := now.Format(time.RFC3339Nano) nowParsedA, err := time.Parse(time.RFC3339Nano, nowStr) nowParsedB, err := time.Parse(time.RFC3339Nano, nowStr) successfulAssert.Nil(err, "should not fail") successfulAssert.Equal(nowParsedA, nowParsedB, "should not fail") successfulAssert.Equal(nil, nil, "should not fail") successfulAssert.Equal(true, true, "should not fail") successfulAssert.Equal(1, 1, "should not fail") successfulAssert.Equal("foo", "foo", "should not fail") successfulAssert.Equal(map[string]int{"one": 1, "three": 3, "two": 2}, m, "should not fail") failingAssert.Equal("one", 1, "should fail and be logged") failingAssert.Equal("two", "2", "should fail and be logged") } // TestAssertDifferent tests the Different() assertion. func TestAssertDifferent(t *testing.T) { successfulAssert := successfulAssertion(t) failingAssert := failingAssertion(t) m := map[string]int{"one": 1, "two": 2, "three": 3} successfulAssert.Different(nil, "nil", "should not fail") successfulAssert.Different("true", true, "should not fail") successfulAssert.Different(1, 2, "should not fail") successfulAssert.Different("foo", "bar", "should not fail") successfulAssert.Different(map[string]int{"three": 3, "two": 2}, m, "should not fail") failingAssert.Different("one", "one", "should fail and be logged") failingAssert.Different(2, 2, "should fail and be logged") } // TestAssertAbout tests the About() assertion. func TestAssertAbout(t *testing.T) { successfulAssert := successfulAssertion(t) failingAssert := failingAssertion(t) successfulAssert.About(1.0, 1.0, 0.0, "equal, no extend") successfulAssert.About(1.0, 1.0, 0.1, "equal, little extend") successfulAssert.About(0.9, 1.0, 0.1, "different, within bounds of extent") successfulAssert.About(1.1, 1.0, 0.1, "different, within bounds of extent") failingAssert.About(0.8, 1.0, 0.1, "different, out of bounds of extent") failingAssert.About(1.2, 1.0, 0.1, "different, out of bounds of extent") } // TestAssertRange tests the Range() assertion. func TestAssertRange(t *testing.T) { successfulAssert := successfulAssertion(t) failingAssert := failingAssertion(t) now := time.Now() successfulAssert.Range(byte(9), byte(1), byte(22), "byte in range") successfulAssert.Range(9, 1, 22, "int in range") successfulAssert.Range(9.0, 1.0, 22.0, "float64 in range") successfulAssert.Range('f', 'a', 'z', "rune in range") successfulAssert.Range("foo", "a", "zzzzz", "string in range") successfulAssert.Range(now, now.Add(-time.Hour), now.Add(time.Hour), "time in range") successfulAssert.Range(time.Minute, time.Second, time.Hour, "duration in range") successfulAssert.Range([]int{1, 2, 3}, 1, 10, "slice length in range") successfulAssert.Range([3]int{1, 2, 3}, 1, 10, "array length in range") successfulAssert.Range(map[int]int{3: 1, 2: 2, 1: 3}, 1, 10, "map length in range") failingAssert.Range(byte(1), byte(10), byte(20), "byte out of range") failingAssert.Range(1, 10, 20, "int out of range") failingAssert.Range(1.0, 10.0, 20.0, "float64 out of range") failingAssert.Range('a', 'x', 'z', "rune out of range") failingAssert.Range("aaa", "uuuuu", "zzzzz", "string out of range") failingAssert.Range(now, now.Add(time.Minute), now.Add(time.Hour), "time out of range") failingAssert.Range(time.Second, time.Minute, time.Hour, "duration in range") failingAssert.Range([]int{1, 2, 3}, 5, 10, "slice length out of range") failingAssert.Range([3]int{1, 2, 3}, 5, 10, "array length out of range") failingAssert.Range(map[int]int{3: 1, 2: 2, 1: 3}, 5, 10, "map length out of range") } // TestAssertContents tests the Contents() assertion. func TestAssertContents(t *testing.T) { successfulAssert := successfulAssertion(t) failingAssert := failingAssertion(t) successfulAssert.Contents("bar", "foobarbaz") successfulAssert.Contents(4711, []int{1, 2, 3, 4711, 5, 6, 7, 8, 9}) failingAssert.Contents(4711, "12345-4711-67890") failingAssert.Contents(4711, "foo") failingAssert.Contents(4711, []interface{}{1, "2", 3, "4711", 5, 6, 7, 8, 9}) successfulAssert.Contents("4711", []interface{}{1, "2", 3, "4711", 5, 6, 7, 8, 9}) failingAssert.Contents("foobar", []byte("the quick brown fox jumps over the lazy dog")) } // TestAssertContentsPrint test the visualization of failing content tests. func TestAssertContentsPrint(t *testing.T) { assert := audit.NewTestingAssertion(t, false) assert.Logf("printing of failing content tests") assert.Contents("foobar", []byte("the quick brown fox jumps over the lazy dog"), "test fails but passes, just visualization") assert.Contents([]byte("foobar"), []byte("the quick brown ..."), "test fails but passes, just visualization") } // TestOffsetPrint test the correct visualization when printing // with offset. func TestOffsetPrint(t *testing.T) { assert := audit.NewTestingAssertion(t, false) // Log should reference line below (167). failWithOffset(assert, "172") } // TestAssertSubstring tests the Substring() assertion. func TestAssertSubstring(t *testing.T) { successfulAssert := successfulAssertion(t) failingAssert := failingAssertion(t) successfulAssert.Substring("is assert", "this is assert test", "should not fail") successfulAssert.Substring("test", "this is 1 test", "should not fail") failingAssert.Substring("foo", "this is assert test", "should fail and be logged") failingAssert.Substring("this is assert test", "this is assert test", "should fail and be logged") } // TestAssertCase tests the Case() assertion. func TestAssertCase(t *testing.T) { successfulAssert := successfulAssertion(t) failingAssert := failingAssertion(t) successfulAssert.Case("FOO", true, "is all uppercase") successfulAssert.Case("foo", false, "is all lowercase") failingAssert.Case("Foo", true, "is mixed case") failingAssert.Case("Foo", false, "is mixed case") } // TestAssertMatch tests the Match() assertion. func TestAssertMatch(t *testing.T) { successfulAssert := successfulAssertion(t) failingAssert := failingAssertion(t) successfulAssert.Match("this is assert test", "this.*test", "should not fail") successfulAssert.Match("this is 1 test", "this is [0-9] test", "should not fail") failingAssert.Match("this is assert test", "foo", "should fail and be logged") failingAssert.Match("this is assert test", "this*test", "should fail and be logged") } // TestAssertErrorMatch tests the ErrorMatch() assertion. func TestAssertErrorMatch(t *testing.T) { successfulAssert := successfulAssertion(t) failingAssert := failingAssertion(t) err := errors.New("oops, an error") successfulAssert.ErrorMatch(err, "oops, an error", "should not fail") successfulAssert.ErrorMatch(err, "oops,.*", "should not fail") failingAssert.ErrorMatch(err, "foo", "should fail and be logged") } // TestAssertImplementor tests the Implementor() assertion. func TestAssertImplementor(t *testing.T) { successfulAssert := successfulAssertion(t) failingAssert := failingAssertion(t) var err error var w io.Writer successfulAssert.Implementor(errors.New("error test"), &err, "should not fail") failingAssert.Implementor("string test", &err, "should fail and be logged") failingAssert.Implementor(errors.New("error test"), &w, "should fail and be logged") } // TestAssertAssignable tests the Assignable() assertion. func TestAssertAssignable(t *testing.T) { successfulAssert := successfulAssertion(t) failingAssert := failingAssertion(t) successfulAssert.Assignable(1, 5, "should not fail") failingAssert.Assignable("one", 5, "should fail and be logged") } // TestAssertUnassignable tests the Unassignable() assertion. func TestAssertUnassignable(t *testing.T) { successfulAssert := successfulAssertion(t) failingAssert := failingAssertion(t) successfulAssert.Unassignable("one", 5, "should not fail") failingAssert.Unassignable(1, 5, "should fail and be logged") } // TestAssertEmpty tests the Empty() assertion. func TestAssertEmpty(t *testing.T) { successfulAssert := successfulAssertion(t) failingAssert := failingAssertion(t) successfulAssert.Empty("", "should not fail") successfulAssert.Empty([]bool{}, "should also not fail") failingAssert.Empty("not empty", "should fail and be logged") failingAssert.Empty([3]int{1, 2, 3}, "should also fail and be logged") failingAssert.Empty(true, "illegal type has to fail") } // TestAssertNotEmpty tests the NotEmpty() assertion. func TestAssertNotEmpty(t *testing.T) { successfulAssert := successfulAssertion(t) failingAssert := failingAssertion(t) successfulAssert.NotEmpty("not empty", "should not fail") successfulAssert.NotEmpty([3]int{1, 2, 3}, "should also not fail") failingAssert.NotEmpty("", "should fail and be logged") failingAssert.NotEmpty([]int{}, "should also fail and be logged") failingAssert.NotEmpty(true, "illegal type has to fail") } // TestAssertLength tests the Length() assertion. func TestAssertLength(t *testing.T) { successfulAssert := successfulAssertion(t) failingAssert := failingAssertion(t) successfulAssert.Length("", 0, "should not fail") successfulAssert.Length([]bool{true, false}, 2, "should also not fail") failingAssert.Length("not empty", 0, "should fail and be logged") failingAssert.Length([3]int{1, 2, 3}, 10, "should also fail and be logged") failingAssert.Length(true, 1, "illegal type has to fail") } // TestAssertPanics tests the Panics() assertion. func TestAssertPanics(t *testing.T) { successfulAssert := successfulAssertion(t) failingAssert := failingAssertion(t) successfulAssert.Panics(func() { panic("ouch") }, "should panic") failingAssert.Panics(func() { _ = 1 + 1 }, "should not panic") } // TestAssertPathExists tests the PathExists() assertion. func TestAssertPathExists(t *testing.T) { successfulAssert := successfulAssertion(t) failingAssert := failingAssertion(t) td := audit.NewTempDir(successfulAssert) successfulAssert.NotNil(td) defer td.Restore() successfulAssert.PathExists(td.String(), "temporary directory exists") failingAssert.PathExists("/this/path/will/hopefully/not/exist", "illegal path") } // TestAssertWait tests the wait testing. func TestAssertWait(t *testing.T) { successfulAssert := successfulAssertion(t) failingAssert := failingAssertion(t) sigc := audit.MakeSigChan() go func() { time.Sleep(50 * time.Millisecond) sigc <- true }() successfulAssert.Wait(sigc, true, 100*time.Millisecond, "should be true") go func() { time.Sleep(50 * time.Millisecond) sigc <- false }() failingAssert.Wait(sigc, true, 100*time.Millisecond, "should be false") go func() { time.Sleep(200 * time.Millisecond) sigc <- true }() failingAssert.Wait(sigc, true, 100*time.Millisecond, "should timeout") } // TestAssertWaitTested tests the wait tested testing. func TestAssertWaitTested(t *testing.T) { successfulAssert := successfulAssertion(t) failingAssert := failingAssertion(t) tester := func(v interface{}) error { b, ok := v.(bool) if !ok || b == false { return errors.New("illegal value") } return nil } sigc := audit.MakeSigChan() go func() { time.Sleep(50 * time.Millisecond) sigc <- true }() successfulAssert.WaitTested(sigc, tester, 100*time.Millisecond, "should be true") go func() { time.Sleep(50 * time.Millisecond) sigc <- false }() failingAssert.WaitTested(sigc, tester, 100*time.Millisecond, "should be false") go func() { time.Sleep(200 * time.Millisecond) sigc <- true }() failingAssert.WaitTested(sigc, tester, 100*time.Millisecond, "should timeout") } // TestAssertRetry tests the retry testing. func TestAssertRetry(t *testing.T) { successfulAssert := successfulAssertion(t) failingAssert := failingAssertion(t) i := 0 successfulAssert.Retry(func() bool { i++ return i == 5 }, 10, 10*time.Millisecond, "should succeed") failingAssert.Retry(func() bool { return false }, 10, 10*time.Millisecond, "should fail") } // TestAssertFail tests the fail testing. func TestAssertFail(t *testing.T) { failingAssert := failingAssertion(t) failingAssert.Fail("this should fail") } // TestTestingAssertion tests the testing assertion. func TestTestingAssertion(t *testing.T) { assert := audit.NewTestingAssertion(t, false) foo := func() {} bar := 4711 assert.Assignable(47, 11, "should not fail") assert.Assignable(foo, bar, "should fail (but not the test)") assert.Assignable(foo, bar) assert.Assignable(foo, bar, "this", "should", "fail", "too") } // TestPanicAssertion tests if the panic assertions panic when they fail. func TestPanicAssert(t *testing.T) { defer func() { if err := recover(); err != nil { t.Logf("panic worked: '%v'", err) } }() assert := audit.NewPanicAssertion() foo := func() {} assert.Assignable(47, 11, "should not fail") assert.Assignable(47, foo, "should fail") t.Errorf("should not be reached") } // TestValidationAssertion test the validation of data. func TestValidationAssertion(t *testing.T) { assert, failures := audit.NewValidationAssertion() assert.True(true, "should not fail") assert.True(false, "should fail") assert.Equal(1, 2, "should fail") if !failures.HasErrors() { t.Errorf("should have errors") } if len(failures.Errors()) != 2 { t.Errorf("wrong number of errors") } t.Log(failures.Error()) if len(failures.Details()) != 2 { t.Errorf("wrong number of details") } details := failures.Details() fn, l, f := details[0].Location() tt := details[0].Test() if fn != "asserts_test.go" || l != 421 || f != "TestValidationAssertion" { t.Errorf("wrong location of first detail: %d", l) } if tt != audit.True { t.Errorf("wrong test type of first detail: %v", tt) } fn, l, f = details[1].Location() tt = details[1].Test() if fn != "asserts_test.go" || l != 422 || f != "TestValidationAssertion" { t.Errorf("wrong location of second detail: %d", l) } if tt != audit.Equal { t.Errorf("wrong test type of second detail: %v", tt) } } // TestSetFailable tests the setting of the failable // to the one of a sub-test. func TestSetFailable(t *testing.T) { successfulAssert := successfulAssertion(t) failingAssert := failingAssertion(t) t.Run("success", func(t *testing.T) { defer successfulAssert.SetFailable(t)() successfulAssert.True(true) }) t.Run("fail", func(t *testing.T) { defer failingAssert.SetFailable(t)() failingAssert.True(false) }) } //-------------------- // META FAILER //-------------------- type metaFailer struct { t *testing.T fail bool } func (f *metaFailer) IncrCallstackOffset() func() { return func() {} } func (f *metaFailer) Logf(format string, args ...interface{}) { f.t.Logf(format, args...) } func (f *metaFailer) Fail(test audit.Test, obtained, expected interface{}, msgs ...string) bool { msg := strings.Join(msgs, " ") if msg != "" { msg = " [" + msg + "]" } format := "testing assert %q failed: '%v' (%v) <> '%v' (%v)" + msg obtainedVD := audit.ValueDescription(obtained) expectedVD := audit.ValueDescription(expected) f.Logf(format, test, obtained, obtainedVD, expected, expectedVD) if f.fail { f.t.FailNow() } return f.fail } //-------------------- // HELPER //-------------------- // failWithOffset checks the offset increment. func failWithOffset(assert audit.Assertion, line string) { restore := assert.IncrCallstackOffset() defer restore() assert.Fail("should fail referencing line " + line) } // successfulAssertion returns an assertion which doesn't expect a failing. func successfulAssertion(t *testing.T) audit.Assertion { return audit.NewAssertion(&metaFailer{t, true}) } // failingAssertion returns an assertion which only logs a failing but doesn't fail. func failingAssertion(t *testing.T) audit.Assertion { return audit.NewAssertion(&metaFailer{t, false}) } // EOF golib-4.24.2/audit/doc.go000066400000000000000000000021651315505703200151060ustar00rootroot00000000000000// Tideland Go Library - Audit // // Copyright (C) 2012-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. // Package audit of the Tideland Go Library helps writing convenient and // powerful unit tests. One part of it are assertions to compare expected // and obtained values. Additional text output for failing tests can be // added. // // In the beginning of a test function a new assertion instance is created with: // // assert := audit.NewTestingAssertion(t, shallFail) // // Inside the test an assert looks like: // // assert.Equal(obtained, expected, "obtained value has to be like expected") // // If shallFail is set to true a failing assert also lets fail the Go test. // Otherwise the failing is printed but the tests continue. Other functions // help with temporary directories, environment variables, and the generating // of test data. // // Additional helpers support in generating test data or work with the // environment, like temporary directories or environment variables, in // a safe and convenient way. package audit // EOF golib-4.24.2/audit/environments.go000066400000000000000000000072541315505703200170740ustar00rootroot00000000000000// Tideland Go Library - Audit // // Copyright (C) 2013-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package audit //-------------------- // IMPORTS //-------------------- import ( "crypto/rand" "fmt" "os" "path/filepath" ) //-------------------- // TEMPDIR //-------------------- // TempDir represents a temporary directory and possible subdirectories // for testing purposes. It simply is created with // // assert := audit.NewTestingAssertion(t, false) // td := audit.NewTempDir(assert) // defer td.Restore() // // tdName := td.String() // subName:= td.Mkdir("my", "sub", "directory") // // The deferred Restore() removes the temporary directory with all // contents. type TempDir struct { assert Assertion dir string } // NewTempDir creates a new temporary directory usable for direct // usage or further subdirectories. func NewTempDir(assert Assertion) *TempDir { id := make([]byte, 8) td := &TempDir{ assert: assert, } for i := 0; i < 256; i++ { _, err := rand.Read(id[:]) td.assert.Nil(err) dir := filepath.Join(os.TempDir(), fmt.Sprintf("gots-%x", id)) if err = os.Mkdir(dir, 0700); err == nil { td.dir = dir break } if td.dir == "" { msg := fmt.Sprintf("cannot create temporary directory %q: %v", td.dir, err) td.assert.Fail(msg) return nil } } return td } // Restore deletes the temporary directory and all contents. func (td *TempDir) Restore() { err := os.RemoveAll(td.dir) if err != nil { msg := fmt.Sprintf("cannot remove temporary directory %q: %v", td.dir, err) td.assert.Fail(msg) } } // Mkdir creates a potentially nested directory inside the // temporary directory. func (td *TempDir) Mkdir(name ...string) string { innerName := filepath.Join(name...) fullName := filepath.Join(td.dir, innerName) if err := os.MkdirAll(fullName, 0700); err != nil { msg := fmt.Sprintf("cannot create nested temporary directory %q: %v", fullName, err) td.assert.Fail(msg) } return fullName } // String returns the temporary directory. func (td *TempDir) String() string { return td.dir } //-------------------- // ENVVARS //-------------------- // EnvVars allows to change and restore environment variables. The // same variable can be set multiple times. Simply do // // assert := audit.NewTestingAssertion(t, false) // ev := audit.NewEnvVars(assert) // defer ev.Restore() // // ev.Set("MY_VAR", myValue) // // ... // // ev.Set("MY_VAR", anotherValue) // // The deferred Restore() resets to the original values. type EnvVars struct { assert Assertion vars map[string]string } // NewEnvVars creates func NewEnvVars(assert Assertion) *EnvVars { ev := &EnvVars{ assert: assert, vars: make(map[string]string), } return ev } // Restore resets all changed environment variables func (ev *EnvVars) Restore() { for key, value := range ev.vars { if err := os.Setenv(key, value); err != nil { msg := fmt.Sprintf("cannot reset environment variable %q: %v", key, err) ev.assert.Fail(msg) } } } // Set sets an environment variable to a new value. func (ev *EnvVars) Set(key, value string) { v := os.Getenv(key) _, ok := ev.vars[key] if !ok { ev.vars[key] = v } if err := os.Setenv(key, value); err != nil { msg := fmt.Sprintf("cannot set environment variable %q: %v", key, err) ev.assert.Fail(msg) } } // Unset unsets an environment variable. func (ev *EnvVars) Unset(key string) { v := os.Getenv(key) _, ok := ev.vars[key] if !ok { ev.vars[key] = v } if err := os.Unsetenv(key); err != nil { msg := fmt.Sprintf("cannot unset environment variable %q: %v", key, err) ev.assert.Fail(msg) } } // EOF golib-4.24.2/audit/environments_test.go000066400000000000000000000046401315505703200201270ustar00rootroot00000000000000// Tideland Go Library - Audit - Unit Tests // // Copyright (C) 2013-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package audit_test //-------------------- // IMPORTS //-------------------- import ( "os" "testing" "github.com/tideland/golib/audit" ) //-------------------- // TESTS //-------------------- // TestTempDirCreate tests the creation of temporary directories. func TestTempDirCreate(t *testing.T) { assert := audit.NewTestingAssertion(t, true) testDir := func(dir string) { fi, err := os.Stat(dir) assert.Nil(err) assert.True(fi.IsDir()) assert.Equal(fi.Mode().Perm(), os.FileMode(0700)) } td := audit.NewTempDir(assert) assert.NotNil(td) defer td.Restore() tds := td.String() assert.NotEmpty(tds) testDir(tds) sda := td.Mkdir("subdir", "foo") assert.NotEmpty(sda) testDir(sda) sdb := td.Mkdir("subdir", "bar") assert.NotEmpty(sdb) testDir(sdb) } // TestTempDirRestore tests the restoring of temporary created // directories. func TestTempDirRestore(t *testing.T) { assert := audit.NewTestingAssertion(t, true) td := audit.NewTempDir(assert) assert.NotNil(td) tds := td.String() fi, err := os.Stat(tds) assert.Nil(err) assert.True(fi.IsDir()) td.Restore() fi, err = os.Stat(tds) assert.ErrorMatch(err, "stat .* no such file or directory") } // TestEnvVarsSet tests the setting of temporary environment variables. func TestEnvVarsSet(t *testing.T) { assert := audit.NewTestingAssertion(t, true) testEnv := func(key, value string) { v := os.Getenv(key) assert.Equal(v, value) } ev := audit.NewEnvVars(assert) assert.NotNil(ev) defer ev.Restore() ev.Set("TESTING_ENV_A", "FOO") testEnv("TESTING_ENV_A", "FOO") ev.Set("TESTING_ENV_B", "BAR") testEnv("TESTING_ENV_B", "BAR") ev.Unset("TESTING_ENV_A") testEnv("TESTING_ENV_A", "") } // TestEnvVarsREstore tests the restoring of temporary set environment // variables. func TestEnvVarsRestore(t *testing.T) { assert := audit.NewTestingAssertion(t, true) testEnv := func(key, value string) { v := os.Getenv(key) assert.Equal(v, value) } ev := audit.NewEnvVars(assert) assert.NotNil(ev) path := os.Getenv("PATH") assert.NotEmpty(path) ev.Set("PATH", "/foo:/bar/bin") testEnv("PATH", "/foo:/bar/bin") ev.Set("PATH", "/bar:/foo:/yadda/bin") testEnv("PATH", "/bar:/foo:/yadda/bin") ev.Restore() testEnv("PATH", path) } // EOF golib-4.24.2/audit/failer.go000066400000000000000000000227311315505703200156040ustar00rootroot00000000000000// Tideland Go Library - Audit // // Copyright (C) 2012-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package audit //-------------------- // IMPORTS //-------------------- import ( "bytes" "errors" "fmt" "path" "runtime" "strings" "sync" "time" ) //-------------------- // FAILER //-------------------- // Failer describes a type controlling how an assert // reacts after a failure. type Failer interface { // IncrCallstackOffset increases the callstack offset for // the assertion output (see Assertion) and returns a function // for restoring. IncrCallstackOffset() func() // Logf can be used to display useful information during testing. Logf(format string, args ...interface{}) // Fail will be called if an assert fails. Fail(test Test, obtained, expected interface{}, msgs ...string) bool } // FailureDetail contains detailed information of a failure. type FailureDetail interface { // TImestamp tells when the failure has happened. Timestamp() time.Time // Locations returns file name, line number, and // function name of the failure. Location() (string, int, string) // Test tells which kind of test has failed. Test() Test // Error returns the failure as error. Error() error // Message return the optional test message. Message() string } // failureDetail implements the FailureDetail interface. type failureDetail struct { timestamp time.Time fileName string lineNumber int funcName string test Test err error message string } // TImestamp implements the FailureDetail interface. func (d *failureDetail) Timestamp() time.Time { return d.timestamp } // Locations implements the FailureDetail interface. func (d *failureDetail) Location() (string, int, string) { return d.fileName, d.lineNumber, d.funcName } // Test implements the FailureDetail interface. func (d *failureDetail) Test() Test { return d.test } // Error implements the FailureDetail interface. func (d *failureDetail) Error() error { return d.err } // Message implements the FailureDetail interface. func (d *failureDetail) Message() string { return d.message } // Failures collects the collected failures // of a validation assertion. type Failures interface { // HasErrors returns true, if assertion failures happened. HasErrors() bool // Details returns the collected details. Details() []FailureDetail // Errors returns the so far collected errors. Errors() []error // Error returns the collected errors as one error. Error() error } //-------------------- // PANIC FAILER //-------------------- // panicFailer reacts with a panic. type panicFailer struct{} // IncrCallstackOffset implements the Failer interface. func (f panicFailer) IncrCallstackOffset() func() { return func() {} } // Logf implements the Failer interface. func (f panicFailer) Logf(format string, args ...interface{}) { backendPrintf(format+"\n", args...) } // Fail implements the Failer interface. func (f panicFailer) Fail(test Test, obtained, expected interface{}, msgs ...string) bool { obex := obexString(test, obtained, expected) panic(failString(test, obex, msgs...)) } // NewPanicAssertion creates a new Assertion instance which panics if a test fails. func NewPanicAssertion() Assertion { return NewAssertion(&panicFailer{}) } //-------------------- // VALIDATION FAILER //-------------------- // validationFailer collects validation errors, e.g. when // validating form input data. type validationFailer struct { mux sync.Mutex offset int details []FailureDetail errs []error } // HasErrors implements the Failures interface. func (f *validationFailer) HasErrors() bool { f.mux.Lock() defer f.mux.Unlock() return len(f.errs) > 0 } // Details implements the Failures interface. func (f *validationFailer) Details() []FailureDetail { f.mux.Lock() defer f.mux.Unlock() return f.details } // Errors implements the Failures interface. func (f *validationFailer) Errors() []error { f.mux.Lock() defer f.mux.Unlock() return f.errs } // Error implements the Failures interface. func (f *validationFailer) Error() error { f.mux.Lock() defer f.mux.Unlock() strs := []string{} for i, err := range f.errs { strs = append(strs, fmt.Sprintf("[%d] %v", i, err)) } return errors.New(strings.Join(strs, " / ")) } // IncrCallstackOffset implements the Failer interface. func (f *validationFailer) IncrCallstackOffset() func() { f.mux.Lock() defer f.mux.Unlock() offset := f.offset f.offset++ return func() { f.mux.Lock() defer f.mux.Unlock() f.offset = offset } } // Logf implements the Failer interface. func (f *validationFailer) Logf(format string, args ...interface{}) { f.mux.Lock() defer f.mux.Unlock() pc, file, line, _ := runtime.Caller(f.offset) _, fileName := path.Split(file) funcNameParts := strings.Split(runtime.FuncForPC(pc).Name(), ".") funcNamePartsIdx := len(funcNameParts) - 1 funcName := funcNameParts[funcNamePartsIdx] prefix := fmt.Sprintf("%s:%d %s(): ", fileName, line, funcName) backendPrintf(prefix+format+"\n", args...) } // Fail implements the Failer interface. func (f *validationFailer) Fail(test Test, obtained, expected interface{}, msgs ...string) bool { f.mux.Lock() defer f.mux.Unlock() pc, file, line, _ := runtime.Caller(f.offset) _, fileName := path.Split(file) funcNameParts := strings.Split(runtime.FuncForPC(pc).Name(), ".") funcNamePartsIdx := len(funcNameParts) - 1 funcName := funcNameParts[funcNamePartsIdx] obex := obexString(test, obtained, expected) err := errors.New(failString(test, obex, msgs...)) detail := &failureDetail{ timestamp: time.Now(), fileName: fileName, lineNumber: line, funcName: funcName, test: test, err: err, message: strings.Join(msgs, " "), } f.details = append(f.details, detail) f.errs = append(f.errs, err) return false } // NewValidationAssertion creates a new Assertion instance which collections // validation failures. The returned Failures instance allows to test an access // them. func NewValidationAssertion() (Assertion, Failures) { vf := &validationFailer{ offset: 2, details: []FailureDetail{}, errs: []error{}, } return NewAssertion(vf), vf } //-------------------- // TESTING FAILER //-------------------- // Failable allows an assertion to signal a fail to an external instance // like testing.T or testing.B. type Failable interface { FailNow() } // testingFailer works together with the testing package of Go and // may signal the fail to it. type testingFailer struct { mux sync.Mutex failable Failable offset int shallFail bool } // IncrCallstackOffset implements the failer interface. func (f *testingFailer) IncrCallstackOffset() func() { f.mux.Lock() defer f.mux.Unlock() offset := f.offset f.offset++ return func() { f.mux.Lock() defer f.mux.Unlock() f.offset = offset } } // Logf implements the Failer interface. func (f *testingFailer) Logf(format string, args ...interface{}) { f.mux.Lock() defer f.mux.Unlock() pc, file, line, _ := runtime.Caller(f.offset) _, fileName := path.Split(file) funcNameParts := strings.Split(runtime.FuncForPC(pc).Name(), ".") funcNamePartsIdx := len(funcNameParts) - 1 funcName := funcNameParts[funcNamePartsIdx] prefix := fmt.Sprintf("%s:%d %s(): ", fileName, line, funcName) backendPrintf(prefix+format+"\n", args...) } // Fail implements the Failer interface. func (f *testingFailer) Fail(test Test, obtained, expected interface{}, msgs ...string) bool { f.mux.Lock() defer f.mux.Unlock() pc, file, line, _ := runtime.Caller(f.offset) _, fileName := path.Split(file) funcNameParts := strings.Split(runtime.FuncForPC(pc).Name(), ".") funcNamePartsIdx := len(funcNameParts) - 1 funcName := funcNameParts[funcNamePartsIdx] buffer := &bytes.Buffer{} fmt.Fprintf(buffer, "--------------------------------------------------------------------------------\n") if test == Fail { fmt.Fprintf(buffer, "%s:%d: Assert failed!\n\n", fileName, line) } else { fmt.Fprintf(buffer, "%s:%d: Assert '%s' failed!\n\n", fileName, line, test) } fmt.Fprintf(buffer, "Function...: %s()\n", funcName) switch test { case True, False, Nil, NotNil, Empty, NotEmpty, Panics: fmt.Fprintf(buffer, "Obtained...: %v\n", obtained) case Implementor, Assignable, Unassignable: fmt.Fprintf(buffer, "Obtained...: %v\n", ValueDescription(obtained)) fmt.Fprintf(buffer, "Expected...: %v\n", ValueDescription(expected)) case Contents: switch typedObtained := obtained.(type) { case string: fmt.Fprintf(buffer, "Part.......: %s\n", typedObtained) fmt.Fprintf(buffer, "Full.......: %s\n", expected) default: fmt.Fprintf(buffer, "Part.......: %v\n", obtained) fmt.Fprintf(buffer, "Full.......: %v\n", expected) } case Fail: default: fmt.Fprintf(buffer, "Obtained...: %v\n", obtained) fmt.Fprintf(buffer, "Expected...: %v\n", expected) } if len(msgs) > 0 { fmt.Fprintf(buffer, "Description: %s\n", strings.Join(msgs, "\n ")) } fmt.Fprintf(buffer, "--------------------------------------------------------------------------------\n") backendPrintf(buffer.String()) if f.shallFail { f.failable.FailNow() } return false } // NewTestingAssertion creates a new Assertion instance for use with the testing // package. The *testing.T has to be passed as failable, the first argument. // shallFail controls if a failing assertion also lets fail the Go test. func NewTestingAssertion(f Failable, shallFail bool) Assertion { return NewAssertion(&testingFailer{ failable: f, offset: 2, shallFail: shallFail, }) } // EOF golib-4.24.2/audit/generators.go000066400000000000000000000560311315505703200165130ustar00rootroot00000000000000// Tideland Go Library - Audit // // Copyright (C) 2013-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package audit //-------------------- // IMPORTS //-------------------- import ( "math/rand" "strings" "time" "unicode" "unicode/utf8" ) //-------------------- // CONSTANTS //-------------------- // patterns is used by the pattern generator and contains the // runes for a defined pattern identifier. var patterns = map[rune]string{ '0': "0123456789", '1': "123456789", 'o': "01234567", 'O': "01234567", 'h': "0123456789abcdef", 'H': "0123456789ABCDEF", 'a': "abcdefghijklmnopqrstuvwxyz", 'A': "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 'c': "bcdfghjklmnpqrstvwxyz", 'C': "BCDFGHJKLMNPQRSTVWXYZ", 'v': "aeiou", 'V': "AEIOU", 'z': "abcdefghijklmnopqrstuvwxyz0123456789", 'Z': "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", } //-------------------- // HELPERS //-------------------- // SimpleRand returns a random number generator with a source using // the the current time as seed. It's not the best random, but ok to // generate test data. func SimpleRand() *rand.Rand { seed := time.Now().UnixNano() source := rand.NewSource(seed) return rand.New(source) } // FixedRand returns a random number generator with a fixed source // so that tests using the generate functions can be repeated with // the same result. func FixedRand() *rand.Rand { source := rand.NewSource(42) return rand.New(source) } // ToUpperFirst returns the passed string with the first rune // converted to uppercase. func ToUpperFirst(s string) string { if s == "" { return "" } r, n := utf8.DecodeRuneInString(s) return string(unicode.ToUpper(r)) + s[n:] } // BuildEMail creates an e-mail address out of first and last // name and the domain. func BuildEMail(first, last, domain string) string { valid := make(map[rune]bool) for _, r := range "abcdefghijklmnopqrstuvwxyz0123456789-" { valid[r] = true } name := func(in string) string { out := []rune{} for _, r := range strings.ToLower(in) { if valid[r] { out = append(out, r) } } return string(out) } return name(first) + "." + name(last) + "@" + domain } // BuildTime returns the current time plus or minus the passed // offset formatted as string and as Time. The returned time is // the parsed formatted one to avoid parsing troubles in tests. func BuildTime(layout string, offset time.Duration) (string, time.Time) { t := time.Now().Add(offset) ts := t.Format(layout) tp, err := time.Parse(layout, ts) if err != nil { panic("cannot build time: " + err.Error()) } return ts, tp } //-------------------- // GENERATOR //-------------------- // Generator is responsible for generating different random data // based on a random number generator. type Generator struct { rand *rand.Rand } // NewGenerator returns a new generator using the passed random number // generator. func NewGenerator(rand *rand.Rand) *Generator { return &Generator{rand} } // Int generates an int between lo and hi including // those values. func (g *Generator) Int(lo, hi int) int { if lo == hi { return lo } if lo > hi { lo, hi = hi, lo } n := g.rand.Intn(hi - lo + 1) return lo + n } // Ints generates a slice of random ints. func (g *Generator) Ints(lo, hi, count int) []int { ints := make([]int, count) for i := 0; i < count; i++ { ints[i] = g.Int(lo, hi) } return ints } // Percent generates an int between 0 and 100. func (g *Generator) Percent() int { return g.Int(0, 100) } // FlipCoin returns true if the internal generated percentage is // equal or greater than the passed percentage. func (g *Generator) FlipCoin(percent int) bool { switch { case percent > 100: percent = 100 case percent < 0: percent = 0 } return g.Percent() >= percent } // OneByteOf returns one of the passed bytes. func (g *Generator) OneByteOf(values ...byte) byte { i := g.Int(0, len(values)-1) return values[i] } // OneRuneOf returns one of the runes of the passed string. func (g *Generator) OneRuneOf(values string) rune { runes := []rune(values) i := g.Int(0, len(runes)-1) return runes[i] } // OneIntOf returns one of the passed ints. func (g *Generator) OneIntOf(values ...int) int { i := g.Int(0, len(values)-1) return values[i] } // OneStringOf returns one of the passed strings. func (g *Generator) OneStringOf(values ...string) string { i := g.Int(0, len(values)-1) return values[i] } // OneDurationOf returns one of the passed durations. func (g *Generator) OneDurationOf(values ...time.Duration) time.Duration { i := g.Int(0, len(values)-1) return values[i] } // Word generates a random word. func (g *Generator) Word() string { return g.OneStringOf(words...) } // Words generates a slice of random words func (g *Generator) Words(count int) []string { words := make([]string, count) for i := 0; i < count; i++ { words[i] = g.Word() } return words } // LimitedWord generates a random word with a length between // lo and hi. func (g *Generator) LimitedWord(lo, hi int) string { length := g.Int(lo, hi) if length < MinWordLen { length = MinWordLen } if length > MaxWordLen { length = MaxWordLen } // Start anywhere in the list. pos := g.Int(0, wordsLen) for { if pos >= wordsLen { pos = 0 } if len(words[pos]) == length { return words[pos] } pos++ } } // Pattern generates a string based on a pattern. Here different // escape chars are replaced by according random chars while all // others are left as they are. Escape chars start with a caret (^) // followed by specializer. Those are: // // - ^ for a caret // - 0 for a number between 0 and 9 // - 1 for a number between 1 and 9 // - o for an octal number // - h for a hexadecimal number (lower-case) // - H for a hexadecimal number (upper-case) // - a for any char between a and z // - A for any char between A and Z // - c for a consonant (lower-case) // - C for a consonant (upper-case) // - v for a vowel (lower-case) // - V for a vowel (upper-case) func (g *Generator) Pattern(pattern string) string { result := []rune{} escaped := false for _, pr := range pattern { if !escaped { if pr == '^' { escaped = true } else { result = append(result, pr) } continue } // Escaped mode. ar := pr runes, ok := patterns[pr] if ok { ar = g.OneRuneOf(runes) } result = append(result, ar) escaped = false } return string(result) } // Sentence generates a sentence between 2 and 15 words // and possibly containing commas. func (g *Generator) Sentence() string { count := g.Int(2, 15) words := g.Words(count) words[0] = ToUpperFirst(words[0]) for i := 2; i < count-1; i++ { if g.FlipCoin(80) { words[i] += "," } } return strings.Join(words, " ") + "." } // Paragraph generates a paragraph between 2 and 10 sentences. func (g *Generator) Paragraph() string { count := g.Int(2, 10) sentences := make([]string, count) for i := 0; i < count; i++ { sentences[i] = g.Sentence() } return strings.Join(sentences, " ") } // Name generates a male or female name consisting out of first, // middle and last name. func (g *Generator) Name() (first, middle, last string) { if g.FlipCoin(50) { return g.FemaleName() } return g.MaleName() } // MaleName generates a male name consisting out of first, middle // and last name. func (g *Generator) MaleName() (first, middle, last string) { first = g.OneStringOf(maleFirstNames...) middle = g.OneStringOf(maleFirstNames...) if g.FlipCoin(80) { first += "-" + g.OneStringOf(maleFirstNames...) } else if g.FlipCoin(80) { middle += "-" + g.OneStringOf(maleFirstNames...) } last = g.OneStringOf(lastNames...) return } // FemaleName generates a female name consisting out of first, middle // and last name. func (g *Generator) FemaleName() (first, middle, last string) { first = g.OneStringOf(femaleFirstNames...) middle = g.OneStringOf(femaleFirstNames...) if g.FlipCoin(80) { first += "-" + g.OneStringOf(femaleFirstNames...) } else if g.FlipCoin(80) { middle += "-" + g.OneStringOf(femaleFirstNames...) } last = g.OneStringOf(lastNames...) return } // Domain generates domain out of name and top level domain. func (g *Generator) Domain() string { tld := g.OneStringOf(topLevelDomains...) if g.FlipCoin(80) { return g.LimitedWord(3, 5) + "-" + g.LimitedWord(3, 5) + "." + tld } return g.LimitedWord(3, 10) + "." + tld } // URL generates a http, https or ftp URL, some of the leading // to a file. func (g *Generator) URL() string { part := func() string { return g.LimitedWord(2, 8) } start := g.OneStringOf("http://www.", "http://blog.", "https://www.", "ftp://") ext := g.OneStringOf("html", "php", "jpg", "mp3", "txt") variant := g.Percent() switch { case variant < 20: return start + part() + "." + g.Domain() + "/" + part() + "." + ext case variant > 80: return start + part() + "." + g.Domain() + "/" + part() + "/" + part() + "." + ext default: return start + part() + "." + g.Domain() } } // EMail returns a random e-mail address. func (g *Generator) EMail() string { if g.FlipCoin(50) { first, _, last := g.MaleName() return BuildEMail(first, last, g.Domain()) } first, _, last := g.FemaleName() return BuildEMail(first, last, g.Domain()) } // Duration generates a duration between lo and hi including // those values. func (g *Generator) Duration(lo, hi time.Duration) time.Duration { if lo == hi { return lo } if lo > hi { lo, hi = hi, lo } n := g.rand.Int63n(int64(hi) - int64(lo) + 1) return lo + time.Duration(n) } // SleepOneOf chooses randomely one of the passed durations // and lets the goroutine sleep for this time. func (g *Generator) SleepOneOf(sleeps ...time.Duration) time.Duration { sleep := g.OneDurationOf(sleeps...) time.Sleep(sleep) return sleep } // Time generates a time between the given one and that time // plus the given duration. The result will have the passed // location. func (g *Generator) Time(loc *time.Location, base time.Time, dur time.Duration) time.Time { base = base.UTC() return base.Add(g.Duration(0, dur)).In(loc) } //-------------------- // GENERATOR DATA //-------------------- // words is a list of words based on lorem ipsum and own extensions. var words = []string{ "a", "ac", "accumsan", "accusam", "accusantium", "ad", "adipiscing", "alias", "aliquam", "aliquet", "aliquip", "aliquyam", "amet", "aenean", "ante", "aperiam", "arcu", "assum", "at", "auctor", "augue", "aut", "autem", "bibendum", "blandit", "blanditiis", "clita", "commodo", "condimentum", "congue", "consectetuer", "consequat", "consequatur", "consequuntur", "consetetur", "convallis", "cras", "cubilia", "culpa", "cum", "curabitur", "curae", "cursus", "dapibus", "delectus", "delenit", "diam", "dictum", "dictumst", "dignissim", "dis", "dolor", "dolore", "dolores", "doloremque", "doming", "donec", "dui", "duis", "duo", "ea", "eaque", "earum", "egestas", "eget", "eirmod", "eleifend", "elementum", "elit", "elitr", "enim", "eos", "erat", "eros", "errare", "error", "esse", "est", "et", "etiam", "eu", "euismod", "eum", "ex", "exerci", "exercitationem", "facer", "facili", "facilisis", "fames", "faucibus", "felis", "fermentum", "feugait", "feugiat", "fringilla", "fuga", "fusce", "gravida", "gubergren", "habitant", "habitasse", "hac", "harum", "hendrerit", "hic", "iaculis", "id", "illum", "illo", "imperdiet", "in", "integer", "interdum", "invidunt", "ipsa", "ipsum", "iriure", "iusto", "justo", "kasd", "kuga", "labore", "lacinia", "lacus", "laoreet", "laudantium", "lectus", "leo", "liber", "libero", "ligula", "lobortis", "laboriosam", "lorem", "luctus", "luptatum", "maecenas", "magna", "magni", "magnis", "malesuada", "massa", "mattis", "mauris", "mazim", "mea", "metus", "mi", "minim", "molestie", "mollis", "montes", "morbi", "mus", "nam", "nascetur", "natoque", "nec", "neque", "nesciunt", "netus", "nibh", "nihil", "nisi", "nisl", "no", "nobis", "non", "nonummy", "nonumy", "nostrud", "nulla", "nullam", "nunc", "odio", "odit", "officia", "option", "orci", "ornare", "parturient", "pede", "pellentesque", "penatibus", "perfendis", "perspiciatis", "pharetra", "phasellus", "placerat", "platea", "porta", "porttitor", "possim", "posuere", "praesent", "praesentium", "pretium", "primis", "proin", "pulvinar", "purus", "quam", "qui", "quia", "quis", "quisque", "quod", "rebum", "rhoncus", "riduculus", "risus", "rutrum", "sadipscing", "sagittis", "sanctus", "sapien", "scelerisque", "sea", "sed", "sem", "semper", "senectus", "sit", "sociis", "sodales", "sollicitudin", "soluta", "stet", "suscipit", "suspendisse", "takimata", "tation", "te", "tellus", "tempor", "tempora", "temporibus", "tempus", "tincidunt", "tortor", "totam", "tristique", "turpis", "ullam", "ullamcorper", "ultrices", "ultricies", "urna", "ut", "varius", "vehicula", "vel", "velit", "venenatis", "veniam", "vero", "vestibulum", "vitae", "vivamus", "viverra", "voluptua", "volutpat", "voluptatem", "vulputate", "voluptatem", "wisi", "wiskaleborium", "xantippe", "xeon", "yodet", "yggdrasil", "zypres", "zyril", } // wordsLen is the length of the word list. var wordsLen = len(words) const ( // MinWordLen is the length of the shortest word. MinWordLen = 1 // MaxWordLen is the length of the longest word. MaxWordLen = 14 ) // maleFirstNames is a list of popular male first names. var maleFirstNames = []string{ "Jacob", "Michael", "Joshua", "Matthew", "Ethan", "Andrew", "Daniel", "Anthony", "Christopher", "Joseph", "William", "Alexander", "Ryan", "David", "Nicholas", "Tyler", "James", "John", "Jonathan", "Nathan", "Samuel", "Christian", "Noah", "Dylan", "Benjamin", "Logan", "Brandon", "Gabriel", "Zachary", "Jose", "Elijah", "Angel", "Kevin", "Jack", "Caleb", "Justin", "Austin", "Evan", "Robert", "Thomas", "Luke", "Mason", "Aidan", "Jackson", "Isaiah", "Jordan", "Gavin", "Connor", "Aiden", "Isaac", "Jason", "Cameron", "Hunter", "Jayden", "Juan", "Charles", "Aaron", "Lucas", "Luis", "Owen", "Landon", "Diego", "Brian", "Adam", "Adrian", "Kyle", "Eric", "Ian", "Nathaniel", "Carlos", "Alex", "Bryan", "Jesus", "Julian", "Sean", "Carter", "Hayden", "Jeremiah", "Cole", "Brayden", "Wyatt", "Chase", "Steven", "Timothy", "Dominic", "Sebastian", "Xavier", "Jaden", "Jesse", "Devin", "Seth", "Antonio", "Richard", "Miguel", "Colin", "Cody", "Alejandro", "Caden", "Blake", "Carson", } // maleFirstNames is a list of popular female first names. var femaleFirstNames = []string{ "Emily", "Emma", "Madison", "Abigail", "Olivia", "Isabella", "Hannah", "Samantha", "Ava", "Ashley", "Sophia", "Elizabeth", "Alexis", "Grace", "Sarah", "Alyssa", "Mia", "Natalie", "Chloe", "Brianna", "Lauren", "Ella", "Anna", "Taylor", "Kayla", "Hailey", "Jessica", "Victoria", "Jasmine", "Sydney", "Julia", "Destiny", "Morgan", "Kaitlyn", "Savannah", "Katherine", "Alexandra", "Rachel", "Lily", "Megan", "Kaylee", "Jennifer", "Angelina", "Makayla", "Allison", "Brooke", "Maria", "Trinity", "Lillian", "Mackenzie", "Faith", "Sofia", "Riley", "Haley", "Gabrielle", "Nicole", "Kylie", "Katelyn", "Zoe", "Paige", "Gabriella", "Jenna", "Kimberly", "Stephanie", "Alexa", "Avery", "Andrea", "Leah", "Madeline", "Nevaeh", "Evelyn", "Maya", "Mary", "Michelle", "Jada", "Sara", "Audrey", "Brooklyn", "Vanessa", "Amanda", "Ariana", "Rebecca", "Caroline", "Amelia", "Mariah", "Jordan", "Jocelyn", "Arianna", "Isabel", "Marissa", "Autumn", "Melanie", "Aaliyah", "Gracie", "Claire", "Isabelle", "Molly", "Mya", "Diana", "Katie", } // lastNames is a list of popular last names. var lastNames = []string{ "Smith", "Johnson", "Williams", "Brown", "Jones", "Miller", "Davis", "Garcia", "Rodriguez", "Wilson", "Martinez", "Anderson", "Taylor", "Thomas", "Hernandez", "Moore", "Martin", "Jackson", "Thompson", "White", "Lopez", "Lee", "Gonzalez", "Harris", "Clark", "Lewis", "Robinson", "Walker", "Perez", "Hall", "Young", "Allen", "Sanchez", "Wright", "King", "Scott", "Green", "Baker", "Adams", "Nelson", "Hill", "Ramirez", "Campbell", "Mitchell", "Roberts", "Carter", "Phillips", "Evans", "Turner", "Torres", "Parker", "Collins", "Edwards", "Stewart", "Flores", "Morris", "Nguyen", "Murphy", "Rivera", "Cook", "Rogers", "Morgan", "Peterson", "Cooper", "Reed", "Bailey", "Bell", "Gomez", "Kelly", "Howard", "Ward", "Cox", "Diaz", "Richardson", "Wood", "Watson", "Brooks", "Bennett", "Gray", "James", "Reyes", "Cruz", "Hughes", "Price", "Myers", "Long", "Foster", "Sanders", "Ross", "Morales", "Powell", "Sullivan", "Russell", "Ortiz", "Jenkins", "Gutierrez", "Perry", "Butler", "Barnes", "Fisher", "Henderson", "Coleman", "Simmons", "Patterson", "Jordan", "Reynolds", "Hamilton", "Graham", "Kim", "Gonzales", "Alexander", "Ramos", "Wallace", "Griffin", "West", "Cole", "Hayes", "Chavez", "Gibson", "Bryant", "Ellis", "Stevens", "Murray", "Ford", "Marshall", "Owens", "McDonald", "Harrison", "Ruiz", "Kennedy", "Wells", "Alvarez", "Woods", "Mendoza", "Castillo", "Olson", "Webb", "Washington", "Tucker", "Freeman", "Burns", "Henry", "Vasquez", "Snyder", "Simpson", "Crawford", "Jimenez", "Porter", "Mason", "Shaw", "Gordon", "Wagner", "Hunter", "Romero", "Hicks", "Dixon", "Hunt", "Palmer", "Robertson", "Black", "Holmes", "Stone", "Meyer", "Boyd", "Mills", "Warren", "Fox", "Rose", "Rice", "Moreno", "Schmidt", "Patel", "Ferguson", "Nichols", "Herrera", "Medina", "Ryan", "Fernandez", "Weaver", "Daniels", "Stephens", "Gardner", "Payne", "Kelley", "Dunn", "Pierce", "Arnold", "Tran", "Spencer", "Peters", "Hawkins", "Grant", "Hansen", "Castro", "Hoffman", "Hart", "Elliott", "Cunningham", "Knight", "Bradley", "Carroll", "Hudson", "Duncan", "Armstrong", "Berry", "Andrews", "Johnston", "Ray", "Lane", "Riley", "Carpenter", "Perkins", "Aguilar", "Silva", "Richards", "Willis", "Matthews", "Chapman", "Lawrence", "Garza", "Vargas", "Watkins", "Wheeler", "Larson", "Carlson", "Harper", "George", "Greene", "Burke", "Guzman", "Morrison", "Munoz", "Jacobs", "Obrien", "Lawson", "Franklin", "Lynch", "Bishop", "Carr", "Salazar", "Austin", "Mendez", "Gilbert", "Jensen", "Williamson", "Montgomery", "Harvey", "Oliver", "Howell", "Dean", "Hanson", "Weber", "Garrett", "Sims", "Burton", "Fuller", "Soto", "McCoy", "Welch", "Chen", "Schultz", "Walters", "Reid", "Fields", "Walsh", "Little", "Fowler", "Bowman", "Davidson", "May", "Day", "Schneider", "Newman", "Brewer", "Lucas", "Holland", "Wong", "Banks", "Santos", "Curtis", "Pearson", "Delgado", "Valdez", "Pena", "Rios", "Douglas", "Sandoval", "Barrett", "Hopkins", "Keller", "Guerrero", "Stanley", "Bates", "Alvarado", "Beck", "Ortega", "Wade", "Estrada", "Contreras", "Barnett", "Caldwell", "Santiago", "Lambert", "Powers", "Chambers", "Nunez", "Craig", "Leonard", "Lowe", "Rhodes", "Byrd", "Gregory", "Shelton", "Frazier", "Becker", "Maldonado", "Fleming", "Vega", "Sutton", "Cohen", "Jennings", "Parks", "McDaniel", "Watts", "Barker", "Norris", "Vaughn", "Vazquez", "Holt", "Schwartz", "Steele", "Benson", "Neal", "Dominguez", "Horton", "Terry", "Wolfe", "Hale", "Lyons", "Graves", "Haynes", "Miles", "Park", "Warner", "Padilla", "Bush", "Thornton", "McCarthy", "Mann", "Zimmerman", "Erickson", "Fletcher", "McKinney", "Page", "Dawson", "Joseph", "Marquez", "Reeves", "Klein", "Espinoza", "Baldwin", "Moran", "Love", "Robbins", "Higgins", "Ball", "Cortez", "Le", "Griffith", "Bowen", "Sharp", "Cummings", "Ramsey", "Hardy", "Swanson", "Barber", "Acosta", "Luna", "Chandler", "Blair", "Daniel", "Cross", "Simon", "Dennis", "Oconnor", "Quinn", "Gross", "Navarro", "Moss", "Fitzgerald", "Doyle", "McLaughlin", "Rojas", "Rodgers", "Stevenson", "Singh", "Yang", "Figueroa", "Harmon", "Newton", "Paul", "Manning", "Garner", "McGee", "Reese", "Francis", "Burgess", "Adkins", "Goodman", "Curry", "Brady", "Christensen", "Potter", "Walton", "Goodwin", "Mullins", "Molina", "Webster", "Fischer", "Campos", "Avila", "Sherman", "Todd", "Chang", "Blake", "Malone", "Wolf", "Hodges", "Juarez", "Gill", "Farmer", "Hines", "Gallagher", "Duran", "Hubbard", "Cannon", "Miranda", "Wang", "Saunders", "Tate", "Mack", "Hammond", "Carrillo", "Townsend", "Wise", "Ingram", "Barton", "Mejia", "Ayala", "Schroeder", "Hampton", "Rowe", "Parsons", "Frank", "Waters", "Strickland", "Osborne", "Maxwell", "Chan", "Deleon", "Norman", "Harrington", "Casey", "Patton", "Logan", "Bowers", "Mueller", "Glover", "Floyd", "Hartman", "Buchanan", "Cobb", "French", "Kramer", "McCormick", "Clarke", "Tyler", "Gibbs", "Moody", "Conner", "Sparks", "McGuire", "Leon", "Bauer", "Norton", "Pope", "Flynn", "Hogan", "Robles", "Salinas", "Yates", "Lindsey", "Lloyd", "Marsh", "McBride", "Owen", "Solis", "Pham", "Lang", "Pratt", "Lara", "Brock", "Ballard", "Trujillo", "Shaffer", "Drake", "Roman", "Aguirre", "Morton", "Stokes", "Lamb", "Pacheco", "Patrick", "Cochran", "Shepherd", "Cain", "Burnett", "Hess", "Li", "Cervantes", "Olsen", "Briggs", "Ochoa", "Cabrera", "Velasquez", "Montoya", "Roth", "Meyers", "Cardenas", "Fuentes", "Weiss", "Hoover", "Wilkins", "Nicholson", "Underwood", "Short", "Carson", "Morrow", "Colon", "Holloway", "Summers", "Bryan", "Petersen", "McKenzie", "Serrano", "Wilcox", "Carey", "Clayton", "Poole", "Calderon", "Gallegos", "Greer", "Rivas", "Guerra", "Decker", "Collier", "Wall", "Whitaker", "Bass", "Flowers", "Davenport", "Conley", "Houston", "Huff", "Copeland", "Hood", "Monroe", "Massey", "Roberson", "Combs", "Franco", "Larsen", "Pittman", "Randall", "Skinner", "Wilkinson", "Kirby", "Cameron", "Bridges", "Anthony", "Richard", "Kirk", "Bruce", "Singleton", "Mathis", "Bradford", "Boone", "Abbott", "Charles", "Allison", "Sweeney", "Atkinson", "Horn", "Jefferson", "Rosales", "York", "Christian", "Phelps", "Farrell", "Castaneda", "Nash", "Dickerson", "Bond", "Wyatt", "Foley", "Chase", "Gates", "Vincent", "Mathews", "Hodge", "Garrison", "Trevino", "Villarreal", "Heath", "Dalton", "Valencia", "Callahan", "Hensley", "Atkins", "Huffman", "Roy", "Boyer", "Shields", "Lin", "Hancock", "Grimes", "Glenn", "Cline", "Delacruz", "Camacho", "Dillon", "Parrish", "O'Neill", "Melton", "Booth", "Kane", "Berg", "Harrell", "Pitts", "Savage", "Wiggins", "Brennan", "Salas", "Marks", "Russo", "Sawyer", "Baxter", "Golden", "Hutchinson", "Liu", "Walter", "McDowell", "Wiley", "Rich", "Humphrey", "Johns", "Koch", "Suarez", "Hobbs", "Beard", "Gilmore", "Ibarra", "Keith", "Macias", "Khan", "Andrade", "Ware", "Stephenson", "Henson", "Wilkerson", "Dyer", "McClure", "Blackwell", "Mercado", "Tanner", "Eaton", "Clay", "Barron", "Beasley", "O'Neal", "Preston", "Small", "Wu", "Zamora", "Macdonald", "Vance", "Snow", "McClain", "Stafford", "Orozco", "Barry", "English", "Shannon", "Kline", "Jacobson", "Woodard", "Huang", "Kemp", "Mosley", "Prince", "Merritt", "Hurst", "Villanueva", "Roach", "Nolan", "Lam", "Yoder", "McCullough", "Lester", "Santana", "Valenzuela", "Winters", "Barrera", "Leach", "Orr", "Berger", "McKee", "Strong", "Conway", "Stein", "Whitehead", "Bullock", "Escobar", "Knox", "Meadows", "Solomon", "Velez", "Odonnell", "Kerr", "Stout", "Blankenship", "Browning", "Kent", "Lozano", "Bartlett", "Pruitt", "Buck", "Barr", "Gaines", "Durham", "Gentry", "McIntyre", "Sloan", "Melendez", "Rocha", "Herman", "Sexton", "Moon", "Hendricks", "Rangel", } // topLevelDomains is a number of existing top level domains. var topLevelDomains = []string{"asia", "at", "au", "biz", "ch", "cn", "com", "de", "es", "eu", "fr", "gr", "guru", "info", "it", "mobi", "name", "net", "org", "pl", "ru", "tel", "tv", "uk", "us", } // EOF golib-4.24.2/audit/generators_test.go000066400000000000000000000201711315505703200175460ustar00rootroot00000000000000// Tideland Go Library - Audit - Unit Tests // // Copyright (C) 2013-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the NewGenerator BSD license. package audit_test //-------------------- // IMPORTS //-------------------- import ( "fmt" "strings" "testing" "time" "github.com/tideland/golib/audit" ) //-------------------- // TESTS //-------------------- // TestBuildDate tests the generation of dates. func TestBuildDate(t *testing.T) { assert := audit.NewTestingAssertion(t, true) layouts := []string{ time.ANSIC, time.UnixDate, time.RubyDate, time.RFC822, time.RFC822Z, time.RFC850, time.RFC1123, time.RFC1123Z, time.RFC3339, time.RFC3339Nano, time.Kitchen, time.Stamp, time.StampMilli, time.StampMicro, time.StampNano, } for _, layout := range layouts { ts, t := audit.BuildTime(layout, 0) tsp, err := time.Parse(layout, ts) assert.Nil(err) assert.Equal(t, tsp) ts, t = audit.BuildTime(layout, -30*time.Minute) tsp, err = time.Parse(layout, ts) assert.Nil(err) assert.Equal(t, tsp) ts, t = audit.BuildTime(layout, time.Hour) tsp, err = time.Parse(layout, ts) assert.Nil(err) assert.Equal(t, tsp) } } // TestInts tests the generation of ints. func TestInts(t *testing.T) { assert := audit.NewTestingAssertion(t, true) gen := audit.NewGenerator(audit.FixedRand()) // Test individual ints. for i := 0; i < 10000; i++ { lo := gen.Int(-100, 100) hi := gen.Int(-100, 100) n := gen.Int(lo, hi) if hi < lo { lo, hi = hi, lo } assert.True(lo <= n && n <= hi) } // Test int slices. ns := gen.Ints(0, 500, 10000) assert.Length(ns, 10000) for _, n := range ns { assert.True(n >= 0 && n <= 500) } // Test the generation of percent. for i := 0; i < 10000; i++ { p := gen.Percent() assert.True(p >= 0 && p <= 100) } // Test the flipping of coins. ct := 0 cf := 0 for i := 0; i < 10000; i++ { c := gen.FlipCoin(50) if c { ct++ } else { cf++ } } assert.About(float64(ct), float64(cf), 500) } // TestOneOf tests the generation of selections. func TestOneOf(t *testing.T) { assert := audit.NewTestingAssertion(t, true) gen := audit.NewGenerator(audit.FixedRand()) for i := 0; i < 10000; i++ { b := gen.OneByteOf(1, 2, 3, 4, 5) assert.True(b >= 1 && b <= 5) r := gen.OneRuneOf("abcdef") assert.True(r >= 'a' && r <= 'f') n := gen.OneIntOf(1, 2, 3, 4, 5) assert.True(n >= 1 && n <= 5) s := gen.OneStringOf("one", "two", "three", "four", "five") assert.Substring(s, "one/two/three/four/five") d := gen.OneDurationOf(1*time.Second, 2*time.Second, 3*time.Second) assert.True(d >= 1*time.Second && d <= 3*time.Second) } } // TestWords tests the generation of words. func TestWords(t *testing.T) { assert := audit.NewTestingAssertion(t, true) gen := audit.NewGenerator(audit.FixedRand()) // Test single words. for i := 0; i < 10000; i++ { w := gen.Word() for _, r := range w { assert.True(r >= 'a' && r <= 'z') } } // Test limited words. for i := 0; i < 10000; i++ { lo := gen.Int(audit.MinWordLen, audit.MaxWordLen) hi := gen.Int(audit.MinWordLen, audit.MaxWordLen) w := gen.LimitedWord(lo, hi) wl := len(w) if hi < lo { lo, hi = hi, lo } assert.True(lo <= wl && wl <= hi, info("WL %d LO %d HI %d", wl, lo, hi)) } } // TestPattern tests the generation based on patterns. func TestPattern(t *testing.T) { assert := audit.NewTestingAssertion(t, true) gen := audit.NewGenerator(audit.FixedRand()) assertPattern := func(pattern, runes string) { set := make(map[rune]bool) for _, r := range runes { set[r] = true } for i := 0; i < 10; i++ { result := gen.Pattern(pattern) for _, r := range result { assert.True(set[r], pattern, result, runes) } } } assertPattern("^^", "^") assertPattern("^0^0^0^0^0", "0123456789") assertPattern("^1^1^1^1^1", "123456789") assertPattern("^o^o^o^o^o", "01234567") assertPattern("^h^h^h^h^h", "0123456789abcdef") assertPattern("^H^H^H^H^H", "0123456789ABCDEF") assertPattern("^a^a^a^a^a", "abcdefghijklmnopqrstuvwxyz") assertPattern("^A^A^A^A^A", "ABCDEFGHIJKLMNOPQRSTUVWXYZ") assertPattern("^c^c^c^c^c", "bcdfghjklmnpqrstvwxyz") assertPattern("^C^C^C^C^C", "BCDFGHJKLMNPQRSTVWXYZ") assertPattern("^v^v^v^v^v", "aeiou") assertPattern("^V^V^V^V^V", "AEIOU") assertPattern("^z^z^z^z^z", "abcdefghijklmnopqrstuvwxyz0123456789") assertPattern("^Z^Z^Z^Z^Z", "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") assertPattern("^1^0.^0^0^0,^0^0 €", "0123456789 .,€") } // TestText tests the generation of text. func TestText(t *testing.T) { assert := audit.NewTestingAssertion(t, true) gen := audit.NewGenerator(audit.FixedRand()) for i := 0; i < 10000; i++ { s := gen.Sentence() ws := strings.Split(s, " ") lws := len(ws) assert.True(2 <= lws && lws <= 15, info("SL: %d", lws)) assert.True('A' <= s[0] && s[0] <= 'Z', info("SUC: %v", s[0])) } for i := 0; i < 10000; i++ { p := gen.Paragraph() ss := strings.Split(p, ". ") lss := len(ss) assert.True(2 <= lss && lss <= 10, info("PL: %d", lss)) for _, s := range ss { ws := strings.Split(s, " ") lws := len(ws) assert.True(2 <= lws && lws <= 15, info("PSL: %d", lws)) assert.True('A' <= s[0] && s[0] <= 'Z', info("PSUC: %v", s[0])) } } } // TestName tests the generation of names. func TestName(t *testing.T) { assert := audit.NewTestingAssertion(t, true) gen := audit.NewGenerator(audit.FixedRand()) assert.Equal(audit.ToUpperFirst("yadda"), "Yadda") for i := 0; i < 10000; i++ { first, middle, last := gen.Name() assert.Match(first, `[A-Z][a-z]+(-[A-Z][a-z]+)?`) assert.Match(middle, `[A-Z][a-z]+(-[A-Z][a-z]+)?`) assert.Match(last, `[A-Z]['a-zA-Z]+`) first, middle, last = gen.MaleName() assert.Match(first, `[A-Z][a-z]+(-[A-Z][a-z]+)?`) assert.Match(middle, `[A-Z][a-z]+(-[A-Z][a-z]+)?`) assert.Match(last, `[A-Z]['a-zA-Z]+`) first, middle, last = gen.FemaleName() assert.Match(first, `[A-Z][a-z]+(-[A-Z][a-z]+)?`) assert.Match(middle, `[A-Z][a-z]+(-[A-Z][a-z]+)?`) assert.Match(last, `[A-Z]['a-zA-Z]+`) } } // TestDomain tests the generation of domains. func TestDomain(t *testing.T) { assert := audit.NewTestingAssertion(t, true) gen := audit.NewGenerator(audit.FixedRand()) for i := 0; i < 00100; i++ { domain := gen.Domain() assert.Match(domain, `^[a-z0-9.-]+\.[a-z]{2,4}$`) } } // TestURL tests the generation of URLs. func TestURL(t *testing.T) { assert := audit.NewTestingAssertion(t, true) gen := audit.NewGenerator(audit.FixedRand()) for i := 0; i < 10000; i++ { url := gen.URL() assert.Match(url, `(http|ftp|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?`) } } // TestEMail tests the generation of e-mail addresses. func TestEMail(t *testing.T) { assert := audit.NewTestingAssertion(t, true) gen := audit.NewGenerator(audit.FixedRand()) for i := 0; i < 10000; i++ { addr := gen.EMail() assert.Match(addr, `^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$`) } } // TestTimes tests the generation of durations and times. func TestTimes(t *testing.T) { assert := audit.NewTestingAssertion(t, true) gen := audit.NewGenerator(audit.FixedRand()) for i := 0; i < 10000; i++ { // Test durations. lo := gen.Duration(time.Second, time.Minute) hi := gen.Duration(time.Second, time.Minute) d := gen.Duration(lo, hi) if hi < lo { lo, hi = hi, lo } assert.True(lo <= d && d <= hi, "High / Low") // Test times. loc := time.Local now := time.Now() dur := gen.Duration(24*time.Hour, 30*24*time.Hour) t := gen.Time(loc, now, dur) assert.True(t.Equal(now) || t.After(now), "Equal or after now") assert.True(t.Before(now.Add(dur)) || t.Equal(now.Add(dur)), "Before or equal now plus duration") } sleeps := map[int]time.Duration{ 1: 1 * time.Millisecond, 2: 2 * time.Millisecond, 3: 3 * time.Millisecond, 4: 4 * time.Millisecond, 5: 5 * time.Millisecond, } for i := 0; i < 1000; i++ { sleep := gen.SleepOneOf(sleeps[1], sleeps[2], sleeps[3], sleeps[4], sleeps[5]) s := int(sleep) / 1000000 _, ok := sleeps[s] assert.True(ok, "Chosen duration is one the arguments") } } //-------------------- // HELPER //-------------------- var info = fmt.Sprintf // EOF golib-4.24.2/audit/printer.go000066400000000000000000000052501315505703200160220ustar00rootroot00000000000000// Tideland Go Library - Audit // // Copyright (C) 2012-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package audit //-------------------- // IMPORTS //-------------------- import ( "fmt" "reflect" "sync" ) //-------------------- // PRINTER //-------------------- var testNames = []string{ Invalid: "invalid", True: "true", False: "false", Nil: "nil", NotNil: "not nil", Equal: "equal", Different: "different", Contents: "contents", About: "about", Range: "range", Substring: "substring", Case: "case", Match: "match", ErrorMatch: "error match", Implementor: "implementor", Assignable: "assignable", Unassignable: "unassignable", Empty: "empty", NotEmpty: "not empty", Length: "length", Panics: "panics", PathExists: "path exists", Wait: "wait", Retry: "retry", Fail: "fail", } func (t Test) String() string { if int(t) < len(testNames) { return testNames[t] } return "invalid" } // ValueDescription returns a description of a value as string. func ValueDescription(value interface{}) string { rvalue := reflect.ValueOf(value) kind := rvalue.Kind() switch kind { case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice: return kind.String() + " of " + rvalue.Type().Elem().String() case reflect.Func: return kind.String() + " " + rvalue.Type().Name() + "()" case reflect.Interface, reflect.Struct: return kind.String() + " " + rvalue.Type().Name() case reflect.Ptr: return kind.String() + " to " + rvalue.Type().Elem().String() } // Default. return kind.String() } // Printer allows to switch between different outputs. type Printer interface { // Printf prints a formatted information. Printf(format string, args ...interface{}) } // printerBackend is the globally printer used during // the assertions. var ( printerBackend Printer = &fmtPrinter{} mux sync.Mutex ) // fmtPrinter uses the standard fmt package for printing. type fmtPrinter struct{} // Printf implements the Printer interface. func (p *fmtPrinter) Printf(format string, args ...interface{}) { fmt.Printf(format, args...) } // SetPrinter sets a new global printer and returns the // current one. func SetPrinter(p Printer) Printer { mux.Lock() defer mux.Unlock() cp := printerBackend printerBackend = p return cp } // backendPrintf uses the printer backend for output. It is used // in the types below. func backendPrintf(format string, args ...interface{}) { mux.Lock() defer mux.Unlock() printerBackend.Printf(format, args...) } // EOF golib-4.24.2/audit/tester.go000066400000000000000000000162301315505703200156450ustar00rootroot00000000000000// Tideland Go Library - Audit // // Copyright (C) 2012-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package audit //-------------------- // IMPORTS //-------------------- import ( "bytes" "errors" "fmt" "os" "reflect" "regexp" "strings" "time" ) //-------------------- // TESTER //-------------------- // Tester is a helper which can be used in own Assertion implementations. type Tester struct{} // IsTrue checks if obtained is true. func (t Tester) IsTrue(obtained bool) bool { return obtained == true } // IsNil checks if obtained is nil in a safe way. func (t Tester) IsNil(obtained interface{}) bool { if obtained == nil { // Standard test. return true } // Some types have to be tested via reflection. value := reflect.ValueOf(obtained) kind := value.Kind() switch kind { case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: return value.IsNil() } return false } // IsEqual checks if obtained and expected are equal. func (t Tester) IsEqual(obtained, expected interface{}) bool { return reflect.DeepEqual(obtained, expected) } // IsAbout checks if obtained and expected are to a given extent almost equal. func (t Tester) IsAbout(obtained, expected, extent float64) bool { if extent < 0.0 { extent = extent * (-1) } low := expected - extent high := expected + extent return low <= obtained && obtained <= high } // IsInRange checks for range assertions func (t Tester) IsInRange(obtained, low, high interface{}) (bool, error) { // First standard types. switch o := obtained.(type) { case byte: l, lok := low.(byte) h, hok := high.(byte) if !lok && !hok { return false, errors.New("low and/or high are no byte") } return l <= o && o <= h, nil case int: l, lok := low.(int) h, hok := high.(int) if !lok && !hok { return false, errors.New("low and/or high are no int") } return l <= o && o <= h, nil case float64: l, lok := low.(float64) h, hok := high.(float64) if !lok && !hok { return false, errors.New("low and/or high are no float64") } return l <= o && o <= h, nil case rune: l, lok := low.(rune) h, hok := high.(rune) if !lok && !hok { return false, errors.New("low and/or high are no rune") } return l <= o && o <= h, nil case string: l, lok := low.(string) h, hok := high.(string) if !lok && !hok { return false, errors.New("low and/or high are no string") } return l <= o && o <= h, nil case time.Time: l, lok := low.(time.Time) h, hok := high.(time.Time) if !lok && !hok { return false, errors.New("low and/or high are no time") } return (l.Equal(o) || l.Before(o)) && (h.After(o) || h.Equal(o)), nil case time.Duration: l, lok := low.(time.Duration) h, hok := high.(time.Duration) if !lok && !hok { return false, errors.New("low and/or high are no duration") } return l <= o && o <= h, nil } // Now check the collection types. ol, err := t.Len(obtained) if err != nil { return false, errors.New("no valid type with a length") } l, lok := low.(int) h, hok := high.(int) if !lok && !hok { return false, errors.New("low and/or high are no int") } return l <= ol && ol <= h, nil } // Contains checks if the part type is matching to the full type and // if the full data contains the part data. func (t Tester) Contains(part, full interface{}) (bool, error) { switch fullValue := full.(type) { case string: // Content of a string. switch partValue := part.(type) { case string: return strings.Contains(fullValue, partValue), nil case []byte: return strings.Contains(fullValue, string(partValue)), nil default: partString := fmt.Sprintf("%v", partValue) return strings.Contains(fullValue, partString), nil } case []byte: // Content of a byte slice. switch partValue := part.(type) { case string: return bytes.Contains(fullValue, []byte(partValue)), nil case []byte: return bytes.Contains(fullValue, partValue), nil default: partBytes := []byte(fmt.Sprintf("%v", partValue)) return bytes.Contains(fullValue, partBytes), nil } default: // Content of any array or slice, use reflection. value := reflect.ValueOf(full) kind := value.Kind() if kind == reflect.Array || kind == reflect.Slice { length := value.Len() for i := 0; i < length; i++ { current := value.Index(i) if reflect.DeepEqual(part, current.Interface()) { return true, nil } } return false, nil } } return false, errors.New("full value is no string, array, or slice") } // IsSubstring checks if obtained is a substring of the full string. func (t Tester) IsSubstring(obtained, full string) bool { return strings.Contains(full, obtained) } // IsCase checks if the obtained string is uppercase or lowercase. func (t Tester) IsCase(obtained string, upperCase bool) bool { if upperCase { return obtained == strings.ToUpper(obtained) } return obtained == strings.ToLower(obtained) } // IsMatching checks if the obtained string matches a regular expression. func (t Tester) IsMatching(obtained, regex string) (bool, error) { return regexp.MatchString("^"+regex+"$", obtained) } // IsImplementor checks if obtained implements the expected interface variable pointer. func (t Tester) IsImplementor(obtained, expected interface{}) (bool, error) { obtainedValue := reflect.ValueOf(obtained) expectedValue := reflect.ValueOf(expected) if !obtainedValue.IsValid() { return false, fmt.Errorf("obtained value is invalid: %v", obtained) } if !expectedValue.IsValid() || expectedValue.Kind() != reflect.Ptr || expectedValue.Elem().Kind() != reflect.Interface { return false, fmt.Errorf("expected value is no interface variable pointer: %v", expected) } return obtainedValue.Type().Implements(expectedValue.Elem().Type()), nil } // IsAssignable checks if the types of obtained and expected are assignable. func (t Tester) IsAssignable(obtained, expected interface{}) bool { obtainedValue := reflect.ValueOf(obtained) expectedValue := reflect.ValueOf(expected) return obtainedValue.Type().AssignableTo(expectedValue.Type()) } // Len checks the length of the obtained string, array, slice, map or channel. func (t Tester) Len(obtained interface{}) (int, error) { // Check using the lenable interface. if l, ok := obtained.(lenable); ok { return l.Len(), nil } // Check the standard types. obtainedValue := reflect.ValueOf(obtained) obtainedKind := obtainedValue.Kind() switch obtainedKind { case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice, reflect.String: return obtainedValue.Len(), nil default: descr := ValueDescription(obtained) return 0, fmt.Errorf("obtained %s is no array, chan, map, slice, string or understands Len()", descr) } } // HasPanic checks if the passed function panics. func (t Tester) HasPanic(pf func()) (ok bool) { defer func() { if r := recover(); r != nil { // Panic, that's ok! ok = true } }() pf() return false } // IsValidPath checks if the given directory or // file path exists. func (t Tester) IsValidPath(path string) (bool, error) { _, err := os.Stat(path) if err == nil { return true, nil } if os.IsNotExist(err) { return false, nil } return true, err } // EOF golib-4.24.2/cache/000077500000000000000000000000001315505703200137435ustar00rootroot00000000000000golib-4.24.2/cache/cache.go000066400000000000000000000153511315505703200153420ustar00rootroot00000000000000// Tideland Go Library - Cache // // Copyright (C) 2009-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package cache //-------------------- // IMPORTS //-------------------- import ( "time" "github.com/tideland/golib/errors" "github.com/tideland/golib/identifier" "github.com/tideland/golib/loop" ) //-------------------- // CONSTANTS //-------------------- // bucketStatus defines the different statuses of a bucket. type bucketStatus int const ( statusLoading bucketStatus = iota + 1 statusLoaded ) //-------------------- // CACHEABLE //-------------------- // Cacheable defines the interface for all cacheable information. type Cacheable interface { // ID returns the identifier of the information. ID() string // IsOutdated checks if their's a newer version of the Cacheable. IsOutdated() (bool, error) // Discard tells the Cacheable to clean up itself. Discard() error } //-------------------- // LOADER //-------------------- // CacheableLoader allows the user to define a function for // loading/reloading of cacheable instances. type CacheableLoader func(id string) (Cacheable, error) //-------------------- // OPTIONS //-------------------- // Option allows to configure a Cache. type Option func(c Cache) error // ID returns the option to set the cache ID. func ID(id string) Option { return func(c Cache) error { switch oc := c.(type) { case *cache: oc.id = id return nil default: return errors.New(ErrIllegalCache, errorMessages) } } } // Loader returns the option to set the loader function. func Loader(l CacheableLoader) Option { return func(c Cache) error { switch oc := c.(type) { case *cache: oc.load = l return nil default: return errors.New(ErrIllegalCache, errorMessages) } } } // Interval returns the option to set the cleanup check interval. func Interval(d time.Duration) Option { return func(c Cache) error { switch oc := c.(type) { case *cache: oc.interval = d return nil default: return errors.New(ErrIllegalCache, errorMessages) } } } // TTL returns the option to set the time to live for Cacheables. func TTL(d time.Duration) Option { return func(c Cache) error { switch oc := c.(type) { case *cache: oc.ttl = d return nil default: return errors.New(ErrIllegalCache, errorMessages) } } } //-------------------- // INFO //-------------------- // Info contains statistical information about the Cache. type Info struct { ID string Interval time.Duration TTL time.Duration Len int } //-------------------- // CACHE //-------------------- // Cache loads and returns instances by ID and caches them in memory. type Cache interface { // Load returns a Cacheable from memory or source. Load(id string, timeout time.Duration) (Cacheable, error) // Discard explicitly removes a Cacheable from Cache. Normally // done automatically. Discard(id string) error // Clear empties the Cache. Clear() error // Len returns the number of entries in the Cache. Len() int // Stop tells the Cache to stop working. Stop() error } // responder descibes a channel for functions returning // the result of a task. type responder chan func() (Cacheable, error) // bucket contains a Cacheable and the data needed to manage it. type bucket struct { cacheable Cacheable status bucketStatus loaded time.Time lastUsed time.Time waiters []responder } // cache implements the Cache interface. type cache struct { id string load CacheableLoader interval time.Duration ttl time.Duration buckets map[string]*bucket taskc chan task lenc chan chan int backend loop.Loop } // New creates a new cache. func New(options ...Option) (Cache, error) { c := &cache{ id: identifier.NewUUID().String(), interval: time.Minute, ttl: 10 * time.Minute, buckets: make(map[string]*bucket), taskc: make(chan task), lenc: make(chan chan int), } for _, option := range options { if err := option(c); err != nil { return nil, errors.Annotate(err, ErrSettingOptions, errorMessages) } } if c.load == nil { return nil, errors.New(ErrNoLoader, errorMessages) } c.backend = loop.Go(c.backendLoop, "cache", c.id) return c, nil } // Load implements the Cache interface. func (c *cache) Load(id string, timeout time.Duration) (Cacheable, error) { // Send lookup task. responsec := make(responder, 1) c.taskc <- lookupTask(id, responsec) // Receive response. select { case response := <-responsec: cacheable, err := response() return cacheable, err case <-time.After(timeout): return nil, errors.New(ErrTimeout, errorMessages, "loading") } } // Discard implements the Cache interface. func (c *cache) Discard(id string) error { // Send discard task. responsec := make(responder, 1) c.taskc <- discardTask(id, responsec) // Receive response. select { case response := <-responsec: _, err := response() return err case <-time.After(5 * time.Second): return errors.New(ErrTimeout, errorMessages, "discarding") } } // Clear implements the Cache interface. func (c *cache) Clear() error { // Send clear task. responsec := make(responder, 1) c.taskc <- clearTask(responsec) // Receive response. select { case response := <-responsec: _, err := response() return err case <-time.After(5 * time.Second): return errors.New(ErrTimeout, errorMessages, "discarding") } } // Len implements the Cache interface. func (c *cache) Len() int { // Send info task. lenc := make(chan int, 1) c.lenc <- lenc // Receive response. l := <-lenc return l } // Stop implements the Cache interface. func (c *cache) Stop() error { return c.backend.Stop() } // backendLoop runs the cache. func (c *cache) backendLoop(l loop.Loop) error { // Prepare ticker for lifetime check. checker := time.NewTicker(c.interval) defer checker.Stop() // Run loop. for { select { case <-l.ShallStop(): return nil case do := <-c.taskc: if err := do(c); err != nil { return err } case lenc := <-c.lenc: lenc <- len(c.buckets) case <-checker.C: if err := c.cleanup(); err != nil { return err } } } } // cleanup looks for too long unused Cacheables // and removes them. func (c *cache) cleanup() error { unused := []string{} now := time.Now() // First find old ones. for id, bucket := range c.buckets { if bucket.status == statusLoading { continue } if bucket.lastUsed.Add(c.ttl).Before(now) { unused = append(unused, id) } } // Now delete found ones. var errs []error for _, id := range unused { cacheable := c.buckets[id].cacheable delete(c.buckets, id) if err := cacheable.Discard(); err != nil { errs = append(errs, err) } } if len(errs) > 0 { return errors.Collect(errs...) } return nil } // EOF golib-4.24.2/cache/cache_test.go000066400000000000000000000227041315505703200164010ustar00rootroot00000000000000// Tideland Go Library - Cache - Unit Tests // // Copyright (C) 2009-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package cache_test //-------------------- // IMPORTS //-------------------- import ( "sync" "testing" "time" "fmt" "github.com/tideland/golib/audit" "github.com/tideland/golib/cache" "github.com/tideland/golib/errors" ) //-------------------- // CONSTANTS //-------------------- const ( idErrorDuringLoading = "/error/during/loading" idErrorDuringReloading = "/error/during/reloading" idErrorDuringCheck = "/error/during/check" idErrorDuringDiscarding = "/error/during/discarding" idTimeout = "/timeout" idIsOutdated = "/is/outdated" idValidCacheable = "/valid/cacheable" idSuccessfulDiscarding = "/successful/discarding" idConcurrent = "/concurrent" idCleanup = "/cleanup/%d" ) //-------------------- // TESTS //-------------------- // TestNoLoader tests the creation of a cache without // a loader. Must lead to an error. func TestNoLoader(t *testing.T) { assert := audit.NewTestingAssertion(t, true) c, err := cache.New() assert.Nil(c) assert.True(errors.IsError(err, cache.ErrNoLoader)) } // TestLoader tests the creation of a cache with // a loader. func TestLoader(t *testing.T) { assert := audit.NewTestingAssertion(t, true) te := initEnvironment() c, err := cache.New(cache.ID("loader"), cache.Loader(te.loader)) assert.Nil(err) assert.NotNil(c) err = c.Stop() assert.Nil(err) } // TestLoading tests the load method of a Cache. func TestLoading(t *testing.T) { assert := audit.NewTestingAssertion(t, true) te := initEnvironment() c, err := cache.New(cache.ID("loading"), cache.Loader(te.loader)) assert.Nil(err) defer c.Stop() cacheable, err := c.Load(idErrorDuringLoading, time.Second) assert.Nil(cacheable) assert.ErrorMatch(err, ".*error during loading.*") cacheable, err = c.Load(idTimeout, 10*time.Millisecond) assert.Nil(cacheable) assert.ErrorMatch(err, ".*timeout.*") now := time.Now() cacheable, err = c.Load(idValidCacheable, time.Second) first := time.Now().Sub(now) assert.Nil(err) assert.Equal(cacheable.ID(), idValidCacheable) now = time.Now() cacheable, err = c.Load(idValidCacheable, time.Second) second := time.Now().Sub(now) assert.Nil(err) assert.Equal(cacheable.ID(), idValidCacheable) assert.True(second < first) } // TestConcurrentLoading tests the concurrent loading. func TestConcurrentLoading(t *testing.T) { assert := audit.NewTestingAssertion(t, true) te := initEnvironment() c, err := cache.New(cache.ID("concurrent-loading"), cache.Loader(te.loader)) assert.Nil(err) defer c.Stop() var wg sync.WaitGroup for i := 9; i < 100; i++ { wg.Add(1) go func(n int) { defer wg.Done() cacheable, err := c.Load(idConcurrent, time.Second) assert.Nil(err) assert.Equal(cacheable.ID(), idConcurrent) assert.Logf("Goroutine %d loaded %q", n, cacheable.ID()) }(i) } wg.Wait() } // TestOutdatingFail tests the failing outdating of Cacheables. func TestOutdatingFail(t *testing.T) { assert := audit.NewTestingAssertion(t, true) te := initEnvironment() c, err := cache.New(cache.ID("outdating-fail"), cache.Loader(te.loader)) assert.Nil(err) defer c.Stop() cacheable, err := c.Load(idErrorDuringCheck, time.Second) assert.Nil(err) assert.Equal(cacheable.ID(), idErrorDuringCheck) cacheable, err = c.Load(idErrorDuringCheck, time.Second) assert.Nil(cacheable) assert.ErrorMatch(err, ".*error during check if '/error/during/check' is outdated.*") } // TestOutdatingReload tests the reload of outdated Cacheables. func TestOutdatingReload(t *testing.T) { assert := audit.NewTestingAssertion(t, true) te := initEnvironment() c, err := cache.New(cache.ID("outdating-reload"), cache.Loader(te.loader)) assert.Nil(err) defer c.Stop() for i := 1; i < 6; i++ { _, err := c.Load(idIsOutdated, time.Minute) assert.Nil(err) assert.Equal(te.loaded[idIsOutdated], i) } _, err = c.Load(idIsOutdated, time.Second) assert.Nil(err) assert.Equal(te.loaded[idIsOutdated], 1) } // TestOutdatingReloadError tests an error during reload of // outdated Cacheables. func TestOutdatingReloadError(t *testing.T) { assert := audit.NewTestingAssertion(t, true) te := initEnvironment() c, err := cache.New(cache.ID("outdating-reload-error"), cache.Loader(te.loader)) assert.Nil(err) defer c.Stop() for i := 1; i < 6; i++ { _, err := c.Load(idErrorDuringReloading, time.Second) assert.Nil(err) assert.Equal(te.loaded[idErrorDuringReloading], i) } _, err = c.Load(idErrorDuringReloading, time.Second) assert.ErrorMatch(err, ".*error during loading.*") } // TestDiscarding tests the discarding of Cacheables. func TestDiscarding(t *testing.T) { assert := audit.NewTestingAssertion(t, true) te := initEnvironment() c, err := cache.New(cache.ID("discarding"), cache.Loader(te.loader)) assert.Nil(err) defer c.Stop() // Test successful discarding, multiple times ok. cacheable, err := c.Load(idSuccessfulDiscarding, time.Second) assert.Nil(err) assert.Equal(cacheable.ID(), idSuccessfulDiscarding) err = c.Discard(idSuccessfulDiscarding) assert.Nil(err) err = c.Discard(idSuccessfulDiscarding) assert.Nil(err) // And now discarding with error. cacheable, err = c.Load(idErrorDuringDiscarding, time.Second) assert.Nil(err) assert.NotNil(cacheable) err = c.Discard(idErrorDuringDiscarding) assert.True(errors.IsError(err, cache.ErrDiscard)) // Discard while several ones are waiting. var wg sync.WaitGroup for i := 0; i < 10; i++ { wg.Add(1) go func() { defer wg.Done() cacheable, err := c.Load(idTimeout, time.Second) if err == nil { assert.Equal(cacheable.ID(), idTimeout) } else { assert.True(errors.IsError(err, cache.ErrDiscardedWhileLoading)) } }() } err = c.Discard(idTimeout) assert.Nil(err) wg.Wait() } // TestCleanup tests the cleanup of unused Cacheables. func TestCleanup(t *testing.T) { assert := audit.NewTestingAssertion(t, true) te := initEnvironment() c, err := cache.New(cache.ID("cleanup"), cache.Loader(te.loader), cache.Interval(250*time.Millisecond), cache.TTL(250*time.Millisecond)) assert.Nil(err) defer c.Stop() // Fill the cache. for i := 0; i < 50; i++ { id := fmt.Sprintf(idCleanup, i) cacheable, err := c.Load(id, time.Second) assert.Nil(err) assert.Equal(cacheable.ID(), id) } firstLen := c.Len() time.Sleep(500 * time.Millisecond) secondLen := c.Len() assert.Logf("1st: %d > 2nd: %d", firstLen, secondLen) assert.True(firstLen > secondLen) } // TestClear tests the clearing of a Cache. func TestClear(t *testing.T) { assert := audit.NewTestingAssertion(t, true) te := initEnvironment() c, err := cache.New(cache.ID("clear"), cache.Loader(te.loader)) assert.Nil(err) defer c.Stop() // Fill the cache. for i := 0; i < 50; i++ { id := fmt.Sprintf(idCleanup, i) cacheable, err := c.Load(id, time.Second) assert.Nil(err) assert.Equal(cacheable.ID(), id) } firstLen := c.Len() err = c.Clear() assert.Nil(err) secondLen := c.Len() assert.Logf("1st: %d > 2nd: %d", firstLen, secondLen) assert.True(firstLen > secondLen) } //-------------------- // HELPERS //-------------------- const ( errLoading = iota + 1 errIsOutdated errDiscarding errDoubleDiscarding ) var errorMessages = errors.Messages{ errLoading: "error during loading", errIsOutdated: "error during check if '%s' is outdated", errDiscarding: "error during discarding of '%s'", errDoubleDiscarding: "cacheable '%s' double discarded", } type testEnvironment struct { mutex sync.Mutex loaded map[string]int reloaded map[string]bool } // initEnvironment creates a new test environment. func initEnvironment() *testEnvironment { return &testEnvironment{ loaded: make(map[string]int), reloaded: make(map[string]bool), } } // loader loads the testCacheable. func (te *testEnvironment) loader(id string) (cache.Cacheable, error) { switch id { case idErrorDuringLoading: return nil, errors.New(errLoading, errorMessages) case idTimeout: time.Sleep(50 * time.Millisecond) } time.Sleep(50 * time.Millisecond) te.mutex.Lock() te.loaded[id] = 1 te.mutex.Unlock() if id == idErrorDuringReloading && te.reloaded[id] { return nil, errors.New(errLoading, errorMessages) } return &testCacheable{ te: te, id: id, discarded: false, }, nil } // testCacheable implements Cacheable for testing. type testCacheable struct { te *testEnvironment id string discarded bool } func (tc *testCacheable) ID() string { return tc.id } // IsOutdated checks if their's a newer version of the Cacheable. func (tc *testCacheable) IsOutdated() (bool, error) { switch tc.id { case idErrorDuringCheck: return false, errors.New(errIsOutdated, errorMessages, tc.id) case idErrorDuringReloading: tc.te.mutex.Lock() tc.te.loaded[tc.id]++ tc.te.mutex.Unlock() if tc.te.loaded[tc.id] == 5 { tc.te.mutex.Lock() tc.te.reloaded[tc.id] = true tc.te.mutex.Unlock() return true, nil } case idIsOutdated: tc.te.mutex.Lock() tc.te.loaded[tc.id]++ tc.te.mutex.Unlock() if tc.te.loaded[tc.id] == 5 { return true, nil } } return false, nil } // Discard tells the Cacheable to clean up itself. func (tc *testCacheable) Discard() error { if tc.id == idErrorDuringDiscarding { return errors.New(errDiscarding, errorMessages, tc.id) } if tc.discarded { return errors.New(errDoubleDiscarding, errorMessages, tc.id) } tc.discarded = true return nil } // EOF golib-4.24.2/cache/doc.go000066400000000000000000000010321315505703200150330ustar00rootroot00000000000000// Tideland Go Library - Cache // // Copyright (C) 2009-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. // Package cache lazily loads information on demand and caches them. // The data inside a cache has to implement the Cacheable interface // which also contains methods for checking, if the information is // outdated, and for discarding the cached instance. It is loaded // by a user defined CacheableLoader function. package cache // EOF golib-4.24.2/cache/errors.go000066400000000000000000000023431315505703200156100ustar00rootroot00000000000000// Tideland Go Library - Cache // // Copyright (C) 2009-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package cache //-------------------- // IMPORTS //-------------------- import ( "github.com/tideland/golib/errors" ) //-------------------- // CONSTANTS //-------------------- // Errors of the Cache. const ( ErrSettingOptions = iota + 1 ErrIllegalCache ErrNoLoader ErrLoading ErrCheckOutdated ErrDiscard ErrDiscardedWhileLoading ErrTimeout ErrFileLoading ErrFileSize ErrFileChecking ) var errorMessages = errors.Messages{ ErrSettingOptions: "cannot set option", ErrIllegalCache: "illegal cache type for option", ErrNoLoader: "no loader configured", ErrLoading: "cannot load cacheable '%s'", ErrCheckOutdated: "cannot check if '%s' is outdated", ErrDiscard: "cannot discard '%s'", ErrDiscardedWhileLoading: "cacheable '%s' discarded while loading", ErrTimeout: "timeout while %s", ErrFileLoading: "cannot load file '%s'", ErrFileSize: "file '%s' is too large", ErrFileChecking: "cannot check file '%s'", } // EOF golib-4.24.2/cache/loader.go000066400000000000000000000055331315505703200155460ustar00rootroot00000000000000// Tideland Go Library - Cache - Loader // // Copyright (C) 2009-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package cache //-------------------- // IMPORTS //-------------------- import ( "io" "io/ioutil" "os" "path/filepath" "time" "bytes" "github.com/tideland/golib/errors" ) //-------------------- // LOADER //-------------------- // FileCacheable contains a file. type FileCacheable interface { Cacheable // ReadCloser returns the io.ReadCloser for the // cached file or the file itself if it's too large. ReadCloser() (io.ReadCloser, error) } // fileBuffer encapsulates a bytes.Buffer as io.ReadCloser. type fileBuffer struct { b *bytes.Buffer } // Read implements the io.ReadCloser interface. func (fb *fileBuffer) Read(p []byte) (int, error) { return fb.b.Read(p) } // Close implements the io.ReadCloser interface. func (fb *fileBuffer) Close() error { return nil } // fileCacheable implements the FileCacheable interface. type fileCacheable struct { name string fullname string tooLarge bool modTime time.Time data []byte } // ID implements the Cacheable interface. func (c *fileCacheable) ID() string { return c.name } // IsOutdated implements the Cacheable interface. func (c *fileCacheable) IsOutdated() (bool, error) { fi, err := os.Stat(c.fullname) if err != nil { return false, errors.Annotate(err, ErrFileChecking, errorMessages) } if fi.ModTime().After(c.modTime) { return true, nil } return false, nil } // Discard implements the Cacheable interface. func (c *fileCacheable) Discard() error { return nil } // ReadCloser implements the FileCacheable interface. func (c *fileCacheable) ReadCloser() (io.ReadCloser, error) { // Check if the file has to be returned directly because // it is too large. if c.tooLarge { f, err := os.Open(c.fullname) if err != nil { return nil, errors.Annotate(err, ErrFileLoading, errorMessages, c.name) } return f, nil } // It's a cached buffer, so return this. return &fileBuffer{bytes.NewBuffer(c.data)}, nil } // NewFileLoader returns a CacheableLoader for files. It // starts at the given root directory. func NewFileLoader(root string, maxSize int64) CacheableLoader { return func(name string) (Cacheable, error) { fn := filepath.Join(root, name) fi, err := os.Stat(fn) if err != nil { return nil, errors.Annotate(err, ErrFileLoading, errorMessages, name) } if fi.Size() > maxSize { return &fileCacheable{ name: name, fullname: fn, tooLarge: true, modTime: fi.ModTime(), }, nil } data, err := ioutil.ReadFile(fn) if err != nil { return nil, errors.Annotate(err, ErrFileLoading, errorMessages, name) } return &fileCacheable{ name: name, fullname: fn, modTime: fi.ModTime(), data: data, }, nil } } // EOF golib-4.24.2/cache/loader_test.go000066400000000000000000000037451315505703200166100ustar00rootroot00000000000000// Tideland Go Library - Cache - Unit Tests // // Copyright (C) 2009-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package cache_test //-------------------- // IMPORTS //-------------------- import ( "io/ioutil" "path/filepath" "testing" "github.com/tideland/golib/audit" "github.com/tideland/golib/cache" ) //-------------------- // CONSTANTS //-------------------- const multiplier = 1024 * 1024 //-------------------- // TESTS //-------------------- // TestFileLoader tests the loading of files. func TestFileLoader(t *testing.T) { assert := audit.NewTestingAssertion(t, true) td := audit.NewTempDir(assert) defer td.Restore() createFile(assert, td.String(), "fa", 1) createFile(assert, td.String(), "fb", 2) createFile(assert, td.String(), "fc", 3) createFile(assert, td.String(), "fd", 4) createFile(assert, td.String(), "fe", 5) loader := cache.NewFileLoader(td.String(), int64(3*multiplier)) tests := []struct { name string size int }{ {"fa", 1}, {"fb", 2}, {"fc", 3}, {"fd", 4}, {"fe", 5}, } for i, test := range tests { assert.Logf("test #%d: %s with size %d mb", i, test.name, test.size) for j := 0; j < 10; j++ { c, err := loader(test.name) assert.Nil(err) assert.Equal(c.ID(), test.name) fc, ok := c.(cache.FileCacheable) assert.True(ok) p := make([]byte, test.size*multiplier) rc, err := fc.ReadCloser() assert.Nil(err) n, err := rc.Read(p) assert.Nil(err) assert.Equal(n, test.size*multiplier) err = rc.Close() assert.Nil(err) } } } //-------------------- // HEKPERS //-------------------- // createFile creates a file for loader tests. func createFile(assert audit.Assertion, dir, name string, size int) string { fn := filepath.Join(dir, name) data := []byte{} for i := 0; i < size*multiplier; i++ { data = append(data, 'X') } err := ioutil.WriteFile(fn, []byte(data), 0644) assert.Nil(err) return fn } // EOF golib-4.24.2/cache/tasks.go000066400000000000000000000105151315505703200154210ustar00rootroot00000000000000// Tideland Go Library - Cache - Tasks // // Copyright (C) 2009-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package cache //-------------------- // IMPORTS //-------------------- import ( "time" "github.com/tideland/golib/errors" ) //-------------------- // TASKS //-------------------- // task contains any task a cache shall do. type task func(c *cache) error // failedTask notifies the cache that a loading failed. func failedTask(id string, err error) task { return func(c *cache) error { // Check for discarded Cacheable first. if c.buckets[id] == nil { return nil } // Notify all waiters. for _, waiter := range c.buckets[id].waiters { waiter <- func() (Cacheable, error) { return nil, errors.Annotate(err, ErrLoading, errorMessages, id) } } delete(c.buckets, id) return nil } } // successTask notifies the cache that a loading succeeded. func successTask(id string, cacheable Cacheable) task { return func(c *cache) error { // Check for discarded Cacheable first. if c.buckets[id] == nil { return nil } // Set bucket values. b := c.buckets[id] b.cacheable = cacheable b.status = statusLoaded b.loaded = time.Now() b.lastUsed = b.loaded // Notify all waiters. for _, waiter := range c.buckets[id].waiters { waiter <- func() (Cacheable, error) { return cacheable, nil } } b.waiters = nil return nil } } // loading is the asynchronous loading function. func loading(c *cache, id string) { cacheable, err := c.load(id) if err != nil { c.taskc <- failedTask(id, err) } else { c.taskc <- successTask(id, cacheable) } } // lookupTask returns the task for looking up the cache. func lookupTask(id string, responsec responder) task { return func(c *cache) error { b, ok := c.buckets[id] switch { case !ok: // ID is unknown. c.buckets[id] = &bucket{ status: statusLoading, waiters: []responder{responsec}, } go loading(c, id) case ok && b.status == statusLoading: // ID is known but Cacheable is not yet retrieved. b.waiters = append(b.waiters, responsec) case ok && b.status == statusLoaded: // ID is known and Cacheable is loaded. outdated, err := b.cacheable.IsOutdated() if err != nil { // Error during check if outdated. responsec <- func() (Cacheable, error) { return nil, errors.Annotate(err, ErrCheckOutdated, errorMessages, id) } delete(c.buckets, id) return nil } if outdated { // Outdated, so reload. c.buckets[id].status = statusLoading c.buckets[id].waiters = []responder{responsec} go loading(c, id) } // Everything fine. b.lastUsed = time.Now() responsec <- func() (Cacheable, error) { return b.cacheable, nil } } return nil } } // discardTask returns the task for discarding a Cacheable. func discardTask(id string, responsec responder) task { return func(c *cache) error { bucket, ok := c.buckets[id] if !ok { // Not found, so nothing to discard. responsec <- func() (Cacheable, error) { return nil, nil } return nil } // Discard Cacheable, notify possible waiters, // delete bucket, and notify caller. var err error if bucket.cacheable != nil { err = bucket.cacheable.Discard() } for _, waiter := range bucket.waiters { waiter <- func() (Cacheable, error) { return nil, errors.New(ErrDiscardedWhileLoading, errorMessages, id) } } delete(c.buckets, id) if err != nil { err = errors.Annotate(err, ErrDiscard, errorMessages, id) } responsec <- func() (Cacheable, error) { return nil, err } return nil } } // clearTask returns the task to clear the cache. func clearTask(responsec responder) task { return func(c *cache) error { var errs []error for id, bucket := range c.buckets { switch bucket.status { case statusLoading: for _, waiter := range bucket.waiters { waiter <- func() (Cacheable, error) { return nil, errors.New(ErrDiscardedWhileLoading, errorMessages, id) } } case statusLoaded: if err := bucket.cacheable.Discard(); err != nil { errs = append(errs, err) } } } c.buckets = make(map[string]*bucket) var err error if len(errs) > 0 { err = errors.Collect(errs...) } responsec <- func() (Cacheable, error) { return nil, err } return nil } } // EOF golib-4.24.2/collections/000077500000000000000000000000001315505703200152165ustar00rootroot00000000000000golib-4.24.2/collections/changer.go000066400000000000000000000171241315505703200171610ustar00rootroot00000000000000// Tideland Go Library - Collections - Changer // // Copyright (C) 2015 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package collections //-------------------- // IMPORTS //-------------------- import ( "github.com/tideland/golib/errors" ) //-------------------- // CHANGER //-------------------- // changer implements the Changer interface. type changer struct { node *node err error } // Value implements the Changer interface. func (c *changer) Value() (interface{}, error) { if c.err != nil { return nil, c.err } return c.node.content.value(), nil } // SetValue implements the Changer interface. func (c *changer) SetValue(v interface{}) (interface{}, error) { if c.err != nil { return nil, c.err } oldValue := c.node.content.value() newValue := justValue{v} if !c.node.isAllowed(newValue, true) { return nil, errors.New(ErrDuplicate, errorMessages) } c.node.content = newValue return oldValue, nil } // Add implements the Changer interface. func (c *changer) Add(v interface{}) error { if c.err != nil { return c.err } _, err := c.node.addChild(justValue{v}) return err } // Remove implements the Changer interface. func (c *changer) Remove() error { if c.err != nil { return c.err } return c.node.remove() } // List implements the Changer interface. func (c *changer) List() ([]interface{}, error) { if c.err != nil { return nil, c.err } var list []interface{} err := c.node.doChildren(func(cn *node) error { list = append(list, cn.content.value()) return nil }) if err != nil { return nil, err } return list, nil } // Error implements the Changer interface. func (c *changer) Error() error { return c.err } //-------------------- // STRING CHANGER //-------------------- // stringChanger implements the StringChanger interface. type stringChanger struct { node *node err error } // Value implements the StringChanger interface. func (c *stringChanger) Value() (string, error) { if c.err != nil { return "", c.err } if c.node.content.value() == nil { return "", nil } return c.node.content.value().(string), nil } // SetValue implements the StringChanger interface. func (c *stringChanger) SetValue(v string) (string, error) { if c.err != nil { return "", c.err } oldValue := c.node.content.value().(string) newValue := justValue{v} if !c.node.isAllowed(newValue, true) { return "", errors.New(ErrDuplicate, errorMessages) } c.node.content = newValue return oldValue, nil } // Add implements the StringChanger interface. func (c *stringChanger) Add(v string) error { if c.err != nil { return c.err } _, err := c.node.addChild(justValue{v}) return err } // Remove implements the StringChanger interface. func (c *stringChanger) Remove() error { if c.err != nil { return c.err } return c.node.remove() } // List implements the StringChanger interface. func (c *stringChanger) List() ([]string, error) { if c.err != nil { return nil, c.err } var list []string err := c.node.doChildren(func(cn *node) error { list = append(list, cn.content.value().(string)) return nil }) if err != nil { return nil, err } return list, nil } // Error implements the StringChanger interface. func (c *stringChanger) Error() error { return c.err } //-------------------- // KEY/VALUE CHANGER //-------------------- // keyValueChanger implements the KeyValueChanger interface. type keyValueChanger struct { node *node err error } // Key implements the KeyValueChanger interface. func (c *keyValueChanger) Key() (string, error) { if c.err != nil { return "", c.err } return c.node.content.key().(string), nil } // SetKey implements the KeyValueChanger interface. func (c *keyValueChanger) SetKey(key string) (string, error) { if c.err != nil { return "", c.err } if !c.node.container.duplicates { if c.node.hasDuplicateSibling(key) { return "", errors.New(ErrDuplicate, errorMessages) } } current := c.node.content.key().(string) c.node.content = keyValue{key, c.node.content.value()} return current, nil } // Value implements the KeyValueChanger interface. func (c *keyValueChanger) Value() (interface{}, error) { if c.err != nil { return nil, c.err } return c.node.content.value(), nil } // SetValue implements the KeyValueChanger interface. func (c *keyValueChanger) SetValue(value interface{}) (interface{}, error) { if c.err != nil { return nil, c.err } current := c.node.content.value() c.node.content = keyValue{c.node.content.key(), value} return current, nil } // Add implements the KeyValueChanger interface. func (c *keyValueChanger) Add(k string, v interface{}) error { if c.err != nil { return c.err } _, err := c.node.addChild(keyValue{k, v}) return err } // Remove implements the KeyValueChanger interface. func (c *keyValueChanger) Remove() error { if c.err != nil { return c.err } return c.node.remove() } // List implements the KeyValueChanger interface. func (c *keyValueChanger) List() ([]KeyValue, error) { if c.err != nil { return nil, c.err } var list []KeyValue err := c.node.doChildren(func(cn *node) error { list = append(list, KeyValue{cn.content.key().(string), cn.content.value()}) return nil }) if err != nil { return nil, err } return list, nil } // Error implements the KeyValueChanger interface. func (c *keyValueChanger) Error() error { return c.err } //-------------------- // KEY/STRING VALUE CHANGER //-------------------- // keyStringValueChanger implements the KeyStringValueChanger interface. type keyStringValueChanger struct { node *node err error } // Key implements the KeyStringValueChanger interface. func (c *keyStringValueChanger) Key() (string, error) { if c.err != nil { return "", c.err } return c.node.content.key().(string), nil } // SetKey implements the KeyStringValueChanger interface. func (c *keyStringValueChanger) SetKey(key string) (string, error) { if c.err != nil { return "", c.err } if !c.node.container.duplicates { if c.node.hasDuplicateSibling(key) { return "", errors.New(ErrDuplicate, errorMessages) } } current := c.node.content.key().(string) c.node.content = keyValue{key, c.node.content.value()} return current, nil } // Value implements the KeyStringValueChanger interface. func (c *keyStringValueChanger) Value() (string, error) { if c.err != nil { return "", c.err } if c.node.content.value() == nil { return "", nil } return c.node.content.value().(string), nil } // SetValue implements the KeyStringValueChanger interface. func (c *keyStringValueChanger) SetValue(value string) (string, error) { if c.err != nil { return "", c.err } current := c.node.content.value().(string) c.node.content = keyValue{c.node.content.key(), value} return current, nil } // Add implements the KeyStringValueChanger interface. func (c *keyStringValueChanger) Add(k, v string) error { if c.err != nil { return c.err } _, err := c.node.addChild(keyValue{k, v}) return err } // Remove implements the KeyStringValueChanger interface. func (c *keyStringValueChanger) Remove() error { if c.err != nil { return c.err } return c.node.remove() } // List implements the KeyStringValueChanger interface. func (c *keyStringValueChanger) List() ([]KeyStringValue, error) { if c.err != nil { return nil, c.err } var list []KeyStringValue err := c.node.doChildren(func(cn *node) error { list = append(list, KeyStringValue{cn.content.key().(string), cn.content.value().(string)}) return nil }) if err != nil { return nil, err } return list, nil } // Error implements the KeyStringValueChanger interface. func (c *keyStringValueChanger) Error() error { return c.err } // EOF golib-4.24.2/collections/collections.go000066400000000000000000000323571315505703200200750ustar00rootroot00000000000000// Tideland Go Library - Collections // // Copyright (C) 2015-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package collections //-------------------- // IMPORTS //-------------------- import ( "fmt" ) //-------------------- // EXCHANGE TYPES //-------------------- // KeyValue wraps a key and a value for the key/value iterator. type KeyValue struct { Keys string Value interface{} } // KeyStringValue carries a combination of key and string value. type KeyStringValue struct { Key string Value string } //-------------------- // COLLECTIONS - RING BUFFER //-------------------- // RingBuffer defines a buffer which is connected end-to-end. It // grows if needed. type RingBuffer interface { fmt.Stringer // Push adds values to the end of the buffer. Push(values ...interface{}) // Peek returns the first value of the buffer. If the // buffer is empty the second return value is false. Peek() (interface{}, bool) // Pop removes and returns the first value of the buffer. If // the buffer is empty the second return value is false. Pop() (interface{}, bool) // Len returns the number of values in the buffer. Len() int // Cap returns the capacity of the buffer. Cap() int } //-------------------- // COLLECTIONS - STACKS //-------------------- // Stack defines a stack containing any kind of values. type Stack interface { fmt.Stringer // Push adds values to the top of the stack. Push(vs ...interface{}) // Pop removes and returns the top value of the stack. Pop() (interface{}, error) // Peek returns the top value of the stack. Peek() (interface{}, error) // All returns all values bottom-up. All() []interface{} // AllReverse returns all values top-down. AllReverse() []interface{} // Len returns the number of entries in the stack. Len() int // Deflate cleans the stack. Deflate() } // StringStack defines a stack containing string values. type StringStack interface { fmt.Stringer // Push adds strings to the top of the stack. Push(vs ...string) // Pop removes and returns the top value of the stack. Pop() (string, error) // Peek returns the top value of the stack. Peek() (string, error) // All returns all values bottom-up. All() []string // AllReverse returns all values top-down. AllReverse() []string // Len returns the number of entries in the stack. Len() int // Deflate cleans the stack. Deflate() } //-------------------- // COLLECTIONS - SETS //-------------------- // Set defines a set containing any kind of values. type Set interface { fmt.Stringer // Add adds values to the set. Add(vs ...interface{}) // Remove removes a value out if the set. It doesn't // matter if the set does not contain the value. Remove(vs ...interface{}) // Contains checks if the set contains a given value. Contains(v interface{}) bool // All returns all values. All() []interface{} // FindAll returns all values found by the // passed function. FindAll(f func(v interface{}) (bool, error)) ([]interface{}, error) // DoAll executes the passed function on all values. DoAll(f func(v interface{}) error) error // Len returns the number of entries in the set. Len() int // Deflate cleans the stack. Deflate() } // StringSet defines a set containing string values. type StringSet interface { fmt.Stringer // Add adds values to the set. Add(vs ...string) // Remove removes a value out if the set. It doesn't // matter if the set does not contain the value. Remove(vs ...string) // Contains checks if a value is Contains(v string) bool // All returns all values. All() []string // FindAll returns all values found by the // passed function. FindAll(f func(v string) (bool, error)) ([]string, error) // DoAll executes the passed function on all values. DoAll(f func(v string) error) error // Len returns the number of entries in the set. Len() int // Deflate cleans the stack. Deflate() } //-------------------- // COLLECTIONS - TREE CHANGERS //-------------------- // Changer defines the interface to perform changes on a tree // node. It is returned by the addressing operations like At() and // Create() of the Tree. type Changer interface { // Value returns the changer node value. Value() (interface{}, error) // SetValue sets the changer node value. It also returns // the previous value. SetValue(value interface{}) (interface{}, error) // Add sets a child value. Add(value interface{}) error // Remove deletes this changer node. Remove() error // List returns the values of the children of the changer node. List() ([]interface{}, error) // Error returns a potential error of the changer. Error() error } // StringChanger defines the interface to perform changes on a string // tree node. It is returned by the addressing operations like // At() and Create() of the StringTree. type StringChanger interface { // Value returns the changer node value. Value() (string, error) // SetValue sets the changer node value. It also returns // the previous value. SetValue(value string) (string, error) // Add sets a child value. If the key already exists the // value will be overwritten. Add(value string) error // Remove deletes this changer node. Remove() error // List returns the values of the children of the changer node. List() ([]string, error) // Error returns a potential error of the changer. Error() error } // KeyValueChanger defines the interface to perform changes on a // key/value tree node. It is returned by the addressing operations // like At() and Create() of the KeyValueTree. type KeyValueChanger interface { // Key returns the changer node key. Key() (string, error) // SetKey sets the changer node key. Its checks if duplicate // keys are allowed and returns the previous key. SetKey(key string) (string, error) // Value returns the changer node value. Value() (interface{}, error) // SetValue sets the changer node value. It also returns // the previous value. SetValue(value interface{}) (interface{}, error) // Add sets a child key/value. If the key already exists the // value will be overwritten. Add(key string, value interface{}) error // Remove deletes this changer node. Remove() error // List returns the keys and values of the children of the changer node. List() ([]KeyValue, error) // Error returns a potential error of the changer. Error() error } // KeyStringValueChanger defines the interface to perform changes // on a key/string value tree node. It is returned by the addressing // operations like At() and Create() of the KeyStringValueTree. type KeyStringValueChanger interface { // Key returns the changer node key. Key() (string, error) // SetKey sets the changer node key. Its checks if duplicate // keys are allowed and returns the previous key. SetKey(key string) (string, error) // Value returns the changer node value. Value() (string, error) // SetValue sets the changer node value. It also returns // the previous value. SetValue(value string) (string, error) // Add sets a child key/value. If the key already exists the // value will be overwritten. Add(key, value string) error // Remove deletes this changer node. Remove() error // List returns the keys and values of the children of the changer node. List() ([]KeyStringValue, error) // Error returns a potential error of the changer. Error() error } //-------------------- // COLLECTIONS - TREES //-------------------- // Tree defines the interface for a tree able to store any type // of values. type Tree interface { fmt.Stringer // At returns the changer of the path defined by the given // values. If it does not exist it will not be created. Use // Create() here. So to set a child at a given node path do // // err := tree.At("path", 1, "to", "use").Set(12345) At(values ...interface{}) Changer // Root returns the top level changer. Root() Changer // Create returns the changer of the path defined by the // given keys. If it does not exist it will be created, // but at least the root key has to be correct. Create(values ...interface{}) Changer // FindFirst returns the changer for the first node found // by the passed function. FindFirst(f func(value interface{}) (bool, error)) Changer // FindAll returns all changers for the nodes found // by the passed function. FindAll(f func(value interface{}) (bool, error)) []Changer // DoAll executes the passed function on all nodes. DoAll(f func(value interface{}) error) error // DoAllDeep executes the passed function on all nodes // passing a deep list of values ordered top-down. DoAllDeep(f func(values []interface{}) error) error // Len returns the number of nodes of the tree. Len() int // Copy creates a copy of the tree. Copy() Tree // Deflate cleans the tree with a new root value. Deflate(value interface{}) } // StringTree defines the interface for a tree able to store strings. type StringTree interface { fmt.Stringer // At returns the changer of the path defined by the given // values. If it does not exist it will not be created. Use // Create() here. So to set a child at a given node path do // // err := tree.At("path", "one", "to", "use").Set("12345") At(values ...string) StringChanger // Root returns the top level changer. Root() StringChanger // Create returns the changer of the path defined by the // given keys. If it does not exist it will be created, // but at least the root key has to be correct. Create(values ...string) StringChanger // FindFirst returns the changer for the first node found // by the passed function. FindFirst(f func(value string) (bool, error)) StringChanger // FindAll returns all changers for the nodes found // by the passed function. FindAll(f func(value string) (bool, error)) []StringChanger // DoAll executes the passed function on all nodes. DoAll(f func(value string) error) error // DoAllDeep executes the passed function on all nodes // passing a deep list of values ordered top-down. DoAllDeep(f func(values []string) error) error // Len returns the number of nodes of the tree. Len() int // Copy creates a copy of the tree. Copy() StringTree // Deflate cleans the tree with a new root value. Deflate(value string) } // KeyValueTree defines the interface for a tree able to store key/value pairs. type KeyValueTree interface { fmt.Stringer // At returns the changer of the path defined by the given // values. If it does not exist it will not be created. Use // Create() here. So to set a child at a given node path do // // err := tree.At("path", "one", "to", "use").Set(12345) At(keys ...string) KeyValueChanger // Root returns the top level changer. Root() KeyValueChanger // Create returns the changer of the path defined by the // given keys. If it does not exist it will be created, // but at least the root key has to be correct. Create(keys ...string) KeyValueChanger // FindFirst returns the changer for the first node found // by the passed function. FindFirst(f func(key string, value interface{}) (bool, error)) KeyValueChanger // FindAll returns all changers for the nodes found // by the passed function. FindAll(f func(key string, value interface{}) (bool, error)) []KeyValueChanger // DoAll executes the passed function on all nodes. DoAll(f func(key string, value interface{}) error) error // DoAllDeep executes the passed function on all nodes // passing a deep list of keys ordered top-down. DoAllDeep(f func(keys []string, value interface{}) error) error // Len returns the number of nodes of the tree. Len() int // Copy creates a copy of the tree. Copy() KeyValueTree // CopyAt creates a copy of a subtree. CopyAt(keys ...string) (KeyValueTree, error) // Deflate cleans the tree with a new root value. Deflate(key string, value interface{}) } // KeyStringValueTree defines the interface for a tree able to store // key/string value pairs. type KeyStringValueTree interface { fmt.Stringer // At returns the changer of the path defined by the given // values. If it does not exist it will not be created. Use // Create() here. So to set a child at a given node path do // // err := tree.At("path", "one", "to", "use").Set(12345) At(keys ...string) KeyStringValueChanger // Root returns the top level changer. Root() KeyStringValueChanger // Create returns the changer of the path defined by the // given keys. If it does not exist it will be created, // but at least the root key has to be correct. Create(keys ...string) KeyStringValueChanger // FindFirst returns the changer for the first node found // by the passed function. FindFirst(f func(key, value string) (bool, error)) KeyStringValueChanger // FindAll returns all changers for the nodes found // by the passed function. FindAll(f func(key, value string) (bool, error)) []KeyStringValueChanger // DoAll executes the passed function on all nodes. DoAll(f func(key, value string) error) error // DoAllDeep executes the passed function on all nodes // passing a deep list of keys ordered top-down. DoAllDeep(f func(keys []string, value string) error) error // Len returns the number of nodes of the tree. Len() int // Copy creates a copy of the tree. Copy() KeyStringValueTree // CopyAt creates a copy of a subtree. CopyAt(keys ...string) (KeyStringValueTree, error) // Deflate cleans the tree with a new root value. Deflate(key, value string) } // EOF golib-4.24.2/collections/doc.go000066400000000000000000000010671315505703200163160ustar00rootroot00000000000000// Tideland Go Library - Collections // // Copyright (C) 2015-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. // Package collections of the Tideland Go Library provides some typical and // often used collection types like a ring buffer, stacks, sets and trees. // They are implemented as generic collections managing empty interfaces // as well as typed ones, e.g. for strings. They are not synchronized, so // this has to be done by the user. package collections // EOF golib-4.24.2/collections/errors.go000066400000000000000000000031231315505703200170600ustar00rootroot00000000000000// Tideland Go Library - Collections // // Copyright (C) 2015-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package collections //-------------------- // IMPORTS //-------------------- import ( "github.com/tideland/golib/errors" ) //-------------------- // CONSTANTS //-------------------- // Error codes of the collections package. const ( ErrEmpty = iota + 1 ErrNilValue ErrDuplicate ErrIllegalPath ErrNodeAddChild ErrCannotRemoveRoot ErrNodeNotFound ErrNodeFindFirst ErrNodeFindAll ErrNodeDoAll ErrNodeDoChildren ErrFindAll ErrDoAll ) var errorMessages = errors.Messages{ ErrEmpty: "collection is empty", ErrNilValue: "cannot add nil value", ErrDuplicate: "duplicates are not allowed", ErrIllegalPath: "cannot naviragte to the wanted node", ErrNodeAddChild: "cannot add child node", ErrCannotRemoveRoot: "cannot remove root", ErrNodeNotFound: "node not found", ErrNodeFindFirst: "cannot find first node", ErrNodeFindAll: "cannot find all matching nodes", ErrNodeDoAll: "cannot perform function on all nodes", ErrNodeDoChildren: "cannot perform function on child nodes", ErrFindAll: "cannot find all matching values", ErrDoAll: "cannot perform function on all values", } //-------------------- // CHECKERS //-------------------- // IsNodeNotFoundError checks if the error signals that a node // cannot be found. func IsNodeNotFoundError(err error) bool { return errors.IsError(err, ErrNodeNotFound) } // EOF golib-4.24.2/collections/ringbuffer.go000066400000000000000000000047641315505703200177110ustar00rootroot00000000000000// Tideland Go Library - Collections - Ring Buffer // // Copyright (C) 2015 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package collections //-------------------- // IMPORTS //-------------------- import ( "fmt" "strings" ) //-------------------- // RING BUFFER //-------------------- // valueLink is one ring buffer element containing one // value and linking to the next element. type valueLink struct { used bool value interface{} next *valueLink } // ringBuffer implements the RingBuffer interface. type ringBuffer struct { start *valueLink end *valueLink current *valueLink } // NewRingBuffer creates a new ring buffer. func NewRingBuffer(size int) RingBuffer { rb := &ringBuffer{} rb.start = &valueLink{} rb.end = rb.start if size < 2 { size = 2 } for i := 0; i < size-1; i++ { link := &valueLink{} rb.end.next = link rb.end = link } rb.end.next = rb.start return rb } // Push implements the RingBuffer interface. func (rb *ringBuffer) Push(values ...interface{}) { for _, value := range values { if rb.end.next.used == false { rb.end.next.used = true rb.end.next.value = value rb.end = rb.end.next continue } link := &valueLink{ used: true, value: value, next: rb.start, } rb.end.next = link rb.end = rb.end.next } } // Peek implements the RingBuffer interface. func (rb *ringBuffer) Peek() (interface{}, bool) { if rb.start.used == false { return nil, false } return rb.start.value, true } // Pop implements the RingBuffer interface. func (rb *ringBuffer) Pop() (interface{}, bool) { if rb.start.used == false { return nil, false } value := rb.start.value rb.start.used = false rb.start.value = nil rb.start = rb.start.next return value, true } // Len implements the RingBuffer interface. func (rb *ringBuffer) Len() int { l := 0 current := rb.start for current.used { l++ current = current.next if current == rb.start { break } } return l } // Cap implements the RingBuffer interface. func (rb *ringBuffer) Cap() int { c := 1 current := rb.start for current.next != rb.start { c++ current = current.next } return c } // String implements the Stringer interface. func (rb *ringBuffer) String() string { vs := []string{} current := rb.start for current.used { vs = append(vs, fmt.Sprintf("[%v]", current.value)) current = current.next if current == rb.start { break } } return strings.Join(vs, "->") } // EOF golib-4.24.2/collections/ringbuffer_test.go000066400000000000000000000042471315505703200207440ustar00rootroot00000000000000// Tideland Go Library - Collections - Ring Buffer - Unit Tests // // Copyright (C) 2015 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package collections_test //-------------------- // IMPORTS //-------------------- import ( "testing" "github.com/tideland/golib/audit" "github.com/tideland/golib/collections" ) //-------------------- // TESTS //-------------------- // TestRingBufferPush tests the pushing of values. func TestRingBufferPush(t *testing.T) { assert := audit.NewTestingAssertion(t, true) rb := collections.NewRingBuffer(0) assert.Equal(rb.Cap(), 2) assert.Length(rb, 0) rb = collections.NewRingBuffer(10) assert.Equal(rb.Cap(), 10) assert.Length(rb, 0) rb.Push(1, "alpha", nil, true) assert.Length(rb, 4) assert.Equal(rb.String(), "[1]->[alpha]->[]->[true]") } // TestRingBufferPeekPop tests the peeking and popping of values. func TestRingBufferPop(t *testing.T) { assert := audit.NewTestingAssertion(t, true) rb := collections.NewRingBuffer(10) assert.Equal(rb.Cap(), 10) assert.Length(rb, 0) v, ok := rb.Peek() assert.False(ok) assert.Nil(v) v, ok = rb.Pop() assert.False(ok) assert.Nil(v) rb.Push(1, "alpha", nil, true) assert.Length(rb, 4) assert.Equal(rb.String(), "[1]->[alpha]->[]->[true]") tests := []struct { value interface{} ok bool length int }{ {1, true, 3}, {"alpha", true, 2}, {nil, true, 1}, {true, true, 0}, {nil, false, 0}, } for _, test := range tests { v, ok := rb.Peek() assert.Equal(v, test.value) assert.Equal(ok, test.ok) v, ok = rb.Pop() assert.Equal(v, test.value) assert.Equal(ok, test.ok) assert.Length(rb, test.length) } rb.Push(2, "beta") assert.Equal(rb.Cap(), 10) assert.Length(rb, 2) } // TestRingBufferGrow tests the growing of the ring buffer. func TestRingBufferGrow(t *testing.T) { assert := audit.NewTestingAssertion(t, true) rb := collections.NewRingBuffer(4) assert.Equal(rb.Cap(), 4) assert.Length(rb, 0) rb.Push(1, 2, 3, 4, 5, 6, 7, 8) assert.Equal(rb.Cap(), 8) assert.Length(rb, 8) rb.Pop() rb.Pop() assert.Equal(rb.Cap(), 8) assert.Length(rb, 6) } // EOF golib-4.24.2/collections/sets.go000066400000000000000000000076561315505703200165410ustar00rootroot00000000000000// Tideland Go Library - Collections - Sets // // Copyright (C) 2015 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package collections //-------------------- // IMPORTS //-------------------- import ( "fmt" "github.com/tideland/golib/errors" ) //-------------------- // SET //-------------------- // set implements the Set interface. type set struct { values map[interface{}]struct{} } // NewSet creates a set with the passed values // as initial content. func NewSet(vs ...interface{}) Set { s := &set{make(map[interface{}]struct{})} s.Add(vs...) return s } // Add implements the Set interface. func (s *set) Add(vs ...interface{}) { for _, v := range vs { s.values[v] = struct{}{} } } // Remove implements the Set interface. func (s *set) Remove(vs ...interface{}) { for _, v := range vs { delete(s.values, v) } } // Contains implements the Set interface. func (s *set) Contains(v interface{}) bool { _, ok := s.values[v] return ok } // All implements the Set interface. func (s *set) All() []interface{} { all := []interface{}{} for v := range s.values { all = append(all, v) } return all } // FindAll implements the Set interface. func (s *set) FindAll(f func(v interface{}) (bool, error)) ([]interface{}, error) { found := []interface{}{} for v := range s.values { ok, err := f(v) if err != nil { return nil, errors.Annotate(err, ErrFindAll, errorMessages) } if ok { found = append(found, v) } } return found, nil } // DoAll implements the Set interface. func (s *set) DoAll(f func(v interface{}) error) error { for v := range s.values { if err := f(v); err != nil { return errors.Annotate(err, ErrDoAll, errorMessages) } } return nil } // Len implements the Set interface. func (s *set) Len() int { return len(s.values) } // Deflate implements the Set interface. func (s *set) Deflate() { s.values = make(map[interface{}]struct{}) } // Deflate implements the Stringer interface. func (s *set) String() string { all := s.All() return fmt.Sprintf("%v", all) } //-------------------- // STRING SET //-------------------- // stringSet implements the StringSet interface. type stringSet struct { values map[string]struct{} } // NewStringSet creates a string set with the passed values // as initial content. func NewStringSet(vs ...string) StringSet { s := &stringSet{make(map[string]struct{})} s.Add(vs...) return s } // Add implements the StringSet interface. func (s *stringSet) Add(vs ...string) { for _, v := range vs { s.values[v] = struct{}{} } } // Remove implements the StringSet interface. func (s *stringSet) Remove(vs ...string) { for _, v := range vs { delete(s.values, v) } } // Contains implements the StringSet interface. func (s *stringSet) Contains(v string) bool { _, ok := s.values[v] return ok } // All implements the StringSet interface. func (s *stringSet) All() []string { all := []string{} for v := range s.values { all = append(all, v) } return all } // FindAll implements the StringSet interface. func (s *stringSet) FindAll(f func(v string) (bool, error)) ([]string, error) { found := []string{} for v := range s.values { ok, err := f(v) if err != nil { return nil, errors.Annotate(err, ErrFindAll, errorMessages) } if ok { found = append(found, v) } } return found, nil } // DoAll implements the StringSet interface. func (s *stringSet) DoAll(f func(v string) error) error { for v := range s.values { if err := f(v); err != nil { return errors.Annotate(err, ErrDoAll, errorMessages) } } return nil } // Len implements the StringSet interface. func (s *stringSet) Len() int { return len(s.values) } // Deflate implements the StringSet interface. func (s *stringSet) Deflate() { s.values = make(map[string]struct{}) } // Deflate implements the Stringer interface. func (s *stringSet) String() string { all := s.All() return fmt.Sprintf("%v", all) } // EOF golib-4.24.2/collections/sets_test.go000066400000000000000000000070161315505703200175660ustar00rootroot00000000000000// Tideland Go Library - Collections - Sets - Unit Tests // // Copyright (C) 2015 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package collections_test //-------------------- // IMPORTS //-------------------- import ( "errors" "testing" "github.com/tideland/golib/audit" "github.com/tideland/golib/collections" ) //-------------------- // TESTS //-------------------- // TestSetsAddRemove tests the core set methods. func TestSetsAddRemove(t *testing.T) { assert := audit.NewTestingAssertion(t, true) set := collections.NewSet("foo", 42, true) assert.Length(set, 3) set.Add("foo", "bar", 123) assert.Length(set, 5) all := set.All() assert.Length(all, 5) set.Remove("yadda") assert.Length(set, 5) set.Remove("bar", 42) assert.Length(set, 3) set.Remove(false, "foo") assert.Length(set, 2) set.Deflate() assert.Length(set, 0) } // TestSetsFindAll tests the finding of set values. func TestSetsFindAll(t *testing.T) { assert := audit.NewTestingAssertion(t, true) set := collections.NewSet("foo", "bar", 42, true, "yadda", 12345) vs, err := set.FindAll(func(v interface{}) (bool, error) { switch v.(type) { case string: return true, nil default: return false, nil } }) assert.Nil(err) assert.Length(vs, 3) vs, err = set.FindAll(func(v interface{}) (bool, error) { return false, errors.New("ouch") }) assert.ErrorMatch(err, ".* cannot find all matching values: ouch") assert.Length(vs, 0) } // TestSetsDoAll tests the iteration over set values. func TestSetsDoAll(t *testing.T) { assert := audit.NewTestingAssertion(t, true) set := collections.NewSet("foo", "bar", 42, true, "yadda", 12345) sl := 0 err := set.DoAll(func(v interface{}) error { if s, ok := v.(string); ok { sl += len(s) } return nil }) assert.Nil(err) assert.Equal(sl, 11) err = set.DoAll(func(v interface{}) error { return errors.New("ouch") }) assert.ErrorMatch(err, ".* cannot perform function on all values: ouch") } // TestStringSetsAddRemove tests the core set methods. func TestStringSetsAddRemove(t *testing.T) { assert := audit.NewTestingAssertion(t, true) set := collections.NewStringSet("foo", "42", "true") assert.Length(set, 3) set.Add("foo", "bar", "123") assert.Length(set, 5) all := set.All() assert.Length(all, 5) set.Remove("yadda") assert.Length(set, 5) set.Remove("bar", "42") assert.Length(set, 3) set.Remove("false", "foo") assert.Length(set, 2) set.Deflate() assert.Length(set, 0) } // TestStringSetsFindAll tests the finding of set values. func TestStringSetsFindAll(t *testing.T) { assert := audit.NewTestingAssertion(t, true) set := collections.NewStringSet("foo", "bar", "42", "true", "yadda", "12345") vs, err := set.FindAll(func(v string) (bool, error) { return len(v) == 3, nil }) assert.Nil(err) assert.Length(vs, 2) vs, err = set.FindAll(func(v string) (bool, error) { return false, errors.New("ouch") }) assert.ErrorMatch(err, ".* cannot find all matching values: ouch") assert.Length(vs, 0) } // TestStringSetsDoAll tests the iteration over set values. func TestStringSetsDoAll(t *testing.T) { assert := audit.NewTestingAssertion(t, true) set := collections.NewStringSet("foo", "bar", "42", "true", "yadda", "12345") sl := 0 err := set.DoAll(func(v string) error { sl += len(v) return nil }) assert.Nil(err) assert.Equal(sl, 22) err = set.DoAll(func(v string) error { return errors.New("ouch") }) assert.ErrorMatch(err, ".* cannot perform function on all values: ouch") } // EOF golib-4.24.2/collections/stacks.go000066400000000000000000000067441315505703200170500ustar00rootroot00000000000000// Tideland Go Library - Collections - Stacks // // Copyright (C) 2015 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package collections //-------------------- // IMPORTS //-------------------- import ( "fmt" "github.com/tideland/golib/errors" ) //-------------------- // STACK //-------------------- // stack implements the Stack interface. type stack struct { values []interface{} } // NewStack creates a stack with the passed values // as initial content. func NewStack(vs ...interface{}) Stack { return &stack{ values: vs, } } // Push implements the Stack interface. func (s *stack) Push(vs ...interface{}) { s.values = append(s.values, vs...) } // Pop implements the Stack interface. func (s *stack) Pop() (interface{}, error) { lv := len(s.values) if lv == 0 { return nil, errors.New(ErrEmpty, errorMessages) } v := s.values[lv-1] s.values = s.values[:lv-1] return v, nil } // Peek implements the Stack interface. func (s stack) Peek() (interface{}, error) { lv := len(s.values) if lv == 0 { return nil, errors.New(ErrEmpty, errorMessages) } v := s.values[lv-1] return v, nil } // All implements the Stack interface. func (s *stack) All() []interface{} { sl := len(s.values) all := make([]interface{}, sl) copy(all, s.values) return all } // AllReverse implements the Stack interface. func (s *stack) AllReverse() []interface{} { sl := len(s.values) all := make([]interface{}, sl) for i, value := range s.values { all[sl-1-i] = value } return all } // Len implements the Stack interface. func (s *stack) Len() int { return len(s.values) } // Deflate implements the Stack interface. func (s *stack) Deflate() { s.values = []interface{}{} } // Deflate implements the Stringer interface. func (s *stack) String() string { return fmt.Sprintf("%v", s.values) } //-------------------- // STRING STACK //-------------------- // stringStack implements the StringStack interface. type stringStack struct { values []string } // NewStringStack creates a string stack with the passed values // as initial content. func NewStringStack(vs ...string) StringStack { return &stringStack{ values: vs, } } // Push implements the StringStack interface. func (s *stringStack) Push(vs ...string) { s.values = append(s.values, vs...) } // Pop implements the StringStack interface. func (s *stringStack) Pop() (string, error) { lv := len(s.values) if lv == 0 { return "", errors.New(ErrEmpty, errorMessages) } v := s.values[lv-1] s.values = s.values[:lv-1] return v, nil } // Peek implements the StringStack interface. func (s *stringStack) Peek() (string, error) { lv := len(s.values) if lv == 0 { return "", errors.New(ErrEmpty, errorMessages) } v := s.values[lv-1] return v, nil } // All implements the StringStack interface. func (s *stringStack) All() []string { sl := len(s.values) all := make([]string, sl) copy(all, s.values) return all } // AllReverse implements the StringStack interface. func (s *stringStack) AllReverse() []string { sl := len(s.values) all := make([]string, sl) for i, value := range s.values { all[sl-1-i] = value } return all } // Len implements the Base interface. func (s *stringStack) Len() int { return len(s.values) } // Deflate implements the Base interface. func (s *stringStack) Deflate() { s.values = []string{} } // Deflate implements the Stringer interface. func (s *stringStack) String() string { return fmt.Sprintf("%v", s.values) } // EOF golib-4.24.2/collections/stacks_test.go000066400000000000000000000054701315505703200201020ustar00rootroot00000000000000// Tideland Go Library - Collections - Stacks - Unit Tests // // Copyright (C) 2015-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package collections_test //-------------------- // IMPORTS //-------------------- import ( "testing" "github.com/tideland/golib/audit" "github.com/tideland/golib/collections" ) //-------------------- // TESTS //-------------------- // TestStackPushPop tests the core stack methods. func TestStackPushPop(t *testing.T) { assert := audit.NewTestingAssertion(t, true) // Start with an empty stack. sa := collections.NewStack() assert.Length(sa, 0) sa.Push("foo") sa.Push(4711) assert.Length(sa, 2) sa.Push() assert.Length(sa, 2) sa.Push(false, 8.15) assert.Length(sa, 4) v, err := sa.Peek() assert.Nil(err) assert.Equal(v, 8.15) v, err = sa.Pop() assert.Nil(err) assert.Equal(v, 8.15) assert.Length(sa, 3) // Start with a filled stack. sb := collections.NewStack("a", true, 4711) assert.Length(sb, 3) v, err = sb.Pop() assert.Nil(err) assert.Equal(v, 4711) assert.Length(sb, 2) v, err = sb.Pop() assert.Nil(err) assert.Equal(v, true) assert.Length(sb, 1) v, err = sb.Pop() assert.Nil(err) assert.Equal(v, "a") assert.Length(sb, 0) // Popping the last one returns an error. _, err = sb.Pop() assert.ErrorMatch(err, ".*collection is empty") // And now deflate the first one. sa.Deflate() assert.Length(sa, 0) } // TestStackAll tests the retrieval of all values. func TestStackAll(t *testing.T) { assert := audit.NewTestingAssertion(t, true) s := collections.NewStack(1, "b", 3.0, true) all := s.All() assert.Equal(all, []interface{}{1, "b", 3.0, true}) all = s.AllReverse() assert.Equal(all, []interface{}{true, 3.0, "b", 1}) } // TestStringStackPushPop tests the core string stack methods. func TestStringStackPushPop(t *testing.T) { assert := audit.NewTestingAssertion(t, true) // Start with an empty stack. sa := collections.NewStringStack() assert.Length(sa, 0) sa.Push("foo") sa.Push("bar") assert.Length(sa, 2) sa.Push() assert.Length(sa, 2) sa.Push("baz", "yadda") assert.Length(sa, 4) v, err := sa.Peek() assert.Nil(err) assert.Equal(v, "yadda") v, err = sa.Pop() assert.Nil(err) assert.Equal(v, "yadda") assert.Length(sa, 3) // Start with a filled stack. sb := collections.NewStringStack("a", "b", "c") assert.Length(sb, 3) v, err = sb.Pop() assert.Nil(err) assert.Equal(v, "c") assert.Length(sb, 2) v, err = sb.Pop() assert.Nil(err) assert.Equal(v, "b") assert.Length(sb, 1) v, err = sb.Pop() assert.Nil(err) assert.Equal(v, "a") assert.Length(sb, 0) // Popping the last one returns an error. _, err = sb.Pop() assert.ErrorMatch(err, ".*collection is empty") // And now deflate the first one. sa.Deflate() assert.Length(sa, 0) } // EOF golib-4.24.2/collections/tree.go000066400000000000000000000505501315505703200165110ustar00rootroot00000000000000// Tideland Go Library - Collections - Tree // // Copyright (C) 2015-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package collections //-------------------- // IMPORTS //-------------------- import ( "fmt" "github.com/tideland/golib/errors" ) //-------------------- // NODE VALUE //-------------------- // nodeContent is the base interface for the different value // containing types. type nodeContent interface { // key returns the key for finding ands // check for duplicates. key() interface{} // value returns the value itself. value() interface{} // deepCopy creates a copy of the node content. deepCopy() nodeContent } // justValue has same key and value. type justValue struct { v interface{} } // key implements the nodeContent interface. func (v justValue) key() interface{} { return v.v } // value implements the nodeContent interface. func (v justValue) value() interface{} { return v.v } // deepCopy implements the nodeContent interface. func (v justValue) deepCopy() nodeContent { return justValue{v.v} } // String implements the Stringer interface. func (v justValue) String() string { return fmt.Sprintf("%v", v.v) } // keyValue has different key and value. type keyValue struct { k interface{} v interface{} } // key implements the nodeContent interface. func (v keyValue) key() interface{} { return v.k } // value implements the nodeContent interface. func (v keyValue) value() interface{} { return v.v } // deepCopy implements the nodeContent interface. func (v keyValue) deepCopy() nodeContent { return keyValue{v.k, v.v} } // String implements the Stringer interface. func (v keyValue) String() string { return fmt.Sprintf("%v = '%v'", v.k, v.v) } //-------------------- // NODE CONTAINER //-------------------- // nodeContainer is the top element of all nodes and provides // configuration. type nodeContainer struct { root *node duplicates bool } // newNodeContainer creates a new node container. func newNodeContainer(c nodeContent, duplicates bool) *nodeContainer { nc := &nodeContainer{ root: &node{ content: c, }, duplicates: duplicates, } nc.root.container = nc return nc } // deepCopy creates a copy of the container. func (nc *nodeContainer) deepCopy() *nodeContainer { cnc := &nodeContainer{ duplicates: nc.duplicates, } cnc.root = nc.root.deepCopy(cnc, nil) return cnc } //-------------------- // NODE //-------------------- // node contains the value and structural information of a node. type node struct { container *nodeContainer parent *node content nodeContent children []*node } // isAllowed returns true, if adding the content or setting // it is allowed depending on allowed duplicates. func (n *node) isAllowed(c nodeContent, here bool) bool { if n.container.duplicates { return true } checkNode := n if here { checkNode = n.parent } for _, child := range checkNode.children { if child.content.key() == c.key() { return false } } return true } // hasDuplicateSibling checks if the node has a sibling with the same key. func (n *node) hasDuplicateSibling(key interface{}) bool { if n.parent == nil { return false } for _, sibling := range n.parent.children { if sibling == n { continue } if sibling.content.key() == key { return true } } return false } // addChild adds a child node depending on allowed duplicates. func (n *node) addChild(c nodeContent) (*node, error) { if !n.isAllowed(c, false) { return nil, errors.New(ErrDuplicate, errorMessages) } child := &node{ container: n.container, parent: n, content: c, } n.children = append(n.children, child) return child, nil } // remove deletes this node from its parent. func (n *node) remove() error { if n.parent == nil { return errors.New(ErrCannotRemoveRoot, errorMessages) } for i, child := range n.parent.children { if child == n { n.parent.children = append(n.parent.children[:i], n.parent.children[i+1:]...) return nil } } panic("cannot find node to remove at parent") } // at finds a node by its path. func (n *node) at(path ...nodeContent) (*node, error) { if len(path) == 0 || path[0].key() != n.content.key() { return nil, errors.New(ErrNodeNotFound, errorMessages) } if len(path) == 1 { return n, nil } // Check children for rest of the path. for _, child := range n.children { found, err := child.at(path[1:]...) if err != nil && !IsNodeNotFoundError(err) { return nil, errors.Annotate(err, ErrIllegalPath, errorMessages) } if found != nil { return found, nil } } return nil, errors.New(ErrNodeNotFound, errorMessages) } // create acts like at but if nodes don't exist they will be created. func (n *node) create(path ...nodeContent) (*node, error) { if len(path) == 0 || path[0].key() != n.content.key() { return nil, errors.New(ErrNodeNotFound, errorMessages) } if len(path) == 1 { return n, nil } // Check children for the next path element. var found *node for _, child := range n.children { if path[1].key() == child.content.key() { found = child break } } if found == nil { child, err := n.addChild(path[1]) if err != nil { return nil, errors.Annotate(err, ErrNodeAddChild, errorMessages) } return child.create(path[1:]...) } return found.create(path[1:]...) } // findFirst returns the first node for which the passed function // returns true. func (n *node) findFirst(f func(fn *node) (bool, error)) (*node, error) { hasFound, err := f(n) if err != nil { return nil, errors.Annotate(err, ErrNodeFindFirst, errorMessages) } if hasFound { return n, nil } for _, child := range n.children { found, err := child.findFirst(f) if err != nil && !IsNodeNotFoundError(err) { return nil, errors.Annotate(err, ErrNodeFindFirst, errorMessages) } if found != nil { return found, nil } } return nil, errors.New(ErrNodeNotFound, errorMessages) } // findAll returns all nodes for which the passed function // returns true. func (n *node) findAll(f func(fn *node) (bool, error)) ([]*node, error) { var allFound []*node hasFound, err := f(n) if err != nil { return nil, errors.Annotate(err, ErrNodeFindAll, errorMessages) } if hasFound { allFound = append(allFound, n) } for _, child := range n.children { found, err := child.findAll(f) if err != nil { return nil, errors.Annotate(err, ErrNodeFindAll, errorMessages) } if found != nil { allFound = append(allFound, found...) } } return allFound, nil } // doAll performs the passed function for the node // and all its children deep to the leafs. func (n *node) doAll(f func(dn *node) error) error { if err := f(n); err != nil { return errors.Annotate(err, ErrNodeDoAll, errorMessages) } for _, child := range n.children { if err := child.doAll(f); err != nil { return errors.Annotate(err, ErrNodeDoAll, errorMessages) } } return nil } // doChildren performs the passed function for all children. func (n *node) doChildren(f func(cn *node) error) error { for _, child := range n.children { if err := f(child); err != nil { return errors.Annotate(err, ErrNodeDoChildren, errorMessages) } } return nil } // size recursively calculates the size of the nodeContainer. func (n *node) size() int { l := 1 for _, child := range n.children { l += child.size() } return l } // deepCopy creates a copy of the node. func (n *node) deepCopy(c *nodeContainer, p *node) *node { cn := &node{ container: c, parent: p, content: n.content.deepCopy(), children: make([]*node, len(n.children)), } for i, child := range n.children { cn.children[i] = child.deepCopy(c, cn) } return cn } // String implements the Stringer interface. func (n *node) String() string { out := fmt.Sprintf("[%v", n.content) if len(n.children) > 0 { out += " " for _, child := range n.children { out += child.String() } } out += "]" return out } //-------------------- // TREE //-------------------- // tree implements the Tree interface. type tree struct { container *nodeContainer } // NewTree creates a new tree with or without duplicate // values for children. func NewTree(v interface{}, duplicates bool) Tree { return &tree{ container: newNodeContainer(justValue{v}, duplicates), } } // At implements the Tree interface. func (t *tree) At(values ...interface{}) Changer { var path []nodeContent for _, value := range values { path = append(path, justValue{value}) } n, err := t.container.root.at(path...) return &changer{n, err} } // Root implements the Tree interface. func (t *tree) Root() Changer { return &changer{t.container.root, nil} } // Create implements the Tree interface. func (t *tree) Create(values ...interface{}) Changer { var path []nodeContent for _, value := range values { path = append(path, justValue{value}) } n, err := t.container.root.create(path...) return &changer{n, err} } // FindFirst implements the Tree interface. func (t *tree) FindFirst(f func(v interface{}) (bool, error)) Changer { n, err := t.container.root.findFirst(func(fn *node) (bool, error) { return f(fn.content.value()) }) return &changer{n, err} } // FindFirst implements the Tree interface. func (t *tree) FindAll(f func(v interface{}) (bool, error)) []Changer { ns, err := t.container.root.findAll(func(fn *node) (bool, error) { return f(fn.content.value()) }) if err != nil { return []Changer{&changer{nil, err}} } var cs []Changer for _, n := range ns { cs = append(cs, &changer{n, nil}) } return cs } // DoAll implements the Tree interface. func (t *tree) DoAll(f func(v interface{}) error) error { return t.container.root.doAll(func(dn *node) error { return f(dn.content.value()) }) } // DoAllDeep implements the Tree interface. func (t *tree) DoAllDeep(f func(vs []interface{}) error) error { return t.container.root.doAll(func(dn *node) error { values := []interface{}{} cn := dn for cn != nil { values = append([]interface{}{cn.content.value()}, values...) cn = cn.parent } return f(values) }) } // Len implements the Tree interface. func (t *tree) Len() int { return t.container.root.size() } // Copy implements the Tree interface. func (t *tree) Copy() Tree { return &tree{ container: t.container.deepCopy(), } } // Deflate implements the Tree interface. func (t *tree) Deflate(v interface{}) { t.container.root = &node{ content: justValue{v}, } } // String implements the Stringer interface. func (t *tree) String() string { return t.container.root.String() } //-------------------- // STRING TREE //-------------------- // stringTree implements the StringTree interface. type stringTree struct { container *nodeContainer } // NewStringTree creates a new string tree with or without // duplicate values for children. func NewStringTree(v string, duplicates bool) StringTree { return &stringTree{ container: newNodeContainer(justValue{v}, duplicates), } } // At implements the StringTree interface. func (t *stringTree) At(values ...string) StringChanger { var path []nodeContent for _, value := range values { path = append(path, justValue{value}) } n, err := t.container.root.at(path...) return &stringChanger{n, err} } // Root implements the StringTree interface. func (t *stringTree) Root() StringChanger { return &stringChanger{t.container.root, nil} } // Create implements the StringTree interface. func (t *stringTree) Create(values ...string) StringChanger { var path []nodeContent for _, value := range values { path = append(path, justValue{value}) } n, err := t.container.root.create(path...) return &stringChanger{n, err} } // FindFirst implements the StringTree interface. func (t *stringTree) FindFirst(f func(v string) (bool, error)) StringChanger { n, err := t.container.root.findFirst(func(fn *node) (bool, error) { return f(fn.content.value().(string)) }) return &stringChanger{n, err} } // FindFirst implements the StringTree interface. func (t *stringTree) FindAll(f func(v string) (bool, error)) []StringChanger { ns, err := t.container.root.findAll(func(fn *node) (bool, error) { return f(fn.content.value().(string)) }) if err != nil { return []StringChanger{&stringChanger{nil, err}} } var cs []StringChanger for _, n := range ns { cs = append(cs, &stringChanger{n, nil}) } return cs } // DoAll implements the StringTree interface. func (t *stringTree) DoAll(f func(v string) error) error { return t.container.root.doAll(func(dn *node) error { return f(dn.content.value().(string)) }) } // DoAllDeep implements the StringTree interface. func (t *stringTree) DoAllDeep(f func(vs []string) error) error { return t.container.root.doAll(func(dn *node) error { values := []string{} cn := dn for cn != nil { values = append([]string{cn.content.value().(string)}, values...) cn = cn.parent } return f(values) }) } // Len implements the StringTree interface. func (t *stringTree) Len() int { return t.container.root.size() } // Copy implements the StringTree interface. func (t *stringTree) Copy() StringTree { return &stringTree{ container: t.container.deepCopy(), } } // Deflate implements the StringTree interface. func (t *stringTree) Deflate(v string) { t.container.root = &node{ content: justValue{v}, } } // String implements the Stringer interface. func (t *stringTree) String() string { return t.container.root.String() } //-------------------- // KEY/VALUE TREE //-------------------- // keyValueTree implements the KeyValueTree interface. type keyValueTree struct { container *nodeContainer } // NewKeyValueTree creates a new key/value tree with or without // duplicate values for children. func NewKeyValueTree(k string, v interface{}, duplicates bool) KeyValueTree { return &keyValueTree{ container: newNodeContainer(keyValue{k, v}, duplicates), } } // At implements the KeyValueTree interface. func (t *keyValueTree) At(keys ...string) KeyValueChanger { var path []nodeContent for _, key := range keys { path = append(path, keyValue{key, nil}) } n, err := t.container.root.at(path...) return &keyValueChanger{n, err} } // Root implements the KeyValueTree interface. func (t *keyValueTree) Root() KeyValueChanger { return &keyValueChanger{t.container.root, nil} } // Create implements the KeyValueTree interface. func (t *keyValueTree) Create(keys ...string) KeyValueChanger { var path []nodeContent for _, key := range keys { path = append(path, keyValue{key, nil}) } n, err := t.container.root.create(path...) return &keyValueChanger{n, err} } // FindFirst implements the KeyValueTree interface. func (t *keyValueTree) FindFirst(f func(k string, v interface{}) (bool, error)) KeyValueChanger { n, err := t.container.root.findFirst(func(fn *node) (bool, error) { return f(fn.content.key().(string), fn.content.value()) }) return &keyValueChanger{n, err} } // FindFirst implements the KeyValueTree interface. func (t *keyValueTree) FindAll(f func(k string, v interface{}) (bool, error)) []KeyValueChanger { ns, err := t.container.root.findAll(func(fn *node) (bool, error) { return f(fn.content.key().(string), fn.content.value()) }) if err != nil { return []KeyValueChanger{&keyValueChanger{nil, err}} } var cs []KeyValueChanger for _, n := range ns { cs = append(cs, &keyValueChanger{n, nil}) } return cs } // DoAll implements the KeyValueTree interface. func (t *keyValueTree) DoAll(f func(k string, v interface{}) error) error { return t.container.root.doAll(func(dn *node) error { return f(dn.content.key().(string), dn.content.value()) }) } // DoAllDeep implements the KeyValueTree interface. func (t *keyValueTree) DoAllDeep(f func(ks []string, v interface{}) error) error { return t.container.root.doAll(func(dn *node) error { keys := []string{} cn := dn for cn != nil { keys = append([]string{cn.content.key().(string)}, keys...) cn = cn.parent } return f(keys, dn.content.value()) }) } // Len implements the KeyValueTree interface. func (t *keyValueTree) Len() int { return t.container.root.size() } // Copy implements the KeyValueTree interface. func (t *keyValueTree) Copy() KeyValueTree { return &keyValueTree{ container: t.container.deepCopy(), } } // CopyAt implements the KeyValueTree interface. func (t *keyValueTree) CopyAt(keys ...string) (KeyValueTree, error) { var path []nodeContent for _, key := range keys { path = append(path, keyValue{key, ""}) } n, err := t.container.root.at(path...) if err != nil { return nil, err } nc := &nodeContainer{ duplicates: t.container.duplicates, } nc.root = n.deepCopy(nc, nil) return &keyValueTree{nc}, nil } // Deflate implements the KeyValueTree interface. func (t *keyValueTree) Deflate(k string, v interface{}) { t.container.root = &node{ content: keyValue{k, v}, } } // String implements the Stringer interface. func (t *keyValueTree) String() string { return t.container.root.String() } //-------------------- // KEY/STRING VALUE TREE //-------------------- // keyStringValueTree implements the KeyStringValueTree interface. type keyStringValueTree struct { container *nodeContainer } // NewKeyStringValueTree creates a new key/value tree with or without // duplicate values for children and strings as values. func NewKeyStringValueTree(k, v string, duplicates bool) KeyStringValueTree { return &keyStringValueTree{ container: newNodeContainer(keyValue{k, v}, duplicates), } } // At implements the KeyStringValueTree interface. func (t *keyStringValueTree) At(keys ...string) KeyStringValueChanger { var path []nodeContent for _, key := range keys { path = append(path, keyValue{key, ""}) } n, err := t.container.root.at(path...) return &keyStringValueChanger{n, err} } // Root implements the KeyStringValueTree interface. func (t *keyStringValueTree) Root() KeyStringValueChanger { return &keyStringValueChanger{t.container.root, nil} } // Create implements the KeyStringValueTree interface. func (t *keyStringValueTree) Create(keys ...string) KeyStringValueChanger { var path []nodeContent for _, key := range keys { path = append(path, keyValue{key, ""}) } n, err := t.container.root.create(path...) return &keyStringValueChanger{n, err} } // FindFirst implements the KeyStringValueTree interface. func (t *keyStringValueTree) FindFirst(f func(k, v string) (bool, error)) KeyStringValueChanger { n, err := t.container.root.findFirst(func(fn *node) (bool, error) { return f(fn.content.key().(string), fn.content.value().(string)) }) return &keyStringValueChanger{n, err} } // FindFirst implements the KeyStringValueTree interface. func (t *keyStringValueTree) FindAll(f func(k, v string) (bool, error)) []KeyStringValueChanger { ns, err := t.container.root.findAll(func(fn *node) (bool, error) { return f(fn.content.key().(string), fn.content.value().(string)) }) if err != nil { return []KeyStringValueChanger{&keyStringValueChanger{nil, err}} } var cs []KeyStringValueChanger for _, n := range ns { cs = append(cs, &keyStringValueChanger{n, nil}) } return cs } // DoAll implements the KeyStringValueTree interface. func (t *keyStringValueTree) DoAll(f func(k, v string) error) error { return t.container.root.doAll(func(dn *node) error { return f(dn.content.key().(string), dn.content.value().(string)) }) } // DoAllDeep implements the KeyStringValueTree interface. func (t *keyStringValueTree) DoAllDeep(f func(ks []string, v string) error) error { return t.container.root.doAll(func(dn *node) error { keys := []string{} cn := dn for cn != nil { keys = append([]string{cn.content.key().(string)}, keys...) cn = cn.parent } return f(keys, dn.content.value().(string)) }) } // Len implements the KeyStringValueTree interface. func (t *keyStringValueTree) Len() int { return t.container.root.size() } // Copy implements the KeyStringValueTree interface. func (t *keyStringValueTree) Copy() KeyStringValueTree { return &keyStringValueTree{ container: t.container.deepCopy(), } } // CopyAt implements the KeyStringValueTree interface. func (t *keyStringValueTree) CopyAt(keys ...string) (KeyStringValueTree, error) { var path []nodeContent for _, key := range keys { path = append(path, keyValue{key, ""}) } n, err := t.container.root.at(path...) if err != nil { return nil, err } nc := &nodeContainer{ duplicates: t.container.duplicates, } nc.root = n.deepCopy(nc, nil) return &keyStringValueTree{nc}, nil } // Deflate implements the KeyStringValueTree interface. func (t *keyStringValueTree) Deflate(k, v string) { t.container.root = &node{ content: keyValue{k, v}, } } // String implements the Stringer interface. func (t *keyStringValueTree) String() string { return t.container.root.String() } // EOF golib-4.24.2/collections/tree_test.go000066400000000000000000000651221315505703200175510ustar00rootroot00000000000000// Tideland Go Library - Collections - Tree - Unit Tests // // Copyright (C) 2015-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package collections_test //-------------------- // IMPORTS //-------------------- import ( "errors" "fmt" "strings" "testing" "github.com/tideland/golib/audit" "github.com/tideland/golib/collections" ) //-------------------- // TEST TREE //-------------------- // TestTreeCreate tests the correct creation of a tree. func TestTreeCreate(t *testing.T) { assert := audit.NewTestingAssertion(t, true) // Tree with duplicates, no errors. tree := collections.NewTree("root", true) err := tree.At("root").Add("alpha") assert.Nil(err) err = tree.At("root").Add("bravo") assert.Nil(err) err = tree.At("root").Add("bravo") assert.Nil(err) err = tree.At("root").Add("charlie") assert.Nil(err) err = tree.Create("root", "delta", 1).Add(true) assert.Nil(err) assert.Length(tree, 8) // Deflate tree. tree.Deflate("toor") assert.Length(tree, 1) // Navigate with illegal paths. err = tree.At("foo").Add(0) assert.ErrorMatch(err, ".* node not found") err = tree.At("root", "foo").Add(0) assert.ErrorMatch(err, ".* node not found") // Tree without duplicates, so also with errors. tree = collections.NewTree("root", false) err = tree.At("root").Add("alpha") assert.Nil(err) err = tree.At("root").Add("bravo") assert.Nil(err) err = tree.At("root").Add("bravo") assert.ErrorMatch(err, ".* duplicates are not allowed") } // TestTreeRemove tests the correct removal of tree nodes. func TestTreeRemove(t *testing.T) { assert := audit.NewTestingAssertion(t, true) tree := createTree(assert) err := tree.At("root", "alpha").Remove() assert.Nil(err) assert.Length(tree, 11) err = tree.At("root", "delta").Remove() assert.Nil(err) assert.Length(tree, 6) } // TestTreeSetValue tests the setting of a tree nodes value. func TestTreeSetValue(t *testing.T) { assert := audit.NewTestingAssertion(t, true) tree := createTree(assert) // Tree with duplicates. old, err := tree.At("root", "alpha").SetValue("beta") assert.Nil(err) assert.Equal(old, "alpha") act, err := tree.At("root", "beta").Value() assert.Nil(err) assert.Equal(act, "beta") root, err := tree.Root().Value() assert.Nil(err) assert.Equal(root, "root") // Tree without duplicates. tree = collections.NewTree("root", false) err = tree.At("root").Add("alpha") assert.Nil(err) err = tree.At("root").Add("beta") assert.Nil(err) old, err = tree.At("root", "alpha").SetValue("beta") assert.Nil(old) assert.ErrorMatch(err, ".* duplicates are not allowed") } // TestTreeFind tests the correct finding in tree nodes. func TestTreeFind(t *testing.T) { assert := audit.NewTestingAssertion(t, true) tree := createTree(assert) // Test finding the first matching. list, err := tree.FindFirst(func(v interface{}) (bool, error) { switch vt := v.(type) { case string: return vt == "bravo", nil default: return false, nil } }).List() assert.Nil(err) assert.Equal(list, []interface{}{"foo", "bar"}) _, err = tree.FindFirst(func(v interface{}) (bool, error) { return false, nil }).List() assert.ErrorMatch(err, ".* node not found") _, err = tree.FindFirst(func(v interface{}) (bool, error) { return false, errors.New("ouch") }).List() assert.ErrorMatch(err, ".* cannot find first node: ouch") // Test finding all matching. changers := tree.FindAll(func(v interface{}) (bool, error) { switch v.(type) { case int: return true, nil default: return false, nil } }) assert.Length(changers, 2) v, err := changers[0].Value() assert.Nil(err) assert.Equal(v, 1) v, err = changers[1].Value() assert.Nil(err) assert.Equal(v, 2) changers = tree.FindAll(func(v interface{}) (bool, error) { return false, nil }) assert.Length(changers, 0) changers = tree.FindAll(func(v interface{}) (bool, error) { return false, errors.New("ouch") }) assert.Length(changers, 1) assert.ErrorMatch(changers[0].Error(), ".* cannot find all matching nodes: ouch") } // TestTreeDo tests the iteration over the tree nodes. func TestTreeDo(t *testing.T) { assert := audit.NewTestingAssertion(t, true) tree := collections.NewTree("root", true) err := tree.At("root").Add("alpha") assert.Nil(err) err = tree.At("root").Add("bravo") assert.Nil(err) err = tree.At("root", "bravo").Add("foo") assert.Nil(err) err = tree.At("root", "bravo").Add("bar") assert.Nil(err) err = tree.At("root").Add("bravo") assert.Nil(err) err = tree.At("root").Add("charlie") assert.Nil(err) err = tree.Create("root", "delta", 1).Add(true) assert.Nil(err) err = tree.Create("root", "delta", 2).Add(false) assert.Nil(err) // Test iteration. var values []interface{} err = tree.DoAll(func(v interface{}) error { values = append(values, v) return nil }) assert.Nil(err) assert.Length(values, 12) var all [][]interface{} err = tree.DoAllDeep(func(vs []interface{}) error { all = append(all, vs) return nil }) assert.Nil(err) assert.Length(all, 12) for _, vs := range all { assert.True(len(vs) >= 1 && len(vs) <= 4) } // Test errors. err = tree.DoAll(func(v interface{}) error { return errors.New("ouch") }) assert.ErrorMatch(err, ".* cannot perform function on all nodes: ouch") } // TestTreeCopy tests the copy of a tree. func TestTreeCopy(t *testing.T) { assert := audit.NewTestingAssertion(t, true) tree := collections.NewTree("root", true) err := tree.Create("root", "alpha").Add("a") assert.Nil(err) err = tree.Create("root", "beta").Add("b") assert.Nil(err) err = tree.Create("root", "gamma", "one").Add("1") assert.Nil(err) err = tree.Create("root", "gamma", "two").Add("2") assert.Nil(err) ctree := tree.Copy() assert.Length(ctree, 10) value, err := ctree.At("root", "alpha", "a").Value() assert.Nil(err) assert.Equal(value, "a") value, err = ctree.At("root", "gamma", "two", "2").Value() assert.Nil(err) assert.Equal(value, "2") } //-------------------- // TEST STRING TREE //-------------------- // TestStringTreeCreate tests the correct creation of a string tree. func TestStringTreeCreate(t *testing.T) { assert := audit.NewTestingAssertion(t, true) // String tree with duplicates, no errors. tree := collections.NewStringTree("root", true) err := tree.At("root").Add("alpha") assert.Nil(err) err = tree.At("root").Add("bravo") assert.Nil(err) err = tree.At("root").Add("bravo") assert.Nil(err) err = tree.At("root").Add("charlie") assert.Nil(err) err = tree.Create("root", "delta", "one").Add("true") assert.Nil(err) assert.Length(tree, 8) // Deflate tree. tree.Deflate("toor") assert.Length(tree, 1) // Navigate with illegal paths. err = tree.At("foo").Add("zero") assert.ErrorMatch(err, ".* node not found") err = tree.At("root", "foo").Add("zero") assert.ErrorMatch(err, ".* node not found") // Tree without duplicates, so also with errors. tree = collections.NewStringTree("root", false) err = tree.At("root").Add("alpha") assert.Nil(err) err = tree.At("root").Add("bravo") assert.Nil(err) err = tree.At("root").Add("bravo") assert.ErrorMatch(err, ".* duplicates are not allowed") } // TestStringTreeRemove tests the correct removal of string tree nodes. func TestStringTreeRemove(t *testing.T) { assert := audit.NewTestingAssertion(t, true) tree := createStringTree(assert) err := tree.At("root", "alpha").Remove() assert.Nil(err) assert.Length(tree, 11) err = tree.At("root", "delta").Remove() assert.Nil(err) assert.Length(tree, 6) } // TestStringTreeSetValue tests the setting of a string tree nodes value. func TestStringTreeSetValue(t *testing.T) { assert := audit.NewTestingAssertion(t, true) tree := createStringTree(assert) // Tree with duplicates. old, err := tree.At("root", "alpha").SetValue("beta") assert.Nil(err) assert.Equal(old, "alpha") act, err := tree.At("root", "beta").Value() assert.Nil(err) assert.Equal(act, "beta") // Tree without duplicates. tree = collections.NewStringTree("root", false) err = tree.At("root").Add("alpha") assert.Nil(err) err = tree.At("root").Add("beta") assert.Nil(err) old, err = tree.At("root", "alpha").SetValue("beta") assert.Equal(old, "") assert.ErrorMatch(err, ".* duplicates are not allowed") } // TestStringTreeFind tests the correct finding in string tree nodes. func TestStringTreeFind(t *testing.T) { assert := audit.NewTestingAssertion(t, true) tree := createStringTree(assert) // Test finding the first matching. list, err := tree.FindFirst(func(v string) (bool, error) { return v == "bravo", nil }).List() assert.Nil(err) assert.Equal(list, []string{"foo", "bar"}) _, err = tree.FindFirst(func(v string) (bool, error) { return false, nil }).List() assert.ErrorMatch(err, ".* node not found") _, err = tree.FindFirst(func(v string) (bool, error) { return false, errors.New("ouch") }).List() assert.ErrorMatch(err, ".* cannot find first node: ouch") // Test finding all matching. changers := tree.FindAll(func(v string) (bool, error) { return v == "bravo", nil }) assert.Length(changers, 2) v, err := changers[0].Value() assert.Nil(err) assert.Equal(v, "bravo") v, err = changers[1].Value() assert.Nil(err) assert.Equal(v, "bravo") changers = tree.FindAll(func(v string) (bool, error) { return false, nil }) assert.Length(changers, 0) changers = tree.FindAll(func(v string) (bool, error) { return false, errors.New("ouch") }) assert.Length(changers, 1) assert.ErrorMatch(changers[0].Error(), ".* cannot find all matching nodes: ouch") } // TestStringTreeDo tests the iteration over the string tree nodes. func TestStringTreeDo(t *testing.T) { assert := audit.NewTestingAssertion(t, true) tree := createStringTree(assert) // Test iteration. var values []string err := tree.DoAll(func(v string) error { values = append(values, v) return nil }) assert.Nil(err) assert.Length(values, 12) var all [][]string err = tree.DoAllDeep(func(vs []string) error { all = append(all, vs) return nil }) assert.Nil(err) assert.Length(all, 12) for _, vs := range all { assert.True(len(vs) >= 1 && len(vs) <= 4) } // Test errors. err = tree.DoAll(func(v string) error { return errors.New("ouch") }) assert.ErrorMatch(err, ".* cannot perform function on all nodes: ouch") } // TestStringTreeCopy tests the copy of a string tree. func TestStringTreeCopy(t *testing.T) { assert := audit.NewTestingAssertion(t, true) tree := collections.NewStringTree("root", true) err := tree.Create("root", "alpha").Add("a") assert.Nil(err) err = tree.Create("root", "beta").Add("b") assert.Nil(err) err = tree.Create("root", "gamma", "one").Add("1") assert.Nil(err) err = tree.Create("root", "gamma", "two").Add("2") assert.Nil(err) ctree := tree.Copy() assert.Length(ctree, 10) value, err := ctree.At("root", "alpha", "a").Value() assert.Nil(err) assert.Equal(value, "a") value, err = ctree.At("root", "gamma", "two", "2").Value() assert.Nil(err) assert.Equal(value, "2") } //-------------------- // TEST KEY/VALUE TREE //-------------------- // TestKeyValueTreeCreate tests the correct creation of a key/value tree. func TestKeyValueTreeCreate(t *testing.T) { assert := audit.NewTestingAssertion(t, true) // Key/value tree with duplicates, no errors. tree := collections.NewKeyValueTree("root", 1, true) err := tree.At("root").Add("alpha", 2) assert.Nil(err) err = tree.At("root").Add("bravo", 3) assert.Nil(err) err = tree.At("root").Add("bravo", true) assert.Nil(err) err = tree.At("root").Add("charlie", 1.0) assert.Nil(err) err = tree.Create("root", "delta", "one").Add("true", "false") assert.Nil(err) assert.Length(tree, 8) // Deflate tree. tree.Deflate("toor", 0) assert.Length(tree, 1) // Navigate with illegal paths. err = tree.At("foo").Add("zero", 0) assert.ErrorMatch(err, ".* node not found") err = tree.At("root", "foo").Add("zero", 0) assert.ErrorMatch(err, ".* node not found") // Tree without duplicates, so also with errors. tree = collections.NewKeyValueTree("root", 0, false) err = tree.At("root").Add("alpha", "a") assert.Nil(err) err = tree.At("root").Add("bravo", "b") assert.Nil(err) err = tree.At("root").Add("bravo", 2) assert.ErrorMatch(err, ".* duplicates are not allowed") } // TestKeyValueTreeRemove tests the correct removal of key/value tree nodes. func TestKeyValueTreeRemove(t *testing.T) { assert := audit.NewTestingAssertion(t, true) tree := createKeyValueTree(assert) err := tree.At("root", "alpha").Remove() assert.Nil(err) assert.Length(tree, 11) err = tree.At("root", "delta").Remove() assert.Nil(err) assert.Length(tree, 6) } // TestKeyValueTreeSetKey tests the setting of a key/value tree nodes key. func TestKeyValueTreeSetKey(t *testing.T) { assert := audit.NewTestingAssertion(t, true) tree := createKeyValueTree(assert) // Tree with duplicates. initialKey, err := tree.At("root", "alpha").Key() assert.Nil(err) assert.Equal(initialKey, "alpha") initialValue, err := tree.At("root", "alpha").Value() assert.Nil(err) currentKey, err := tree.At("root", "alpha").SetKey("beta") assert.Nil(err) assert.Equal(initialKey, currentKey) currentValue, err := tree.At("root", "beta").Value() assert.Nil(err) assert.Equal(currentValue, initialValue) // Tree without duplicates. tree = collections.NewKeyValueTree("root", 1, false) err = tree.At("root").Add("alpha", 2) assert.Nil(err) err = tree.At("root").Add("bravo", 3) assert.Nil(err) initialKey, err = tree.At("root", "alpha").SetKey("bravo") assert.Empty(initialKey) assert.ErrorMatch(err, ".* duplicates .*") } // TestKeyValueTreeSetValue tests the setting of a key/value tree nodes value. func TestKeyValueTreeSetValue(t *testing.T) { assert := audit.NewTestingAssertion(t, true) tree := createKeyValueTree(assert) // Tree with duplicates. old, err := tree.At("root", "alpha").SetValue("beta") assert.Nil(err) assert.Equal(old, 2) act, err := tree.At("root", "alpha").Value() assert.Nil(err) assert.Equal(act, "beta") // Tree without duplicates. tree = collections.NewKeyValueTree("root", 1, false) err = tree.At("root").Add("alpha", 2) assert.Nil(err) err = tree.At("root").Add("beta", 3) assert.Nil(err) old, err = tree.At("root", "alpha").SetValue("beta") assert.Nil(err) assert.Equal(old, 2) } // TestKeyValueTreeFind tests the correct finding in key/value tree nodes. func TestKeyValueTreeFind(t *testing.T) { assert := audit.NewTestingAssertion(t, true) tree := createKeyValueTree(assert) // Test finding the first matching. list, err := tree.FindFirst(func(k string, v interface{}) (bool, error) { return k == "bravo", nil }).List() assert.Nil(err) assert.Equal(list, []collections.KeyValue{{"foo", "bar"}, {"bar", "foo"}}) _, err = tree.FindFirst(func(k string, v interface{}) (bool, error) { return false, nil }).List() assert.ErrorMatch(err, ".* node not found") _, err = tree.FindFirst(func(k string, v interface{}) (bool, error) { return false, errors.New("ouch") }).List() assert.ErrorMatch(err, ".* cannot find first node: ouch") // Test finding all matching. changers := tree.FindAll(func(k string, v interface{}) (bool, error) { return k == "bravo", nil }) assert.Length(changers, 2) v, err := changers[0].Value() assert.Nil(err) assert.Equal(v, 3) v, err = changers[1].Value() assert.Nil(err) assert.Equal(v, 4) changers = tree.FindAll(func(k string, v interface{}) (bool, error) { return false, nil }) assert.Length(changers, 0) changers = tree.FindAll(func(k string, v interface{}) (bool, error) { return false, errors.New("ouch") }) assert.Length(changers, 1) assert.ErrorMatch(changers[0].Error(), ".* cannot find all matching nodes: ouch") } // TestKeyValueTreeDo tests the iteration over the key/value tree nodes. func TestKeyValueTreeDo(t *testing.T) { assert := audit.NewTestingAssertion(t, true) tree := createKeyValueTree(assert) // Test iteration. var values []interface{} err := tree.DoAll(func(k string, v interface{}) error { values = append(values, v) return nil }) assert.Nil(err) assert.Length(values, 12) keyValues := map[string]interface{}{} err = tree.DoAllDeep(func(ks []string, v interface{}) error { k := strings.Join(ks, "/") + " = " + fmt.Sprintf("%v", v) keyValues[k] = v return nil }) assert.Nil(err) assert.Length(keyValues, 12) for k := range keyValues { ksv := strings.Split(k, " = ") assert.Length(ksv, 2) ks := strings.Split(ksv[0], "/") assert.True(len(ks) >= 1 && len(ks) <= 4) } // Test errors. err = tree.DoAll(func(k string, v interface{}) error { return errors.New("ouch") }) assert.ErrorMatch(err, ".* cannot perform function on all nodes: ouch") } // TestKeyValueTreeCopy tests the copy of a key/value tree. func TestKeyValueTreeCopy(t *testing.T) { assert := audit.NewTestingAssertion(t, true) tree := collections.NewKeyValueTree("root", "0", true) err := tree.Create("root", "alpha").Add("a", "1") assert.Nil(err) err = tree.Create("root", "beta").Add("b", "2") assert.Nil(err) err = tree.Create("root", "gamma", "one").Add("1", "3.1") assert.Nil(err) err = tree.Create("root", "gamma", "two").Add("2", "3.2") assert.Nil(err) ctree := tree.Copy() assert.Length(ctree, 10) value, err := ctree.At("root", "alpha", "a").Value() assert.Nil(err) assert.Equal(value, "1") value, err = ctree.At("root", "gamma", "two", "2").Value() assert.Nil(err) assert.Equal(value, "3.2") catree, err := ctree.CopyAt("root", "gamma") assert.Nil(err) value, err = catree.At("gamma", "two", "2").Value() assert.Nil(err) assert.Equal(value, "3.2") } //-------------------- // TEST KEY/STRING VALUE TREE //-------------------- // TestKeyStringValueTreeCreate tests the correct creation of a // key/string value tree. func TestKeyStringValueTreeCreate(t *testing.T) { assert := audit.NewTestingAssertion(t, true) // Key/string value tree with duplicates, no errors. tree := collections.NewKeyStringValueTree("root", "one", true) err := tree.At("root").Add("alpha", "two") assert.Nil(err) err = tree.At("root").Add("bravo", "three") assert.Nil(err) err = tree.At("root").Add("bravo", "true") assert.Nil(err) err = tree.At("root").Add("charlie", "1.0") assert.Nil(err) err = tree.Create("root", "delta", "one").Add("true", "false") assert.Nil(err) assert.Length(tree, 8) // Deflate tree. tree.Deflate("toor", "zero") assert.Length(tree, 1) // Navigate with illegal paths. err = tree.At("foo").Add("zero", "0") assert.ErrorMatch(err, ".* node not found") err = tree.At("root", "foo").Add("zero", "0") assert.ErrorMatch(err, ".* node not found") // Tree without duplicates, so also with errors. tree = collections.NewKeyStringValueTree("root", "0", false) err = tree.At("root").Add("alpha", "a") assert.Nil(err) err = tree.At("root").Add("bravo", "b") assert.Nil(err) err = tree.At("root").Add("bravo", "2") assert.ErrorMatch(err, ".* duplicates are not allowed") } // TestKeyStringValueTreeRemove tests the correct removal of // key/string value tree nodes. func TestKeyStringValueTreeRemove(t *testing.T) { assert := audit.NewTestingAssertion(t, true) tree := createKeyStringValueTree(assert) err := tree.At("root", "alpha").Remove() assert.Nil(err) assert.Length(tree, 11) err = tree.At("root", "delta").Remove() assert.Nil(err) assert.Length(tree, 6) } // TestKeyStringValueTreeSetKey tests the setting of a // key/string value tree nodes key. func TestKeyStringValueTreeSetKey(t *testing.T) { assert := audit.NewTestingAssertion(t, true) tree := createKeyStringValueTree(assert) // Tree with duplicates. initialKey, err := tree.At("root", "alpha").Key() assert.Nil(err) assert.Equal(initialKey, "alpha") initialValue, err := tree.At("root", "alpha").Value() assert.Nil(err) currentKey, err := tree.At("root", "alpha").SetKey("beta") assert.Nil(err) assert.Equal(initialKey, currentKey) currentValue, err := tree.At("root", "beta").Value() assert.Nil(err) assert.Equal(currentValue, initialValue) // Tree without duplicates. tree = collections.NewKeyStringValueTree("root", "one", false) err = tree.At("root").Add("alpha", "two") assert.Nil(err) err = tree.At("root").Add("bravo", "three") assert.Nil(err) initialKey, err = tree.At("root", "alpha").SetKey("bravo") assert.Empty(initialKey) assert.ErrorMatch(err, ".* duplicates .*") } // TestKeyStringValueTreeSetValue tests the setting of a // key/string value tree nodes value. func TestKeyStringValueTreeSetValue(t *testing.T) { assert := audit.NewTestingAssertion(t, true) tree := createKeyStringValueTree(assert) // Tree with duplicates. old, err := tree.At("root", "alpha").SetValue("beta") assert.Nil(err) assert.Equal(old, "two") act, err := tree.At("root", "alpha").Value() assert.Nil(err) assert.Equal(act, "beta") // Tree without duplicates. tree = collections.NewKeyStringValueTree("root", "one", false) err = tree.At("root").Add("alpha", "two") assert.Nil(err) err = tree.At("root").Add("beta", "three") assert.Nil(err) old, err = tree.At("root", "alpha").SetValue("beta") assert.Nil(err) assert.Equal(old, "two") } // TestKeyStringValueTreeFind tests the correct finding in // key/string value tree nodes. func TestKeyStringValueTreeFind(t *testing.T) { assert := audit.NewTestingAssertion(t, true) tree := createKeyStringValueTree(assert) // Test finding the first matching. list, err := tree.FindFirst(func(k, v string) (bool, error) { return k == "bravo", nil }).List() assert.Nil(err) assert.Equal(list, []collections.KeyStringValue{{"foo", "bar"}, {"bar", "foo"}}) _, err = tree.FindFirst(func(k, v string) (bool, error) { return false, nil }).List() assert.ErrorMatch(err, ".* node not found") _, err = tree.FindFirst(func(k, v string) (bool, error) { return false, errors.New("ouch") }).List() assert.ErrorMatch(err, ".* cannot find first node: ouch") // Test finding all matching. changers := tree.FindAll(func(k, v string) (bool, error) { return k == "bravo", nil }) assert.Length(changers, 2) v, err := changers[0].Value() assert.Nil(err) assert.Equal(v, "three") v, err = changers[1].Value() assert.Nil(err) assert.Equal(v, "four") changers = tree.FindAll(func(k, v string) (bool, error) { return false, nil }) assert.Length(changers, 0) changers = tree.FindAll(func(k, v string) (bool, error) { return false, errors.New("ouch") }) assert.Length(changers, 1) assert.ErrorMatch(changers[0].Error(), ".* cannot find all matching nodes: ouch") } // TestKeyStringValueTreeDo tests the iteration over the // key/string value tree nodes. func TestKeyStringValueTreeDo(t *testing.T) { assert := audit.NewTestingAssertion(t, true) tree := createKeyStringValueTree(assert) // Test iterations. values := []string{} err := tree.DoAll(func(k, v string) error { values = append(values, v) return nil }) assert.Nil(err) assert.Length(values, 12) keyValues := map[string]string{} err = tree.DoAllDeep(func(ks []string, v string) error { k := strings.Join(ks, "/") + " = " + v keyValues[k] = v return nil }) assert.Nil(err) assert.Length(keyValues, 12) for k := range keyValues { ksv := strings.Split(k, " = ") assert.Length(ksv, 2) ks := strings.Split(ksv[0], "/") assert.True(len(ks) >= 1 && len(ks) <= 4) } // Test errors. err = tree.DoAll(func(k, v string) error { return errors.New("ouch") }) assert.ErrorMatch(err, ".* cannot perform function on all nodes: ouch") } // TestKeyStringValueTreeCopy tests the copy of a key/string value tree. func TestKeyStringValueTreeCopy(t *testing.T) { assert := audit.NewTestingAssertion(t, true) tree := collections.NewKeyStringValueTree("root", "0", true) err := tree.Create("root", "alpha").Add("a", "1") assert.Nil(err) err = tree.Create("root", "beta").Add("b", "2") assert.Nil(err) err = tree.Create("root", "gamma", "one").Add("1", "3.1") assert.Nil(err) err = tree.Create("root", "gamma", "two").Add("2", "3.2") assert.Nil(err) ctree := tree.Copy() assert.Length(ctree, 10) value, err := ctree.At("root", "alpha", "a").Value() assert.Nil(err) assert.Equal(value, "1") value, err = ctree.At("root", "gamma", "two", "2").Value() assert.Nil(err) assert.Equal(value, "3.2") catree, err := ctree.CopyAt("root", "gamma") assert.Nil(err) value, err = catree.At("gamma", "two", "2").Value() assert.Nil(err) assert.Equal(value, "3.2") } //-------------------- // HELPERS //-------------------- func createTree(assert audit.Assertion) collections.Tree { tree := collections.NewTree("root", true) err := tree.At("root").Add("alpha") assert.Nil(err) err = tree.At("root").Add("bravo") assert.Nil(err) err = tree.At("root", "bravo").Add("foo") assert.Nil(err) err = tree.At("root", "bravo").Add("bar") assert.Nil(err) err = tree.At("root").Add("bravo") assert.Nil(err) err = tree.At("root").Add("charlie") assert.Nil(err) err = tree.Create("root", "delta", 1).Add(true) assert.Nil(err) err = tree.Create("root", "delta", 2).Add(false) assert.Nil(err) assert.Length(tree, 12) return tree } func createStringTree(assert audit.Assertion) collections.StringTree { tree := collections.NewStringTree("root", true) err := tree.At("root").Add("alpha") assert.Nil(err) err = tree.At("root").Add("bravo") assert.Nil(err) err = tree.At("root", "bravo").Add("foo") assert.Nil(err) err = tree.At("root", "bravo").Add("bar") assert.Nil(err) err = tree.At("root").Add("bravo") assert.Nil(err) err = tree.At("root").Add("charlie") assert.Nil(err) err = tree.Create("root", "delta", "one").Add("true") assert.Nil(err) err = tree.Create("root", "delta", "two").Add("false") assert.Nil(err) assert.Length(tree, 12) return tree } func createKeyValueTree(assert audit.Assertion) collections.KeyValueTree { tree := collections.NewKeyValueTree("root", 1, true) err := tree.At("root").Add("alpha", 2) assert.Nil(err) err = tree.At("root").Add("bravo", 3) assert.Nil(err) err = tree.At("root", "bravo").Add("foo", "bar") assert.Nil(err) err = tree.At("root", "bravo").Add("bar", "foo") assert.Nil(err) err = tree.At("root").Add("bravo", 4) assert.Nil(err) err = tree.At("root").Add("charlie", 5) assert.Nil(err) err = tree.Create("root", "delta", "one").Add("true", 1) assert.Nil(err) err = tree.Create("root", "delta", "two").Add("false", 0) assert.Nil(err) assert.Length(tree, 12) return tree } func createKeyStringValueTree(assert audit.Assertion) collections.KeyStringValueTree { tree := collections.NewKeyStringValueTree("root", "one", true) err := tree.At("root").Add("alpha", "two") assert.Nil(err) err = tree.At("root").Add("bravo", "three") assert.Nil(err) err = tree.At("root", "bravo").Add("foo", "bar") assert.Nil(err) err = tree.At("root", "bravo").Add("bar", "foo") assert.Nil(err) err = tree.At("root").Add("bravo", "four") assert.Nil(err) err = tree.At("root").Add("charlie", "five") assert.Nil(err) err = tree.Create("root", "delta", "one").Add("true", "one") assert.Nil(err) err = tree.Create("root", "delta", "two").Add("false", "zero") assert.Nil(err) assert.Length(tree, 12) return tree } // EOF golib-4.24.2/errors/000077500000000000000000000000001315505703200142145ustar00rootroot00000000000000golib-4.24.2/errors/doc.go000066400000000000000000000022251315505703200153110ustar00rootroot00000000000000// Tideland Go Library - Errors // // Copyright (C) 2013-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. // Package errors of the Tideland Go Library allows to create more // detailed errors than with errors.New() or fmt.Errorf(). When trying // to differentiate between different errors or to carry helpful // payload own types are needed. // // The errors package allows to easily created formatted errors // with New() like with the fmt.Errorf() function, but also with an // error code. Additionlly a Messages instance has to be passed // to map the error code to their according messages. // // If an error alreay exists use Annotate(). This way the original // error will be stored and can be retrieved with Annotated(). Also // its error message will be appended to the created error separated // by a colon. // // All errors additionally contain their package, filename and line // number. These information can be retrieved using Location(). In // case of a chain of annotated errors those can be retrieved as a // slice of errors with Stack(). package errors // EOF golib-4.24.2/errors/errors.go000066400000000000000000000144311315505703200160620ustar00rootroot00000000000000// Tideland Go Library - Errors // // Copyright (C) 2013-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package errors //-------------------- // IMPORTS //-------------------- import ( "fmt" "path" "runtime" "strings" ) //-------------------- // MESSAGES //-------------------- // Messages contains the message strings for the the error codes. type Messages map[int]string // Format returns the formatted error message for code with the // given arguments. func (m Messages) Format(code int, args ...interface{}) string { if m == nil || m[code] == "" { if len(args) == 0 { return fmt.Sprintf("[ERRORS:999] invalid error code '%d'", code) } format := fmt.Sprintf("%v", args[0]) return fmt.Sprintf(format, args[1:]...) } format := m[code] return fmt.Sprintf(format, args...) } //-------------------- // CONSTANTS //-------------------- // Error codes of the errors package. const ( ErrInvalidErrorType = iota + 1 ErrNotYetImplemented ErrDeprecated ) var errorMessages = Messages{ ErrInvalidErrorType: "invalid error type: %T %q", ErrNotYetImplemented: "feature is not yet implemented: %q", ErrDeprecated: "feature is deprecated: %q", } //-------------------- // ERROR //-------------------- // errorBox encapsulates an error. type errorBox struct { err error code int msg string info *callInfo } // newErrorBox creates an initialized error box. func newErrorBox(err error, code int, msgs Messages, args ...interface{}) *errorBox { return &errorBox{ err: err, code: code, msg: msgs.Format(code, args...), info: retrieveCallInfo(), } } // Error implements the error interface. func (eb *errorBox) Error() string { if eb.err != nil { return fmt.Sprintf("[%s:%03d] %s: %v", eb.info.packagePart, eb.code, eb.msg, eb.err) } return fmt.Sprintf("[%s:%03d] %s", eb.info.packagePart, eb.code, eb.msg) } // errorCollection bundles multiple errors. type errorCollection struct { errs []error } // Error implements the error interface. func (ec *errorCollection) Error() string { errMsgs := make([]string, len(ec.errs)) for i, err := range ec.errs { errMsgs[i] = err.Error() } return strings.Join(errMsgs, "\n") } // Annotate creates an error wrapping another one together with a // a code. func Annotate(err error, code int, msgs Messages, args ...interface{}) error { return newErrorBox(err, code, msgs, args...) } // New creates an error with the given code. func New(code int, msgs Messages, args ...interface{}) error { return newErrorBox(nil, code, msgs, args...) } // Collect collects multiple errors into one. func Collect(errs ...error) error { return &errorCollection{ errs: errs, } } // Valid returns true if it is a valid error generated by // this package. func Valid(err error) bool { _, ok := err.(*errorBox) return ok } // IsError checks if an error is one created by this // package and has the passed code func IsError(err error, code int) bool { if e, ok := err.(*errorBox); ok { return e.code == code } return false } // Annotated returns the possibly annotated error. In case of // a different error an invalid type error is returned. func Annotated(err error) error { if e, ok := err.(*errorBox); ok { return e.err } return New(ErrInvalidErrorType, errorMessages, err, err) } // Location returns the package and the file name as well as the line // number of the error. func Location(err error) (string, string, int, error) { if e, ok := err.(*errorBox); ok { return e.info.packageName, e.info.fileName, e.info.line, nil } return "", "", 0, New(ErrInvalidErrorType, errorMessages, err, err) } // Stack returns a slice of errors down to the first // non-errors error in case of annotated errors. func Stack(err error) []error { if eb, ok := err.(*errorBox); ok { return append([]error{eb}, Stack(eb.err)...) } return []error{err} } // All returns a slice of errors in case of collected errors. func All(err error) []error { if ec, ok := err.(*errorCollection); ok { all := make([]error, len(ec.errs)) copy(all, ec.errs) return all } return []error{err} } // DoAll iterates the passed function over all stacked // or collected errors or simply the one that's passed. func DoAll(err error, f func(error)) { switch terr := err.(type) { case *errorBox: for _, serr := range Stack(err) { f(serr) } case *errorCollection: for _, aerr := range All(err) { f(aerr) } default: f(terr) } } // IsInvalidTypeError checks if an error signals an invalid // type in case of testing for an annotated error. func IsInvalidTypeError(err error) bool { return IsError(err, ErrInvalidErrorType) } // NotYetImplementedError returns the common error for a not yet // implemented feature. func NotYetImplementedError(feature string) error { return New(ErrNotYetImplemented, errorMessages, feature) } // IsNotYetImplementedError checks if an error signals a not yet // implemented feature. func IsNotYetImplementedError(err error) bool { return IsError(err, ErrNotYetImplemented) } // DeprecatedError returns the common error for a deprecated // feature. func DeprecatedError(feature string) error { return New(ErrDeprecated, errorMessages, feature) } // IsDeprecatedError checks if an error signals deprecated // feature. func IsDeprecatedError(err error) bool { return IsError(err, ErrDeprecated) } //-------------------- // PRIVATE HELPERS //-------------------- // callInfo bundles the info about the call environment // when a logging statement occurred. type callInfo struct { packageName string packagePart string fileName string funcName string line int } // retrieveCallInfo func retrieveCallInfo() *callInfo { pc, file, line, _ := runtime.Caller(3) _, fileName := path.Split(file) parts := strings.Split(runtime.FuncForPC(pc).Name(), ".") pl := len(parts) packageName := "" funcName := parts[pl-1] if parts[pl-2][0] == '(' { funcName = parts[pl-2] + "." + funcName packageName = strings.Join(parts[0:pl-2], ".") } else { packageName = strings.Join(parts[0:pl-1], ".") } packageParts := strings.Split(packageName, "/") packagePart := strings.ToUpper(packageParts[len(packageParts)-1]) return &callInfo{ packageName: packageName, packagePart: packagePart, fileName: fileName, funcName: funcName, line: line, } } // EOF golib-4.24.2/errors/errors_test.go000066400000000000000000000066011315505703200171210ustar00rootroot00000000000000// Tideland Go Library - Errors - Unit Tests // // Copyright (C) 2013-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package errors_test //-------------------- // IMPORTS //-------------------- import ( "testing" "github.com/tideland/golib/audit" "github.com/tideland/golib/errors" ) //-------------------- // TESTS //-------------------- // TestIsError tests the creation and checking of errors. func TestIsError(t *testing.T) { assert := audit.NewTestingAssertion(t, true) ec := 42 messages := errors.Messages{ec: "test error %d"} err := errors.New(ec, messages, 1) assert.ErrorMatch(err, `\[ERRORS_TEST:042\] test error 1`) assert.True(errors.IsError(err, ec)) assert.False(errors.IsError(err, 0)) err = testError("test error 2") assert.ErrorMatch(err, "test error 2") assert.False(errors.IsError(err, ec)) assert.False(errors.IsError(err, 0)) } // TestValidation checks the validation of errors and // the retrieval of details. func TestValidation(t *testing.T) { assert := audit.NewTestingAssertion(t, true) ec := 1 messages := errors.Messages{ec: "valid"} err := errors.New(ec, messages) packageName, fileName, line, lerr := errors.Location(err) assert.True(errors.Valid(err)) assert.Nil(lerr) assert.Equal(packageName, "github.com/tideland/golib/errors_test") assert.Equal(fileName, "errors_test.go") assert.Equal(line, 51) } // TestAnnotation the annotation of errors with new errors. func TestAnnotation(t *testing.T) { assert := audit.NewTestingAssertion(t, true) ec := 123 messages := errors.Messages{ec: "annotated"} aerr := testError("wrapped") err := errors.Annotate(aerr, ec, messages) assert.ErrorMatch(err, `\[ERRORS_TEST:123\] annotated: wrapped`) assert.Equal(errors.Annotated(err), aerr) assert.True(errors.IsInvalidTypeError(errors.Annotated(aerr))) assert.Length(errors.Stack(err), 2) } // TestCollection tests the collection of multiple errors to one. func TestCollection(t *testing.T) { assert := audit.NewTestingAssertion(t, true) errA := testError("foo") errB := testError("bar") errC := testError("baz") errD := testError("yadda") cerr := errors.Collect(errA, errB, errC, errD) assert.ErrorMatch(cerr, "foo\nbar\nbaz\nyadda") } // TestDoAll tests the iteration over errors. func TestDoAll(t *testing.T) { assert := audit.NewTestingAssertion(t, true) msgs := []string{} f := func(err error) { msgs = append(msgs, err.Error()) } // Test it on annotated errors. messages := errors.Messages{ 1: "foo", 2: "bar", 3: "baz", 4: "yadda", } errX := testError("xxx") errA := errors.Annotate(errX, 1, messages) errB := errors.Annotate(errA, 2, messages) errC := errors.Annotate(errB, 3, messages) errD := errors.Annotate(errC, 4, messages) errors.DoAll(errD, f) assert.Length(msgs, 5) // Test it on collected errors. msgs = []string{} errA = testError("foo") errB = testError("bar") errC = testError("baz") errD = testError("yadda") cerr := errors.Collect(errA, errB, errC, errD) errors.DoAll(cerr, f) assert.Equal(msgs, []string{"foo", "bar", "baz", "yadda"}) // Test it on a single error. msgs = []string{} errA = testError("foo") errors.DoAll(errA, f) assert.Equal(msgs, []string{"foo"}) } //-------------------- // HELPERS //-------------------- type testError string func (e testError) Error() string { return string(e) } // EOF golib-4.24.2/etc/000077500000000000000000000000001315505703200134535ustar00rootroot00000000000000golib-4.24.2/etc/doc.go000066400000000000000000000035611315505703200145540ustar00rootroot00000000000000// Tideland Go Library - Etc // // Copyright (C) 2016-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. // Package etc of the Tideland Go Library package provides the reading, // parsing, and accessing of configuration data. Different readers // can be passed as sources for the SML formatted input. // // {etc // {global // {base-directory [$BASEDIR||/var/lib/myserver]} // {host-address localhost:1234} // {max-users 50} // } // {service-a // {url http://[global/host-address]/service-a} // {directory [global/base-directory||.]/service-a} // } // } // // After reading this from a file, reader, or string the number of users // can be retrieved with a default value of 10 by calling // // maxUsers := cfg.ValueAsInt("global/max-users", 10) // // The leading "etc" node of the path is set by default. // // If values contain templates formatted [||] the // configuration tries to read the value out of the environment (if the // name starts with a dollar sign) or given path inside the configuration. // This will be done tope-down. So like in the example above the global // base directory will be retrieved out of the environment and can later be // referenced by another entry. The default value is optional. It will be // used, if the environment variable or the path cannot be found. If a // path is invalid and has no default the template will stay inside the // value. So accessing the directory of service-a by // // svcDir := cfg.ValueAsString("service-a/directory", ".") // // leads to "/var/lib/myserver/service-a" and if the base directory // isn't set to "./service-a". If nothing is set the default value // is the "." passed in the method call. package etc // EOF golib-4.24.2/etc/errors.go000066400000000000000000000023511315505703200153170ustar00rootroot00000000000000// Tideland Go Library - Etc - Errors // // Copyright (C) 2016-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package etc //-------------------- // IMPORTS //-------------------- import ( "github.com/tideland/golib/errors" ) //-------------------- // CONSTANTS //-------------------- // Error codes of the etc package. const ( ErrIllegalSourceFormat = iota + 1 ErrIllegalConfigSource ErrCannotReadFile ErrCannotPostProcess ErrInvalidPath ErrCannotSplit ErrCannotApply ) var errorMessages = errors.Messages{ ErrIllegalSourceFormat: "illegal source format", ErrIllegalConfigSource: "illegal source for configuration: %v", ErrCannotReadFile: "cannot read configuration file %q", ErrCannotPostProcess: "cannot post-process configuration: %q", ErrInvalidPath: "invalid configuration path %q", ErrCannotSplit: "cannot split configuration", ErrCannotApply: "cannot apply values to configuration", } //-------------------- // ERROR CHECKING //-------------------- // IsInvalidPathError checks if a path cannot be found. func IsInvalidPathError(err error) bool { return errors.IsError(err, ErrInvalidPath) } // EOF golib-4.24.2/etc/etc.go000066400000000000000000000266451315505703200145720ustar00rootroot00000000000000// Tideland Go Library - Etc // // Copyright (C) 2016-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package etc //-------------------- // IMPORTS //-------------------- import ( "context" "fmt" "io" "io/ioutil" "os" "regexp" "strings" "time" "github.com/tideland/golib/collections" "github.com/tideland/golib/errors" "github.com/tideland/golib/sml" "github.com/tideland/golib/stringex" ) //-------------------- // GLOBAL //-------------------- // key is to address a configuration inside a context. type key int var ( etcKey key = 1 etcRoot = []string{"etc"} defaulter = stringex.NewDefaulter("etc", false) ) //-------------------- // VALUE //-------------------- // value helps to use the stringex.Defaulter. type value struct { path []string changer collections.KeyStringValueChanger } // Value retrieves the value or an error. It implements // the Valuer interface. func (v *value) Value() (string, error) { sv, err := v.changer.Value() if err != nil { return "", errors.New(ErrInvalidPath, errorMessages, fullPathToString(v.path)) } return sv, nil } //-------------------- // ETC //-------------------- // Application is used to apply values to a configurtation. type Application map[string]string // Etc contains the read etc configuration and provides access to // it. ThetcRoot node "etc" is automatically preceded to the path. // The node name have to consist out of 'a' to 'z', '0' to '9', and // '-'. The nodes of a path are separated by '/'. type Etc interface { fmt.Stringer // HasPath checks if the configurations has the defined path // regardles of the value or possible subconfigurations. HasPath(path string) bool // Do iterates over the children of the given path and executes // the function f with that path. Do(path string, f func(p string) error) error // ValueAsString retrieves the string value at a given path. If it // doesn't exist the default value dv is returned. ValueAsString(path, dv string) string // ValueAsBool retrieves the bool value at a given path. If it // doesn't exist the default value dv is returned. ValueAsBool(path string, dv bool) bool // ValueAsInt retrieves the int value at a given path. If it // doesn't exist the default value dv is returned. ValueAsInt(path string, dv int) int // ValueAsFloat64 retrieves the float64 value at a given path. If it // doesn't exist the default value dv is returned. ValueAsFloat64(path string, dv float64) float64 // ValueAsTime retrieves the string value at a given path and // interprets it as time with the passed format. If it // doesn't exist the default value dv is returned. ValueAsTime(path, layout string, dv time.Time) time.Time // ValueAsDuration retrieves the duration value at a given path. // If it doesn't exist the default value dv is returned. ValueAsDuration(path string, dv time.Duration) time.Duration // Spit produces a subconfiguration below the passed path. // The last path part will be the new root, all values below // that configuration node will be below the created root. // In case of an invalid path an empty configuration will // be returned as default. Split(path string) (Etc, error) // Dunp creates a map of paths and their values to apply // them into other configurations. Dump() (Application, error) // Apply creates a new configuration by adding of overwriting // the passed values. The keys of the map have to be slash // separated configuration paths without the leading "etc". Apply(appl Application) (Etc, error) // Write writes the configuration as SML to the passed target. // If prettyPrint is true the written SML is indented and has // linebreaks. Write(target io.Writer, prettyPrint bool) error } // etc implements the Etc interface. type etc struct { values collections.KeyStringValueTree } // Read reads the SML source of the configuration from a // reader, parses it, and returns the etc instance. func Read(source io.Reader) (Etc, error) { builder := sml.NewKeyStringValueTreeBuilder() err := sml.ReadSML(source, builder) if err != nil { return nil, errors.Annotate(err, ErrIllegalSourceFormat, errorMessages) } values, err := builder.Tree() if err != nil { return nil, errors.Annotate(err, ErrIllegalSourceFormat, errorMessages) } if err = values.At("etc").Error(); err != nil { return nil, errors.Annotate(err, ErrIllegalSourceFormat, errorMessages) } cfg := &etc{ values: values, } if err = cfg.postProcess(); err != nil { return nil, errors.Annotate(err, ErrCannotPostProcess, errorMessages) } return cfg, nil } // ReadString reads the SML source of the configuration from a // string, parses it, and returns the etc instance. func ReadString(source string) (Etc, error) { return Read(strings.NewReader(source)) } // ReadFile reads the SML source of a configuration file, // parses it, and returns the etc instance. func ReadFile(filename string) (Etc, error) { source, err := ioutil.ReadFile(filename) if err != nil { return nil, errors.Annotate(err, ErrCannotReadFile, errorMessages, filename) } return ReadString(string(source)) } // HasPath implements the Etc interface. func (e *etc) HasPath(path string) bool { fullPath := makeFullPath(path) changer := e.values.At(fullPath...) return changer.Error() == nil } // Do implements the Etc interface. func (e *etc) Do(path string, f func(p string) error) error { fullPath := makeFullPath(path) changer := e.values.At(fullPath...) if changer.Error() != nil { return changer.Error() } kvs, err := changer.List() if err != nil { return err } for _, kv := range kvs { p := pathToString(append(fullPath, kv.Key)) err := f(p) if err != nil { return err } } return nil } // ValueAsString implements the Etc interface. func (e *etc) ValueAsString(path, dv string) string { value := e.valueAt(path) return defaulter.AsString(value, dv) } // ValueAsBool implements the Etc interface. func (e *etc) ValueAsBool(path string, dv bool) bool { value := e.valueAt(path) return defaulter.AsBool(value, dv) } // ValueAsInt implements the Etc interface. func (e *etc) ValueAsInt(path string, dv int) int { value := e.valueAt(path) return defaulter.AsInt(value, dv) } // ValueAsFloat64 implements the Etc interface. func (e *etc) ValueAsFloat64(path string, dv float64) float64 { value := e.valueAt(path) return defaulter.AsFloat64(value, dv) } // ValueAsTime implements the Etc interface. func (e *etc) ValueAsTime(path, format string, dv time.Time) time.Time { value := e.valueAt(path) return defaulter.AsTime(value, format, dv) } // ValueAsDuration implements the Etc interface. func (e *etc) ValueAsDuration(path string, dv time.Duration) time.Duration { value := e.valueAt(path) return defaulter.AsDuration(value, dv) } // Split implements the Etc interface. func (e *etc) Split(path string) (Etc, error) { if !e.HasPath(path) { // Path not found, return empty configuration. return ReadString("{etc}") } fullPath := makeFullPath(path) values, err := e.values.CopyAt(fullPath...) if err != nil { return nil, errors.Annotate(err, ErrCannotSplit, errorMessages) } values.At(fullPath[len(fullPath)-1:]...).SetKey("etc") es := &etc{ values: values, } return es, nil } // Dump implements the Etc interface. func (e *etc) Dump() (Application, error) { appl := Application{} err := e.values.DoAllDeep(func(ks []string, v string) error { if len(ks) == 1 { // Continue on root element. return nil } path := strings.Join(ks[1:], "/") appl[path] = v return nil }) if err != nil { return nil, err } return appl, nil } // Apply implements the Etc interface. func (e *etc) Apply(appl Application) (Etc, error) { ec := &etc{ values: e.values.Copy(), } for path, value := range appl { fullPath := makeFullPath(path) _, err := ec.values.Create(fullPath...).SetValue(value) if err != nil { return nil, errors.Annotate(err, ErrCannotApply, errorMessages) } } return ec, nil } // Write implements the Etc interface. func (e *etc) Write(target io.Writer, prettyPrint bool) error { // Build the nodes tree. builder := sml.NewNodeBuilder() depth := 0 err := e.values.DoAllDeep(func(ks []string, v string) error { doDepth := len(ks) tag := ks[doDepth-1] for i := depth; i > doDepth; i-- { builder.EndTagNode() } switch { case doDepth > depth: builder.BeginTagNode(tag) builder.TextNode(v) depth = doDepth case doDepth == depth: builder.EndTagNode() builder.BeginTagNode(tag) builder.TextNode(v) case doDepth < depth: builder.EndTagNode() builder.BeginTagNode(tag) builder.TextNode(v) depth = doDepth } return nil }) if err != nil { return err } for i := depth; i > 0; i-- { builder.EndTagNode() } root, err := builder.Root() if err != nil { return err } // Now write the node structure. wp := sml.NewStandardSMLWriter() wctx := sml.NewWriterContext(wp, target, prettyPrint, " ") return sml.WriteSML(root, wctx) } // Apply implements the Stringer interface. func (e *etc) String() string { return fmt.Sprintf("%v", e.values) } // valueAt retrieves and encapsulates the value // at a given path. func (e *etc) valueAt(path string) *value { fullPath := makeFullPath(path) changer := e.values.At(fullPath...) return &value{fullPath, changer} } // postProcess replaces templates formated [path||default] // with values found at that path or the default. func (e *etc) postProcess() error { re := regexp.MustCompile("\\[.+(||.+)\\]") // Find all entries with template. changers := e.values.FindAll(func(k, v string) (bool, error) { return re.MatchString(v), nil }) // Change the template. for _, changer := range changers { value, err := changer.Value() if err != nil { return err } found := re.FindString(value) // Look for default value. sourceDefault := strings.SplitN(found[1:len(found)-1], "||", 2) defaultValue := found if len(sourceDefault) > 1 { defaultValue = sourceDefault[1] } // Check if source is environment variable or path. substitute := "" if strings.HasPrefix(sourceDefault[0], "$") { if envValue, ok := os.LookupEnv(sourceDefault[0][1:]); ok { substitute = envValue } else { substitute = defaultValue } } else { substitute = e.ValueAsString(sourceDefault[0], defaultValue) } replaced := strings.Replace(value, found, substitute, -1) _, err = changer.SetValue(replaced) if err != nil { return err } } return nil } //-------------------- // CONTEXT //-------------------- // NewContext returns a new context that carries a configuration. func NewContext(ctx context.Context, cfg Etc) context.Context { return context.WithValue(ctx, etcKey, cfg) } // FromContext returns the configuration stored in ctx, if any. func FromContext(ctx context.Context) (Etc, bool) { cfg, ok := ctx.Value(etcKey).(Etc) return cfg, ok } //-------------------- // HELPERS //-------------------- // makeFullPath creates the full path out of a string. func makeFullPath(path string) []string { parts := stringex.SplitMap(path, "/", func(p string) (string, bool) { if p == "" { return "", false } return strings.ToLower(p), true }) return append(etcRoot, parts...) } // fullPathToString returns the path in a filesystem like notation. func fullPathToString(path []string) string { return "/" + strings.Join(path, "/") } // pathToString returns the path in a filesystem like notation but // with the leading slash and 'etc'. func pathToString(path []string) string { return strings.Join(path[1:], "/") } // EOF golib-4.24.2/etc/etc_test.go000066400000000000000000000240031315505703200156130ustar00rootroot00000000000000// Tideland Go Library - Etc - Unit Tests // // Copyright (C) 2016-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package etc_test //-------------------- // IMPORTS //-------------------- import ( "bytes" "context" "errors" "io/ioutil" "os" "strings" "testing" "github.com/tideland/golib/audit" "github.com/tideland/golib/etc" ) //-------------------- // TESTS //-------------------- // TestRead tests reading a configuration out of a reader. func TestRead(t *testing.T) { assert := audit.NewTestingAssertion(t, true) source := "{etc {foo 42}{bar 24}}" cfg, err := etc.Read(strings.NewReader(source)) assert.Nil(err) source = "{something {gnagnagna}}" cfg, err = etc.Read(strings.NewReader(source)) assert.Nil(cfg) assert.ErrorMatch(err, `*. illegal source format: .* node not found`) source = "{etc {gna 1}{gna 2}}" cfg, err = etc.Read(strings.NewReader(source)) assert.Nil(cfg) assert.ErrorMatch(err, `*. illegal source format: .* cannot build node structure: node has multiple values`) source = "{etc {gna 1 {foo x} 2}}" cfg, err = etc.Read(strings.NewReader(source)) assert.Nil(cfg) assert.ErrorMatch(err, `*. illegal source format: .* cannot build node structure: node has multiple values`) source = "{etc {foo/bar 1}{bar/foo 2}}" cfg, err = etc.Read(strings.NewReader(source)) assert.Nil(cfg) assert.ErrorMatch(err, `*. illegal source format: .*`) } // TestReadFile tests reading a configuration out of a file. func TestReadFile(t *testing.T) { assert := audit.NewTestingAssertion(t, true) tempDir := audit.NewTempDir(assert) defer tempDir.Restore() etcFile, err := ioutil.TempFile(tempDir.String(), "etc") assert.Nil(err) etcFilename := etcFile.Name() _, err = etcFile.WriteString("{etc {foo 42}{bar 24}}") assert.Nil(err) etcFile.Close() cfg, err := etc.ReadFile(etcFilename) assert.Nil(err) v := cfg.ValueAsString("foo", "X") assert.Equal(v, "42") v = cfg.ValueAsString("bar", "Y") assert.Equal(v, "24") _, err = etc.ReadFile("some-not-existing-configuration-file-due-to-weird-name") assert.ErrorMatch(err, `.* cannot read configuration file .*`) } // TestWrite tests the writing of configurations. func TestWrite(t *testing.T) { assert := audit.NewTestingAssertion(t, true) source := "{etc {a Hello}{sub {sub-a World}{sub-b {sub-b-sub My}}}{b Friend}}" cfgIn, err := etc.ReadString(source) assert.Nil(err) var notPretty bytes.Buffer var pretty bytes.Buffer err = cfgIn.Write(¬Pretty, false) assert.Nil(err) err = cfgIn.Write(&pretty, true) assert.Nil(err) notPrettyStr := notPretty.String() prettyStr := pretty.String() cfgOutNotPretty, err := etc.ReadString(notPrettyStr) assert.Nil(err) cfgOutPretty, err := etc.ReadString(prettyStr) assert.Nil(err) paths := []string{ "a", "sub/sub-a", "sub/sub-b/sub-b-sub", "b", } for _, path := range paths { vsIn := cfgIn.ValueAsString(path, "in") vsOutNotPretty := cfgOutNotPretty.ValueAsString(path, "out") assert.Equal(vsIn, vsOutNotPretty) vsOutPretty := cfgOutPretty.ValueAsString(path, "out") assert.Equal(vsIn, vsOutPretty) } } // TestTemplates tests the substitution of templates. func TestTemplates(t *testing.T) { assert := audit.NewTestingAssertion(t, true) err := os.Setenv("GOLIB_ETC_TEST_A", "1.2.3.4") assert.Nil(err) err = os.Unsetenv("GOLIB_ETC_TEST_B") assert.Nil(err) source := `{etc {a foo} {tests {valid-a x[a]x} {valid-b x[sub/b]x} {invalid-c x[c||123]x} {invalid-d x[sub/d||456]x} {invalid-e x[unknown]x} {invalid-f x[]x} {invalid-g x[||]x} {valid-h [$GOLIB_ETC_TEST_A]} {valid-i [$GOLIB_ETC_TEST_B||4.3.2.1]} {invalid-j [$]} } {sub {b bar}} }` cfg, err := etc.Read(strings.NewReader(source)) assert.Nil(err) // First test regular ones, then those with templates. vs := cfg.ValueAsString("a", "xxx") assert.Equal(vs, "foo") vs = cfg.ValueAsString("sub/b", "xxx") assert.Equal(vs, "bar") vs = cfg.ValueAsString("tests/valid-a", "xxx") assert.Equal(vs, "xfoox") vs = cfg.ValueAsString("tests/valid-b", "xxx") assert.Equal(vs, "xbarx") vs = cfg.ValueAsString("tests/invalid-c", "xxx") assert.Equal(vs, "x123x") vs = cfg.ValueAsString("tests/invalid-d", "xxx") assert.Equal(vs, "x456x") vs = cfg.ValueAsString("tests/invalid-e", "xxx") assert.Equal(vs, "x[unknown]x") vs = cfg.ValueAsString("tests/invalid-f", "xxx") assert.Equal(vs, "x[]x") vs = cfg.ValueAsString("tests/invalid-g", "xxx") assert.Equal(vs, "xx") vs = cfg.ValueAsString("tests/valid-h", "xxx") assert.Equal(vs, "1.2.3.4") vs = cfg.ValueAsString("tests/valid-i", "xxx") assert.Equal(vs, "4.3.2.1") vs = cfg.ValueAsString("tests/invalid-j", "xxx") assert.Equal(vs, "[$]") } // TestHasPath tests the checking of paths. func TestHasPath(t *testing.T) { assert := audit.NewTestingAssertion(t, true) source := "{etc {a Hello}{sub {a World}}}" cfg, err := etc.Read(strings.NewReader(source)) assert.Nil(err) assert.True(cfg.HasPath("a")) assert.True(cfg.HasPath("sub")) assert.True(cfg.HasPath("sub/a")) assert.False(cfg.HasPath("b")) assert.False(cfg.HasPath("sub/b")) } // TestDo tests the iteration over the nodes. func TestDo(t *testing.T) { assert := audit.NewTestingAssertion(t, true) source := "{etc {a Hello}{sub-a {a World}{b here}{c I}{d am}}{sub-b {a Tester}}}" cfg, err := etc.Read(strings.NewReader(source)) assert.Nil(err) paths := []string{} err = cfg.Do("", func(path string) error { paths = append(paths, path) return nil }) assert.Nil(err) assert.Length(paths, 3) assert.Equal(paths, []string{"a", "sub-a", "sub-b"}) paths = []string{} err = cfg.Do("sub-a", func(path string) error { paths = append(paths, path) return nil }) assert.Nil(err) assert.Length(paths, 4) assert.Equal(paths, []string{"sub-a/a", "sub-a/b", "sub-a/c", "sub-a/d"}) sentence := "" err = cfg.Do("sub-a", func(path string) error { sentence += " " + cfg.ValueAsString(path, "X") return nil }) assert.Nil(err) assert.Equal(sentence, " World here I am") err = cfg.Do("", func(path string) error { return errors.New(path) }) assert.ErrorMatch(err, "a") } // TestValueSuccess tests the successful retrieval of values. func TestValueSuccess(t *testing.T) { assert := audit.NewTestingAssertion(t, true) source := `{etc {a Hello} {b true} {c -1} {d 47.11 } {sub {a World} {b 42} }}` cfg, err := etc.Read(strings.NewReader(source)) assert.Nil(err) vs := cfg.ValueAsString("a", "foo") assert.Equal(vs, "Hello") vb := cfg.ValueAsBool("b", false) assert.Equal(vb, true) vi := cfg.ValueAsInt("c", 1) assert.Equal(vi, -1) vf := cfg.ValueAsFloat64("d", 1.0) assert.Equal(vf, 47.11) vs = cfg.ValueAsString("sub/a", "bar") assert.Equal(vs, "World") vi = cfg.ValueAsInt("sub/b", 12345) assert.Equal(vi, 42) } // TestGetDefault tests the retrieval of default values. func TestGetFail(t *testing.T) { assert := audit.NewTestingAssertion(t, true) source := "{etc {a Hello}{sub {a World}}}" cfg, err := etc.Read(strings.NewReader(source)) assert.Nil(err) vs := cfg.ValueAsString("b", "foo") assert.Equal(vs, "foo") vb := cfg.ValueAsBool("b", false) assert.Equal(vb, false) vi := cfg.ValueAsInt("c", 1) assert.Equal(vi, 1) vf := cfg.ValueAsFloat64("d", 1.0) assert.Equal(vf, 1.0) vb = cfg.ValueAsBool("sub/a", false) assert.Equal(vb, false) vi = cfg.ValueAsInt("sub/b", 12345) assert.Equal(vi, 12345) } // TestSplit tests the splitting of configurations. func TestSplit(t *testing.T) { assert := audit.NewTestingAssertion(t, true) source := "{etc {a Hello}{sub {a World}{b Friend}}}" cfg, err := etc.ReadString(source) assert.Nil(err) // Test the splitting. subcfg, err := cfg.Split("sub") assert.Nil(err) va := subcfg.ValueAsString("a", "Foo") assert.Equal(va, "World") vb := subcfg.ValueAsString("b", "Bar") assert.Equal(vb, "Friend") // Changing the sub configuration must not // change the original configuration. applied, err := subcfg.Apply(etc.Application{ "c": "Darling", }) ac := applied.ValueAsString("c", "A1") assert.Equal(ac, "Darling") ac = subcfg.ValueAsString("c", "A2") assert.Equal(ac, "A2") ac = cfg.ValueAsString("c", "A3") assert.Equal(ac, "A3") // Try an illegal splitting. subcfg, err = cfg.Split("some/invalid/path") assert.Nil(err) va = subcfg.ValueAsString("a", "Foo") assert.Equal(va, "Foo") vb = subcfg.ValueAsString("b", "Bar") assert.Equal(vb, "Bar") } // TestDump tests the dumping of a configuration. func TestDump(t *testing.T) { assert := audit.NewTestingAssertion(t, true) source := "{etc {a Hello}{sub {a World}}}" cfg, err := etc.ReadString(source) assert.Nil(err) dump, err := cfg.Dump() assert.Nil(err) assert.Length(dump, 3) source = "{etc}" cfg, err = etc.ReadString(source) assert.Nil(err) applied, err := cfg.Apply(dump) assert.Nil(err) vs := applied.ValueAsString("a", "foo") assert.Equal(vs, "Hello") vs = applied.ValueAsString("sub", "bar") assert.Equal(vs, "") vs = applied.ValueAsString("sub/a", "baz") assert.Equal(vs, "World") } // TestApply tests the applying of values. func TestApply(t *testing.T) { assert := audit.NewTestingAssertion(t, true) source := "{etc {a Hello}{sub {a World}}}" cfg, err := etc.ReadString(source) assert.Nil(err) applied, err := cfg.Apply(etc.Application{ "sub/a": "Tester", "B": "42", }) assert.Nil(err) vs := applied.ValueAsString("a", "foo") assert.Equal(vs, "Hello") vs = applied.ValueAsString("sub/a", "bar") assert.Equal(vs, "Tester") vi := applied.ValueAsInt("b", -1) assert.Equal(vi, 42) } // TestContext tests adding a configuration to a context // an retrieve it again. func TestContext(t *testing.T) { assert := audit.NewTestingAssertion(t, true) source := "{etc {a Hello}{sub {a World}}}" cfg, err := etc.ReadString(source) assert.Nil(err) ctx := context.Background() noCfg, ok := etc.FromContext(ctx) assert.False(ok) assert.Nil(noCfg) cfgCtx := etc.NewContext(ctx, cfg) yesCfg, ok := etc.FromContext(cfgCtx) assert.True(ok) vs := yesCfg.ValueAsString("a", "foo") assert.Equal(vs, "Hello") vs = yesCfg.ValueAsString("sub/a", "bar") assert.Equal(vs, "World") } // EOF golib-4.24.2/feed/000077500000000000000000000000001315505703200136035ustar00rootroot00000000000000golib-4.24.2/feed/atom/000077500000000000000000000000001315505703200145435ustar00rootroot00000000000000golib-4.24.2/feed/atom/atom.go000066400000000000000000000317421315505703200160410ustar00rootroot00000000000000// Tideland Go Library - Atom Feed // // Copyright (C) 2012-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package atom //-------------------- // IMPORTS //-------------------- import ( "encoding/xml" "fmt" "io" "io/ioutil" "net/http" "net/url" "strings" "time" "github.com/tideland/golib/errors" "github.com/tideland/golib/feed/utils" ) //-------------------- // CONST //-------------------- // Attributes of Atom XML documents. const ( Version = "1.0" XMLNS = "http://www.w3.org/2005/Atom" TextType = "text" HTMLType = "html" XHTMLType = "xhtml" AlternateRel = "alternate" EnclosureRel = "enclosure" RelatedRel = "related" SelfRel = "self" ViaRel = "via" ) //-------------------- // MODEL //-------------------- // Feed is the root element of the document. type Feed struct { XMLName string `xml:"feed"` XMLNS string `xml:"xmlns,attr"` Id string `xml:"id"` Title *Text `xml:"title"` Updated string `xml:"updated"` Authors []*Author `xml:"author,omitempty"` Link *Link `xml:"link,omitempty"` Categories []*Category `xml:"category,omitempty"` Contributors []*Contributor `xml:"contributor,omitempty"` Generator *Generator `xml:"generator,omitempty"` Icon string `xml:"icon,omitempty"` Logo string `xml:"logo,omitempty"` Rights *Text `xml:"rights,omitempty"` Subtitle *Text `xml:"subtitle,omitempty"` Entries []*Entry `xml:"entry"` } // Validate checks if the feed is valid. func (f *Feed) Validate() error { if f.XMLNS != XMLNS { return errors.New(ErrValidation, errorMessages, "invalid namespace %q: has to be %q", f.XMLNS, XMLNS) } if _, err := url.Parse(f.Id); err != nil { return errors.Annotate(err, ErrParsing, errorMessages, "feed id") } if err := validateText("feed title", f.Title, true); err != nil { return err } if _, err := ParseTime(f.Updated); err != nil { return errors.Annotate(err, ErrParsing, errorMessages, "feed update") } for _, author := range f.Authors { if err := author.Validate(); err != nil { return err } } if f.Link != nil { if err := f.Link.Validate(); err != nil { return err } } for _, category := range f.Categories { if err := category.Validate(); err != nil { return err } } for _, contributor := range f.Contributors { if err := contributor.Validate(); err != nil { return err } } if f.Generator != nil { if err := f.Generator.Validate(); err != nil { return err } } if err := validateText("feed rights", f.Rights, false); err != nil { return err } if err := validateText("feed subtitle", f.Subtitle, false); err != nil { return err } allEntriesWithAuthor := true for _, entry := range f.Entries { if err := entry.Validate(); err != nil { return err } allEntriesWithAuthor = allEntriesWithAuthor && len(entry.Authors) > 0 } if !allEntriesWithAuthor && len(f.Authors) == 0 { return errors.New(ErrValidation, errorMessages, "author(s) of feed or entries missing") } return nil } // Text contains human-readable text, usually in small quantities. The type // attribute determines how this information is encoded. type Text struct { Text string `xml:",chardata"` Src string `xml:"src,attr,omitempty"` Type string `xml:"type,attr,omitempty"` } // PlainText returns the text as string without any markup. Content from // external sources will be retrieved. func (t Text) PlainText() (string, error) { // Retrieve the raw text. var raw string if t.Src != "" { resp, err := http.Get(t.Src) if err != nil { return "", errors.Annotate(err, ErrNoPlainText, errorMessages, t) } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { return "", errors.Annotate(err, ErrNoPlainText, errorMessages, t) } raw = string(body) } else { raw = t.Text } // Handle raw text depending on type. switch t.Type { case "", TextType: return raw, nil case HTMLType: return utils.StripTags(raw, false, true) case XHTMLType: return utils.StripTags(raw, true, true) } if strings.HasSuffix(t.Type, "xml") { return utils.StripTags(raw, true, true) } err := fmt.Errorf("illegal test type: %s", t.Type) return "", errors.Annotate(err, ErrNoPlainText, errorMessages, t) } // validateText ensures that a text is set if it's mandatory and that // the type is correct. func validateText(description string, t *Text, mandatory bool) error { if (t == nil || t.Text == "") && mandatory { return errors.New(ErrValidation, errorMessages, "%s must not be missing or empty", description) } if t != nil { if t.Src != "" { if _, err := url.Parse(t.Src); err != nil { what := fmt.Sprintf("%s src", description) return errors.Annotate(err, ErrParsing, errorMessages, what) } } switch t.Type { case "", TextType, HTMLType, XHTMLType: // OK. default: return errors.New(ErrValidation, errorMessages, "%s has illegal type %q", description, t.Type) } } return nil } // Author names the author of the feed. type Author struct { Name string `xml:"name"` URI string `xml:"uri,omitempty"` EMail string `xml:"email,omitempty"` } // Validate checks if a feed author is valid. func (a *Author) Validate() error { if a.Name == "" { return errors.New(ErrValidation, errorMessages, "feed author name must not be empty") } if a.URI != "" { if _, err := url.Parse(a.URI); err != nil { return errors.Annotate(err, ErrValidation, errorMessages, "feed author uri is not parsable") } } return nil } // Link identifies a related web page. type Link struct { HRef string `xml:"href,attr"` Rel string `xml:"rel,attr,omitempty"` Type string `xml:"type,attr,omitempty"` HRefLang string `xml:"hreflang,attr,omitempty"` Title string `xml:"title,attr,omitempty"` Length int `xml:"length,attr,omitempty"` } // Validate checks if the feed link is valid. func (l *Link) Validate() error { if _, err := url.Parse(l.HRef); err != nil { return errors.Annotate(err, ErrValidation, errorMessages, "feed link href is not parsable") } switch l.Rel { case "", AlternateRel, EnclosureRel, RelatedRel, SelfRel, ViaRel: // OK. default: if _, err := url.Parse(l.Rel); err != nil { return errors.Annotate(err, ErrValidation, errorMessages, "feed link rel is neither predefined nor parsable") } } return nil } // Category specifies a category that the feed belongs to. type Category struct { Term string `xml:"term,attr"` Scheme string `xml:"scheme,attr,omitempty"` Label string `xml:"label,attr,omitempty"` } // Validate checks if a feed category is valid. func (c *Category) Validate() error { if c.Term == "" { return errors.New(ErrValidation, errorMessages, "feed category term must not be empty") } if c.Scheme != "" { if _, err := url.Parse(c.Scheme); err != nil { return errors.Annotate(err, ErrValidation, errorMessages, "feed category scheme is not parsable") } } return nil } // Contributor names one contributor of the feed. type Contributor struct { Name string `xml:"name"` } // Validate checks if a feed contributor is valid. func (c *Contributor) Validate() error { if c.Name == "" { return errors.New(ErrValidation, errorMessages, "feed contributor name must not be empty") } return nil } // Generator identifies the software used to generate the feed, // for debugging and other purposes. type Generator struct { Generator string `xml:",chardata"` URI string `xml:"uri,attr,omitempty"` Version string `xml:"version,attr,omitempty"` } // Validate checks if a feed generator is valid. func (g *Generator) Validate() error { if g.Generator == "" { return errors.New(ErrValidation, errorMessages, "feed generator must not be empty") } if g.URI != "" { if _, err := url.Parse(g.URI); err != nil { return errors.Annotate(err, ErrParsing, errorMessages, "feed generator URI") } } return nil } // Entry defines one feed entry. type Entry struct { Id string `xml:"id"` Title *Text `xml:"title"` Updated string `xml:"updated"` Authors []*Author `xml:"author,omitempty"` Content *Text `xml:"content,omitempty"` Link *Link `xml:"link,omitempty"` Summary *Text `xml:"subtitle,omitempty"` Categories []*Category `xml:"category,omitempty"` Contributors []*Contributor `xml:"contributor,omitempty"` Published string `xml:"published,omitempty"` Source *Source `xml:"source,omitempty"` Rights *Text `xml:"rights,omitempty"` } // Validate checks if the feed entry is valid. func (e *Entry) Validate() error { if _, err := url.Parse(e.Id); err != nil { return errors.Annotate(err, ErrParsing, errorMessages, "feed entry id") } if err := validateText("feed entry title", e.Title, true); err != nil { return err } if _, err := ParseTime(e.Updated); err != nil { return errors.Annotate(err, ErrParsing, errorMessages, "feed entry update") } for _, author := range e.Authors { if err := author.Validate(); err != nil { return err } } if err := validateText("feed entry content", e.Content, false); err != nil { return err } if e.Link != nil { if err := e.Link.Validate(); err != nil { return err } } if err := validateText("feed entry summary", e.Summary, false); err != nil { return err } for _, category := range e.Categories { if err := category.Validate(); err != nil { return err } } for _, contributor := range e.Contributors { if err := contributor.Validate(); err != nil { return err } } if _, err := ParseTime(e.Published); err != nil { return errors.Annotate(err, ErrParsing, errorMessages, "feed entry published") } if e.Source != nil { if err := e.Source.Validate(); err != nil { return err } } if err := validateText("feed entry rights", e.Rights, false); err != nil { return err } return nil } // Source preserves the source feeds metadata if the entry is copied // from one feed into another feed. type Source struct { Authors []*Author `xml:"author,omitempty"` Categories []*Category `xml:"category,omitempty"` Contributors []*Contributor `xml:"contributor,omitempty"` Generator *Generator `xml:"generator,omitempty"` Icon string `xml:"icon,omitempty"` Id string `xml:"id,omitempty"` Link *Link `xml:"link,omitempty"` Logo string `xml:"logo,omitempty"` Rights *Text `xml:"rights,omitempty"` Subtitle *Text `xml:"subtitle,omitempty"` Title *Text `xml:"title,omitempty"` Updated string `xml:"updated,omitempty"` } // Validate checks if a feed entry source is valid. func (s *Source) Validate() error { for _, author := range s.Authors { if err := author.Validate(); err != nil { return err } } for _, category := range s.Categories { if err := category.Validate(); err != nil { return err } } for _, contributor := range s.Contributors { if err := contributor.Validate(); err != nil { return err } } if s.Generator != nil { if err := s.Generator.Validate(); err != nil { return err } } if _, err := url.Parse(s.Id); err != nil { return errors.Annotate(err, ErrParsing, errorMessages, "feed entry source id") } if s.Link != nil { if err := s.Link.Validate(); err != nil { return err } } if err := validateText("feed entry source rights", s.Rights, false); err != nil { return err } if err := validateText("feed entry source subtitle", s.Subtitle, false); err != nil { return err } if err := validateText("feed entry source title", s.Title, false); err != nil { return err } if _, err := ParseTime(s.Updated); err != nil { return errors.Annotate(err, ErrParsing, errorMessages, "feed entry source update") } return nil } //-------------------- // FUNCTIONS //-------------------- // ParseTime analyzes the Atom date/time string and returns it as Go time. func ParseTime(s string) (t time.Time, err error) { formats := []string{time.RFC3339, time.RFC3339Nano} for _, format := range formats { t, err = time.Parse(format, s) if err == nil { return } } return } // ComposeTime takes a Go time and converts it into a valid Atom time string. func ComposeTime(t time.Time) string { return t.Format(time.RFC3339) } // Encode writes the feed to the writer. func Encode(w io.Writer, feed *Feed) error { enc := xml.NewEncoder(w) if _, err := w.Write([]byte(xml.Header)); err != nil { return err } return enc.Encode(feed) } // Decode reads the feed from the reader. func Decode(r io.Reader) (*Feed, error) { dec := xml.NewDecoder(r) dec.CharsetReader = utils.CharsetReader feed := &Feed{} if err := dec.Decode(feed); err != nil { return nil, err } return feed, nil } // Get retrieves a feed from the given URL. func Get(u *url.URL) (*Feed, error) { resp, err := http.Get(u.String()) if err != nil { return nil, err } defer resp.Body.Close() return Decode(resp.Body) } // EOF golib-4.24.2/feed/atom/atom_test.go000066400000000000000000000044351315505703200170770ustar00rootroot00000000000000// Tideland Go Library - Atom Feed // // Copyright (C) 2012-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package atom_test //-------------------- // IMPORTS //-------------------- import ( "bytes" "net/url" "testing" "time" "github.com/tideland/golib/audit" "github.com/tideland/golib/feed/atom" ) //-------------------- // TESTS //-------------------- // Test parsing and composing of date/times. func TestParseComposeTime(t *testing.T) { assert := audit.NewTestingAssertion(t, true) nowOne := time.Now() strOne := atom.ComposeTime(nowOne) nowTwo, err := atom.ParseTime(strOne) strTwo := atom.ComposeTime(nowTwo) assert.Nil(err) assert.Equal(strOne, strTwo) } // Test encoding and decoding a doc. func TestEncodeDecode(t *testing.T) { assert := audit.NewTestingAssertion(t, true) a1 := &atom.Feed{ XMLNS: atom.XMLNS, Id: "http://tideland.biz/feed/atom", Title: &atom.Text{"Test Encode/Decode", "", "text"}, Updated: atom.ComposeTime(time.Now()), Entries: []*atom.Entry{ { Id: "http://tideland.biz/feed/atom/1", Title: &atom.Text{"Entry 1", "", "text"}, Updated: atom.ComposeTime(time.Now()), }, { Id: "http://tideland.biz/feed/atom/2", Title: &atom.Text{"Entry 2", "", "text"}, Updated: atom.ComposeTime(time.Now()), }, }, } b := &bytes.Buffer{} err := atom.Encode(b, a1) assert.Nil(err, "Encoding returns no error.") assert.Substring(`Test Encode/Decode`, b.String(), "Title has been encoded correctly.") a2, err := atom.Decode(b) assert.Nil(err, "Decoding returns no error.") assert.Equal(a2.Title.Text, "Test Encode/Decode", "Title has been decoded correctly.") assert.Length(a2.Entries, 2, "Decoded feed has the right number of items.") } // Test getting a feed. func TestGet(t *testing.T) { assert := audit.NewTestingAssertion(t, true) u, _ := url.Parse("http://rss.golem.de/rss.php?feed=ATOM1.0") f, err := atom.Get(u) assert.Nil(err, "Getting the Atom document returns no error.") err = f.Validate() assert.Nil(err, "Validating returns no error.") b := &bytes.Buffer{} err = atom.Encode(b, f) assert.Nil(err, "Encoding returns no error.") assert.Logf("--- Atom ---\n%s", b) } // EOF golib-4.24.2/feed/atom/doc.go000066400000000000000000000007721315505703200156450ustar00rootroot00000000000000// Tideland Go Library - Atom Feed // // Copyright (C) 2012-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. // Package atom of the Tideland Go Library implements an atom feed client. // // The Atom package provides the Atom XML schema as Go types for the // usage with the standard marshalling/unmarshalling. The supported // format is Atom 1.0. A client allows to retrieve Atom documents. package atom // EOF golib-4.24.2/feed/atom/errors.go000066400000000000000000000022431315505703200164070ustar00rootroot00000000000000// Tideland Go Library - Atom Feed // // Copyright (C) 2012-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package atom //-------------------- // IMPORTS //-------------------- import ( "github.com/tideland/golib/errors" ) //-------------------- // CONSTANTS //-------------------- // Error codes of the atom feed package. const ( ErrValidation = iota + 1 ErrParsing ErrNoPlainText ) var errorMessages = errors.Messages{ ErrParsing: "cannot parse %s", ErrNoPlainText: "cannot convert text element %q to plain text", } //-------------------- // ERROR CHECKING //-------------------- // IsValidationError checks if the error signals an invalid feed. func IsValidationError(err error) bool { return errors.IsError(err, ErrValidation) } // IsParsingError checks if the error signals a bad formatted value. func IsParsingError(err error) bool { return errors.IsError(err, ErrParsing) } // IsNoPlainTextError checks if the error signals no plain content // inside a text element. func IsNoPlainTextError(err error) bool { return errors.IsError(err, ErrNoPlainText) } // EOF golib-4.24.2/feed/rss/000077500000000000000000000000001315505703200144125ustar00rootroot00000000000000golib-4.24.2/feed/rss/doc.go000066400000000000000000000007621315505703200155130ustar00rootroot00000000000000// Tideland Go Library - RSS Feed // // Copyright (C) 2012-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. // Package rss of the Tideland Go Library implements an RSS feed client. // // The RSS package provides the RSS XML schema as Go types for the // usage with the standard marshalling/unmarshalling. The supported // format is RSS 2.0. A client allows to retrieve RSS documents. package rss // EOF golib-4.24.2/feed/rss/errors.go000066400000000000000000000016301315505703200162550ustar00rootroot00000000000000// Tideland Go Library - RSS Feed - Errors // // Copyright (C) 2012-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package rss //-------------------- // IMPORTS //-------------------- import ( "github.com/tideland/golib/errors" ) //-------------------- // CONSTANTS //-------------------- // Error codes of the RSS package. const ( ErrValidation = iota + 1 ErrParsing ) var errorMessages = map[int]string{ ErrParsing: "cannot parse %s", } //-------------------- // ERROR CHECKING //-------------------- // IsValidationError checks if the error signals an invalid feed. func IsValidationError(err error) bool { return errors.IsError(err, ErrValidation) } // IsParsingError checks if the error signals a bad formatted value. func IsParsingError(err error) bool { return errors.IsError(err, ErrParsing) } // EOF golib-4.24.2/feed/rss/rss.go000066400000000000000000000310261315505703200155520ustar00rootroot00000000000000// Tideland Go Library - RSS Feed // // Copyright (C) 2012-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package rss //-------------------- // IMPORTS //-------------------- import ( "encoding/xml" "io" "net/http" "net/url" "time" "github.com/tideland/golib/errors" "github.com/tideland/golib/feed/utils" ) //-------------------- // CONST //-------------------- // Version of the RSS. const ( Version = "2.0" ) const ( rssDate = "Mon, 02 Jan 2006 15:04 MST" rssDateV1 = "Mon, 02 Jan 2006 15:04:05 MST" rssDateV2 = "02 Jan 2006 15:04 MST" rssDateV3 = "Mon, 02 Jan 2006 15:04 -0700" rssDateV4 = "02 Jan 2006 15:04 -0700" ) //-------------------- // MODEL //-------------------- // RSS is the root element of the document. type RSS struct { XMLName string `xml:"rss"` Version string `xml:"version,attr"` Channel Channel `xml:"channel"` } // Validate checks if the RSS document is valid. func (r *RSS) Validate() error { if r.Version != Version { return errors.New(ErrValidation, errorMessages, "invalid RSS document version: %q", r.Version) } return r.Channel.Validate() } // Channel is the one channel element of the RSS document. type Channel struct { Title string `xml:"title"` Description string `xml:"description"` Link string `xml:"link"` Categories []*Category `xml:"category,omitempty"` Cloud *Cloud `xml:"cloud,omitempty"` Copyright string `xml:"copyright,omitempty"` Docs string `xml:"docs,omitempty"` Generator string `xml:"generator,omitempty"` Image *Image `xml:"image,omitempty"` Language string `xml:"language,omitempty"` LastBuildDate string `xml:"lastBuildDate,omitempty"` ManagingEditor string `xml:"managingEditor,omitempty"` PubDate string `xml:"pubDate,omitempty"` Rating string `xml:"rating,omitempty"` SkipDays *SkipDays `xml:"skipDays,omitempty"` SkipHours *SkipHours `xml:"skipHours,omitempty"` TextInput string `xml:"textInput,omitempty"` TTL int `xml:"ttl,omitempty"` WebMaster string `xml:"webMaster,omitempty"` Items []*Item `xml:"item,omitempty"` } // Validate checks if the cannel is valid. func (c Channel) Validate() error { if c.Title == "" { return errors.New(ErrValidation, errorMessages, "channel title must not be empty") } if c.Description == "" { return errors.New(ErrValidation, errorMessages, "channel description must not be empty") } if c.Link == "" { return errors.New(ErrValidation, errorMessages, "channel link must not be empty") } if _, err := url.Parse(c.Link); err != nil { return errors.Annotate(err, ErrParsing, errorMessages, "channel link") } for _, category := range c.Categories { if err := category.Validate(); err != nil { return err } } if c.Cloud != nil { if err := c.Cloud.Validate(); err != nil { return err } } if c.Docs != "" && c.Docs != "http://blogs.law.harvard.edu/tech/rss" { return errors.New(ErrValidation, errorMessages, "docs %q is not valid", c.Docs) } if c.Image != nil { if err := c.Image.Validate(); err != nil { return err } } if c.Language != "" { // TODO(mue) Language has to be validated. } if c.LastBuildDate != "" { if _, err := ParseTime(c.LastBuildDate); err != nil { return errors.Annotate(err, ErrParsing, errorMessages, "channel last build date") } } if c.PubDate != "" { if _, err := ParseTime(c.PubDate); err != nil { return errors.Annotate(err, ErrParsing, errorMessages, "channel publication date") } } if c.SkipDays != nil { if err := c.SkipDays.Validate(); err != nil { return err } } if c.SkipHours != nil { if err := c.SkipHours.Validate(); err != nil { return err } } if c.TTL < 0 { return errors.New(ErrValidation, errorMessages, "channel ttl is below zero") } for _, item := range c.Items { if err := item.Validate(); err != nil { return err } } return nil } // Category identifies a category or tag to which the feed belongs. type Category struct { Category string `xml:",chardata"` Domain string `xml:"domain,attr,omitempty"` } // Validate checks if the category is valid. func (c *Category) Validate() error { if c.Category == "" { return errors.New(ErrValidation, errorMessages, "channel category must not be empty") } return nil } // Cloud indicates that updates to the feed can be monitored using a web service // that implements the RssCloud application programming interface. type Cloud struct { Domain string `xml:"domain,attr"` Port int `xml:"port,attr,omitempty"` Path string `xml:"path,attr"` RegisterProcedure string `xml:"registerProcedure,attr"` Protocol string `xml:"protocol,attr"` } // Validate checks if the cloud is valid. func (c *Cloud) Validate() error { if c.Domain == "" { return errors.New(ErrValidation, errorMessages, "cloud domain must not be empty") } if c.Path == "" || c.Path[0] != '/' { return errors.New(ErrValidation, errorMessages, "cloud path %q must not be empty and has to start with a slash", c.Path) } if c.Port < 1 || c.Port > 65535 { return errors.New(ErrValidation, errorMessages, "cloud port %d is out of range", c.Port) } return nil } // Image supplies a graphical logo for the feed . type Image struct { Link string `xml:"link"` Title string `xml:"title"` URL string `xml:"url"` Description string `xml:"description,omitempty"` Height int `xml:"height,omitempty"` Width int `xml:"width,omitempty"` } // Validate checks if the image is valid. func (i *Image) Validate() error { if _, err := url.Parse(i.Link); err != nil { return errors.New(ErrValidation, errorMessages, "image link is not parsable", i.Link) } if i.Title == "" { return errors.New(ErrValidation, errorMessages, "image title must not be empty") } if _, err := url.Parse(i.URL); err != nil { return errors.Annotate(err, ErrParsing, errorMessages, "image title") } if i.Height < 1 || i.Height > 400 { return errors.New(ErrValidation, errorMessages, "image height %d is out of range from 1 to 400", i.Height) } if i.Width < 1 || i.Width > 144 { return errors.New(ErrValidation, errorMessages, "image width %d is out of range from 1 to 144", i.Width) } return nil } // SkipDays identifies days of the week during which the feed is not updated. type SkipDays struct { Days []string `xml:"day"` } // Validate checks if the skip days are valid. func (s *SkipDays) Validate() error { skipDays := map[string]bool{ "Monday": true, "Tuesday": true, "Wednesday": true, "Thursday": true, "Friday": true, "Saturday": true, "Sunday": true, } for _, day := range s.Days { if !skipDays[day] { return errors.New(ErrValidation, errorMessages, "skip day %q is invalid", day) } } return nil } // SkipHours identifies the hours of the day during which the feed is not updated. type SkipHours struct { Hours []int `xml:"hour"` } // Validate checks if the skip hours are valid. func (s *SkipHours) Validate() error { for _, hour := range s.Hours { if hour < 0 || hour > 23 { return errors.New(ErrValidation, errorMessages, "skip hour %d is out of range from 0 to 23", hour) } } return nil } // TextInput defines a form to submit a text query to the feed's publisher over // the Common Gateway Interface (CGI). type TextInput struct { Description string `xml:"description"` Link string `xml:"link"` Name string `xml:"name"` Title string `xml:"title"` } // Validate checks if the text input is valid. func (t *TextInput) Validate() error { if t.Description == "" { return errors.New(ErrValidation, errorMessages, "text input description must not be empty") } if _, err := url.Parse(t.Link); err != nil { return errors.Annotate(err, ErrParsing, errorMessages, "text input link") } if t.Name == "" { return errors.New(ErrValidation, errorMessages, "text input name must not be empty") } if t.Title == "" { return errors.New(ErrValidation, errorMessages, "text input title must not be empty") } return nil } // Item represents distinct content published in the feed such as a news article, // weblog entry or some other form of discrete update. It must contain either a // title or description. type Item struct { Title string `xml:"title,omitempty"` Description string `xml:"description,omitempty"` Author string `xml:"author,omitempty"` Categories []*Category `xml:"category,omitempty"` Comments string `xml:"comments,omitempty"` Enclosure *Enclosure `xml:"enclosure,omitempty"` GUID *GUID `xml:"guid,omitempty"` Link string `xml:"link,omitempty"` PubDate string `xml:"pubDate,omitempty"` Source *Source `xml:"source,omitempty"` } // Validate checks if the item is valid. func (i *Item) Validate() error { if i.Title == "" { if i.Description == "" { return errors.New(ErrValidation, errorMessages, "item title or description must not be empty") } } if i.Comments != "" { if _, err := url.Parse(i.Comments); err != nil { return errors.Annotate(err, ErrParsing, errorMessages, "item comments") } } if i.Enclosure != nil { if err := i.Enclosure.Validate(); err != nil { return err } } if i.GUID != nil { if err := i.GUID.Validate(); err != nil { return err } } if i.Link != "" { if _, err := url.Parse(i.Link); err != nil { return errors.Annotate(err, ErrParsing, errorMessages, "item link") } } if i.PubDate != "" { if _, err := ParseTime(i.PubDate); err != nil { return errors.Annotate(err, ErrParsing, errorMessages, "item publcation date") } } if i.Source != nil { if err := i.Source.Validate(); err != nil { return err } } return nil } // Enclosure associates a media object such as an audio or video file with the item. type Enclosure struct { Length int64 `xml:"length,attr"` Type string `xml:"type,attr"` URL string `xml:"url,attr"` } // Validate checks if the enclosure is valid. func (e *Enclosure) Validate() error { if e.Length < 1 { return errors.New(ErrValidation, errorMessages, "item enclosure length %d is too small", e.Length) } if e.Type == "" { return errors.New(ErrValidation, errorMessages, "item enclosure type must not be empty") } if _, err := url.Parse(e.URL); err != nil { return errors.Annotate(err, ErrParsing, errorMessages, "item enclosure url") } return nil } // GUID provides a string that uniquely identifies the item. type GUID struct { GUID string `xml:",chardata"` IsPermaLink bool `xml:"isPermaLink,attr,omitempty"` } // Validate checks if the GUID is valid. func (g *GUID) Validate() error { if g.IsPermaLink { if _, err := url.Parse(g.GUID); err != nil { return errors.Annotate(err, ErrParsing, errorMessages, "item GUID") } } return nil } // Source indicates the fact that the item has been republished from another RSS feed. type Source struct { Source string `xml:",chardata"` URL string `xml:"url,attr"` } // Validate checks if the source is valid. func (s *Source) Validate() error { if s.Source == "" { return errors.New(ErrValidation, errorMessages, "item source must not be empty") } if _, err := url.Parse(s.URL); err != nil { return errors.Annotate(err, ErrParsing, errorMessages, "item source URL") } return nil } //-------------------- // FUNCTIONS //-------------------- // ParseTime analyzes the RSS date/time string and returns it as Go time. func ParseTime(s string) (t time.Time, err error) { formats := []string{rssDate, rssDateV1, rssDateV2, rssDateV3, rssDateV4, time.RFC822, time.RFC822Z} for _, format := range formats { t, err = time.Parse(format, s) if err == nil { return } } return } // ComposeTime takes a Go time and converts it into a valid RSS time string. func ComposeTime(t time.Time) string { return t.Format(rssDate) } // Encode writes the RSS document to the writer. func Encode(w io.Writer, rss *RSS) error { enc := xml.NewEncoder(w) if _, err := w.Write([]byte(xml.Header)); err != nil { return err } return enc.Encode(rss) } // Decode reads the RSS document from the reader. func Decode(r io.Reader) (*RSS, error) { dec := xml.NewDecoder(r) dec.CharsetReader = utils.CharsetReader rss := &RSS{} if err := dec.Decode(rss); err != nil { return nil, err } return rss, nil } // Get retrieves an RSS document from the given URL. func Get(u *url.URL) (*RSS, error) { resp, err := http.Get(u.String()) if err != nil { return nil, err } defer resp.Body.Close() return Decode(resp.Body) } // EOF golib-4.24.2/feed/rss/rss_test.go000066400000000000000000000067271315505703200166230ustar00rootroot00000000000000// Tideland Go Library - RSS Feed - Unit Tests // // Copyright (C) 2012-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package rss_test //-------------------- // IMPORTS //-------------------- import ( "bytes" "net/url" "testing" "time" "github.com/tideland/golib/audit" "github.com/tideland/golib/feed/rss" ) //-------------------- // TESTS //-------------------- // Test parsing and composing of date/times. func TestParseComposeTime(t *testing.T) { assert := audit.NewTestingAssertion(t, true) nowOne := time.Now() strOne := rss.ComposeTime(nowOne) nowTwo, err := rss.ParseTime(strOne) strTwo := rss.ComposeTime(nowTwo) assert.Nil(err) assert.Equal(strOne, strTwo) // Now some tests with different date formats. _, err = rss.ParseTime("21 Jun 2012 23:00 CEST") assert.Nil(err, "No error during time parsing.") _, err = rss.ParseTime("Thu, 21 Jun 2012 23:00 CEST") assert.Nil(err, "No error during time parsing.") _, err = rss.ParseTime("Thu, 21 Jun 2012 23:00 +0100") assert.Nil(err, "No error during time parsing.") } // Test encoding and decoding a doc. func TestEncodeDecode(t *testing.T) { assert := audit.NewTestingAssertion(t, true) r1 := &rss.RSS{ Version: rss.Version, Channel: rss.Channel{ Title: "Test Encode/Decode", Link: "http://www.tideland.biz/rss", Description: "A test document.", Categories: []*rss.Category{ {"foo", ""}, {"bar", "baz"}, }, Items: []*rss.Item{ { Title: "Item 1", Description: "This is item 1", GUID: &rss.GUID{"http://www.tideland.biz/rss/item-1", false}, }, { Title: "Item 2", Description: "This is item 2", GUID: &rss.GUID{"http://www.tideland.biz/rss/item-2", true}, }, }, }, } b := &bytes.Buffer{} err := rss.Encode(b, r1) assert.Nil(err, "Encoding returns no error.") assert.Substring("Test Encode/Decode", b.String(), "Title has been encoded correctly.") r2, err := rss.Decode(b) assert.Nil(err, "Decoding returns no error.") assert.Equal(r2.Channel.Title, "Test Encode/Decode", "Title has been decoded correctly.") assert.Length(r2.Channel.Items, 2, "Decoded document has the right number of items.") } // Test validating a doc. func TestValidate(t *testing.T) { assert := audit.NewTestingAssertion(t, true) r := &rss.RSS{ Version: "1.2.3", } err := r.Validate() assert.ErrorMatch(err, `.* invalid RSS document version: "1.2.3"`) r = &rss.RSS{ Version: rss.Version, } err = r.Validate() assert.ErrorMatch(err, `.* channel title must not be empty`) r = &rss.RSS{ Version: rss.Version, Channel: rss.Channel{ Title: "Test Title", }, } err = r.Validate() assert.ErrorMatch(err, `.* channel description must not be empty`) r = &rss.RSS{ Version: rss.Version, Channel: rss.Channel{ Title: "Test Title", Description: "Test Description", }, } err = r.Validate() assert.ErrorMatch(err, `.* channel link must not be empty`) } // Test getting a doc. func TestGet(t *testing.T) { assert := audit.NewTestingAssertion(t, true) u, _ := url.Parse("http://www.rssboard.org/files/sample-rss-2.xml") r, err := rss.Get(u) assert.Nil(err, "Getting the RSS document returns no error.") err = r.Validate() assert.Nil(err, "Validating returns no error.") b := &bytes.Buffer{} err = rss.Encode(b, r) assert.Nil(err, "Encoding returns no error.") assert.Logf("--- RSS ---\n%s", b) } // EOF golib-4.24.2/feed/utils/000077500000000000000000000000001315505703200147435ustar00rootroot00000000000000golib-4.24.2/feed/utils/doc.go000066400000000000000000000005131315505703200160360ustar00rootroot00000000000000// Tideland Go Libray - Feed Utils // // Copyright (C) 2012-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. // Package utils of the Tideland Go Libray feeds contains several helpers // for the other feed packages. package utils // EOF golib-4.24.2/feed/utils/utils.go000066400000000000000000000054021315505703200164330ustar00rootroot00000000000000// Tideland Go Libray - Feed Utils // // Copyright (C) 2012-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package utils //-------------------- // IMPORTS //-------------------- import ( "bytes" "encoding/xml" "fmt" "html" "io" "os" "strings" "unicode/utf8" ) //-------------------- // CHARSET READER //-------------------- // iso88591CharsetReader converts ISO-8859-1 into UTF-8. type iso88591CharsetReader struct { reader io.ByteReader buffer *bytes.Buffer } // newISO88591CharsetReader creates a new charset reader. func newISO88591CharsetReader(reader io.Reader) *iso88591CharsetReader { buffer := bytes.NewBuffer(make([]byte, 0, utf8.UTFMax)) return &iso88591CharsetReader{reader.(io.ByteReader), buffer} } // ReadByte reads one byte from the reader. func (cr *iso88591CharsetReader) ReadByte() (b byte, err error) { if cr.buffer.Len() <= 0 { r, err := cr.reader.ReadByte() if err != nil { return 0, err } if r < utf8.RuneSelf { return r, nil } cr.buffer.WriteRune(rune(r)) } return cr.buffer.ReadByte() } // Read reads a number of byte from the reader. It's invalid in // this context. func (cr *iso88591CharsetReader) Read(p []byte) (int, error) { return 0, os.ErrInvalid } var mapping = map[string]string{ "": "utf-8", "utf-8": "utf-8", "iso-8859-1": "iso-8859-1", "iso_8859-1:1987": "iso-8859-1", "iso-ir-100": "iso-8859-1", "iso_8859-1": "iso-8859-1", "latin1": "iso-8859-1", "l1": "iso-8859-1", "ibm819": "iso-8859-1", "cp819": "iso-8859-1", "csisolatin1": "iso-8859-1", } // CharsetReader implements the charset reader function for the XML decoder. // Currently UTF-8 and ISO-8859-1 are supported. func CharsetReader(charset string, input io.Reader) (io.Reader, error) { switch mapping[strings.ToLower(charset)] { case "utf-8": return input, nil case "iso-8859-1": return newISO88591CharsetReader(input), nil } return nil, fmt.Errorf("charset %q is not supported", charset) } // StripTags removes the tags of the raw XML string. func StripTags(raw string, strict, escaped bool) (string, error) { // Remove escaping. xmldoc := raw if escaped { xmldoc = html.UnescapeString(xmldoc) } // Decode the document. buffer := []string{} dec := xml.NewDecoder(bytes.NewBufferString(xmldoc)) dec.Strict = strict dec.CharsetReader = CharsetReader for { token, err := dec.Token() if err == io.EOF { break } if err != nil { return "", err } switch t := token.(type) { case xml.CharData: trimmed := strings.TrimSpace(string(t)) buffer = append(buffer, trimmed) default: // NOP. } } return strings.Join(buffer, " "), nil } // EOF golib-4.24.2/feed/utils/utils_test.go000066400000000000000000000034771315505703200175040ustar00rootroot00000000000000// Tideland Go Libray - Feed Utils // // Copyright (C) 2012-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package utils_test //-------------------- // IMPORTS //-------------------- import ( "testing" "github.com/tideland/golib/audit" "github.com/tideland/golib/feed/utils" ) //-------------------- // TESTS //-------------------- func TestStripTags(t *testing.T) { assert := audit.NewTestingAssertion(t, true) in := "

The quick brown fox jumps over the lazy dog.

" out, err := utils.StripTags(in, true, false) assert.Nil(err, "No error during stripping.") assert.Equal(out, "The quick brown fox jumps over the lazy dog .", "Tags have been removed.") in = "

The quick brown fox jumps over the lazy dog.

" out, err = utils.StripTags(in, true, false) assert.ErrorMatch(err, `XML syntax error on line 1.*`, "Error in document detected.") in = "

The quick brown fox jumps over the lazy dog.

" out, err = utils.StripTags(in, false, false) assert.Nil(err, "No error during stripping.") assert.Equal(out, "The quick brown fox jumps over the lazy dog.", "Tags have been removed.") in = "

The quick brown fox & goose jump over the lazy <em>dog</em>.

" out, err = utils.StripTags(in, true, false) assert.Nil(err, "No error during stripping.") assert.Equal(out, "The quick brown fox & goose jump over the lazy dog.", "Tags have been removed.") in = "

The quick brown fox &amp; goose jump over the lazy <em>dog</em>.

" out, err = utils.StripTags(in, true, true) assert.Nil(err, "No error during stripping.") assert.Equal(out, "The quick brown fox & goose jump over the lazy dog .", "Tags have been removed.") } // EOF golib-4.24.2/gjp/000077500000000000000000000000001315505703200134605ustar00rootroot00000000000000golib-4.24.2/gjp/diff.go000066400000000000000000000055561315505703200147320ustar00rootroot00000000000000// Tideland Go Library - Generic JSON Processor - Difference // // Copyright (C) 2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package gjp //-------------------- // IMPORTS //-------------------- import "github.com/tideland/golib/errors" //-------------------- // DIFFERENCE //-------------------- // Diff manages the two parsed documents and their differences. type Diff interface { // FirstDocument returns the first document passed to Diff(). FirstDocument() Document // SecondDocument returns the second document passed to Diff(). SecondDocument() Document // Differences returns a list of paths where the documents // have different content. Differences() []string // DifferenceAt returns the differences at the given path by // returning the first and the second value. DifferenceAt(path string) (Value, Value) } // diff implements Diff. type diff struct { first Document second Document paths []string } // Compare parses and compares the documents and returns their differences. func Compare(first, second []byte, separator string) (Diff, error) { fd, err := Parse(first, separator) if err != nil { return nil, err } sd, err := Parse(second, separator) if err != nil { return nil, err } d := &diff{ first: fd, second: sd, } err = d.compare() if err != nil { return nil, err } return d, nil } // CompareDocuments compares the documents and returns their differences. func CompareDocuments(first, second Document, separator string) (Diff, error) { fd, ok := first.(*document) if !ok { return nil, errors.New(ErrInvalidDocument, errorMessages, "first") } fd.separator = separator sd, ok := second.(*document) if !ok { return nil, errors.New(ErrInvalidDocument, errorMessages, "second") } sd.separator = separator d := &diff{ first: fd, second: sd, } err := d.compare() if err != nil { return nil, err } return d, nil } func (d *diff) FirstDocument() Document { return d.first } func (d *diff) SecondDocument() Document { return d.second } func (d *diff) Differences() []string { return d.paths } func (d *diff) DifferenceAt(path string) (Value, Value) { firstValue := d.first.ValueAt(path) secondValue := d.second.ValueAt(path) return firstValue, secondValue } func (d *diff) compare() error { firstPaths := map[string]struct{}{} firstProcessor := func(path string, value Value) error { firstPaths[path] = struct{}{} if !value.Equals(d.second.ValueAt(path)) { d.paths = append(d.paths, path) } return nil } err := d.first.Process(firstProcessor) if err != nil { return err } secondProcessor := func(path string, value Value) error { _, ok := firstPaths[path] if ok { // Been there, done that. return nil } d.paths = append(d.paths, path) return nil } return d.second.Process(secondProcessor) } // EOF golib-4.24.2/gjp/doc.go000066400000000000000000000026151315505703200145600ustar00rootroot00000000000000// Tideland Go Library - Generic JSON Processor // // Copyright (C) 2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. // Package gjp of the Tideland Go Library package provides the // generic parsing and processing of JSON documents by paths. The // returned values are typed, also a default has to be provided. // The path separator for accessing can be defined when parsing // a document. // // doc, err := gjp.Parse(myDoc, "/") // if err != nil { // ... // } // name := doc.ValueAt("name").AsString("") // street := doc.ValueAt("address/street").AsString("unknown") // // The value passed to AsString() and the others are default values if // there's none at the path. Another way is to create an empty document // with // // doc := gjp.NewDocument("::") // // Here and at parsed documents values can be set with // // err := doc.SetValueAt("a/b/3/c", 4711) // // Additionally values of the document can be processed using // // err := doc.Process(func(path string, value gjp.Value) error { // ... // }) // // Sometimes one is more interested in the differences between two // documents. Here // // diff, err := gjp.Compare(firstDoc, secondDoc, "/") // // privides a gjp.Diff instance which helps to compare individual // paths of the two document. package gjp // EOF golib-4.24.2/gjp/errors.go000066400000000000000000000017711315505703200153310ustar00rootroot00000000000000// Tideland Go Library - Generic JSON Processor - Errors // // Copyright (C) 2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package gjp //-------------------- // IMPORTS //-------------------- import ( "github.com/tideland/golib/errors" ) //-------------------- // CONSTANTS //-------------------- // Error codes of the etc package. const ( ErrUnmarshalling = iota + 1 ErrInvalidDocument ErrCorruptingDocument ErrInvalidPart ErrInvalidPath ErrPathTooLong ErrProcessing ) var errorMessages = errors.Messages{ ErrUnmarshalling: "cannot unmarshal document", ErrInvalidDocument: "invalid %s document, no internal implementation", ErrCorruptingDocument: "setting value would corrupt document", ErrInvalidPart: "invalid part '%s' of the path", ErrInvalidPath: "invalid path '%s'", ErrPathTooLong: "path is too long", ErrProcessing: "cannot process path '%s'", } // EOF golib-4.24.2/gjp/gjp.go000066400000000000000000000067221315505703200145760ustar00rootroot00000000000000// Tideland Go Library - Generic JSON Processor // // Copyright (C) 2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package gjp //-------------------- // IMPORTS //-------------------- import ( "encoding/json" "github.com/tideland/golib/errors" "github.com/tideland/golib/stringex" ) //-------------------- // DOCUMENT //-------------------- // PathValue is the combination of path and value. type PathValue struct { Path string Value Value } // PathValues contains a number of path/value combinations. type PathValues []PathValue // ValueProcessor describes a function for the processing of // values while iterating over a document. type ValueProcessor func(path string, value Value) error // Document represents one JSON document. type Document interface { json.Marshaler // Length returns the number of elements for the given path. Length(path string) int // SetValueAt sets the value at the given path. SetValueAt(path string, value interface{}) error // ValueAt returns the addressed value. ValueAt(path string) Value // Clear removes the so far build document data. Clear() // Query allows to find pathes matching a given pattern. Query(pattern string) (PathValues, error) // Process iterates over a document and processes its values. // There's no order, so nesting into an embedded document or // list may come earlier than higher level paths. Process(processor ValueProcessor) error } // document implements Document. type document struct { separator string root interface{} } // Parse reads a raw document and returns it as // accessible document. func Parse(data []byte, separator string) (Document, error) { var root interface{} err := json.Unmarshal(data, &root) if err != nil { return nil, errors.Annotate(err, ErrUnmarshalling, errorMessages) } return &document{ separator: separator, root: root, }, nil } // NewDocument creates a new empty document. func NewDocument(separator string) Document { return &document{ separator: separator, } } // Length implements Document. func (d *document) Length(path string) int { n, err := valueAt(d.root, splitPath(path, d.separator)) if err != nil { return -1 } // Check if object or array. o, ok := isObject(n) if ok { return len(o) } a, ok := isArray(n) if ok { return len(a) } return 1 } // SetValueAt implements Document. func (d *document) SetValueAt(path string, value interface{}) error { parts := splitPath(path, d.separator) root, err := setValueAt(d.root, value, parts) if err != nil { return err } d.root = root return nil } // ValueAt implements Document. func (d *document) ValueAt(path string) Value { n, err := valueAt(d.root, splitPath(path, d.separator)) return &value{n, err} } // Clear implements Document. func (d *document) Clear() { d.root = nil } // Query implements Document. func (d *document) Query(pattern string) (PathValues, error) { pvs := PathValues{} err := d.Process(func(path string, value Value) error { if stringex.Matches(pattern, path, false) { pvs = append(pvs, PathValue{ Path: path, Value: value, }) } return nil }) return pvs, err } // Process implements Document. func (d *document) Process(processor ValueProcessor) error { return process(d.root, []string{}, d.separator, processor) } // MarshalJSON implements json.Marshaler. func (d *document) MarshalJSON() ([]byte, error) { return json.Marshal(d.root) } // EOF golib-4.24.2/gjp/gjp_test.go000066400000000000000000000272361315505703200156400ustar00rootroot00000000000000// Tideland Go Library - Generic JSON Processor - Unit Tests // // Copyright (C) 2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package gjp_test //-------------------- // IMPORTS //-------------------- import ( "encoding/json" "errors" "testing" "time" "github.com/tideland/golib/audit" "github.com/tideland/golib/gjp" ) //-------------------- // TESTS //-------------------- // TestParseError tests the returned error in case of // an invalid document. func TestParseError(t *testing.T) { assert := audit.NewTestingAssertion(t, true) bs := []byte(`abc{def`) doc, err := gjp.Parse(bs, "/") assert.Nil(doc) assert.ErrorMatch(err, `.*cannot unmarshal document.*`) } // TestClear tests to clear a document. func TestClear(t *testing.T) { assert := audit.NewTestingAssertion(t, true) bs, _ := createDocument(assert) doc, err := gjp.Parse(bs, "/") assert.Nil(err) doc.Clear() doc.SetValueAt("/", "foo") foo := doc.ValueAt("/").AsString("") assert.Equal(foo, "foo") } // TestLength tests retrieving values as strings. func TestLength(t *testing.T) { assert := audit.NewTestingAssertion(t, true) bs, _ := createDocument(assert) doc, err := gjp.Parse(bs, "/") assert.Nil(err) l := doc.Length("X") assert.Equal(l, -1) l = doc.Length("") assert.Equal(l, 4) l = doc.Length("B") assert.Equal(l, 3) l = doc.Length("B/2") assert.Equal(l, 5) l = doc.Length("/B/2/D") assert.Equal(l, 2) l = doc.Length("/B/1/S") assert.Equal(l, 3) l = doc.Length("/B/1/S/0") assert.Equal(l, 1) } // TestProcessing tests the processing of adocument. func TestProcessing(t *testing.T) { assert := audit.NewTestingAssertion(t, true) bs, _ := createDocument(assert) count := 0 processor := func(path string, value gjp.Value) error { count++ assert.Logf("path %02d => %-10q = %q", count, path, value.AsString("")) return nil } doc, err := gjp.Parse(bs, "/") assert.Nil(err) err = doc.Process(processor) assert.Nil(err) assert.Equal(count, 27) processor = func(path string, value gjp.Value) error { return errors.New("ouch") } err = doc.Process(processor) assert.ErrorMatch(err, `.*ouch.*`) } // TestSeparator tests using different separators. func TestSeparator(t *testing.T) { assert := audit.NewTestingAssertion(t, true) bs, lo := createDocument(assert) // Slash as separator, once even starting with it. doc, err := gjp.Parse(bs, "/") assert.Nil(err) sv := doc.ValueAt("A").AsString("illegal") assert.Equal(sv, lo.A) sv = doc.ValueAt("B/0/A").AsString("illegal") assert.Equal(sv, lo.B[0].A) sv = doc.ValueAt("/B/1/D/A").AsString("illegal") assert.Equal(sv, lo.B[1].D.A) sv = doc.ValueAt("/B/2/S").AsString("illegal") assert.Equal(sv, "illegal") // Now two colons. doc, err = gjp.Parse(bs, "::") assert.Nil(err) sv = doc.ValueAt("A").AsString("illegal") assert.Equal(sv, lo.A) sv = doc.ValueAt("B::0::A").AsString("illegal") assert.Equal(sv, lo.B[0].A) sv = doc.ValueAt("B::1::D::A").AsString("illegal") assert.Equal(sv, lo.B[1].D.A) // Check if is undefined. v := doc.ValueAt("you-wont-find-me") assert.True(v.IsUndefined()) } // TestCompare tests comparing two documents. func TestCompare(t *testing.T) { assert := audit.NewTestingAssertion(t, true) first, _ := createDocument(assert) second := createCompareDocument(assert) firstDoc, err := gjp.Parse(first, "/") assert.Nil(err) secondDoc, err := gjp.Parse(second, "/") assert.Nil(err) diff, err := gjp.Compare(first, first, "/") assert.Nil(err) assert.Length(diff.Differences(), 0) diff, err = gjp.Compare(first, second, "/") assert.Nil(err) assert.Length(diff.Differences(), 12) diff, err = gjp.CompareDocuments(firstDoc, secondDoc, "/") assert.Nil(err) assert.Length(diff.Differences(), 12) for _, path := range diff.Differences() { fv, sv := diff.DifferenceAt(path) fvs := fv.AsString("") svs := sv.AsString("") assert.Different(fvs, svs, path) } first, err = diff.FirstDocument().MarshalJSON() assert.Nil(err) second, err = diff.SecondDocument().MarshalJSON() assert.Nil(err) diff, err = gjp.Compare(first, second, ":") assert.Nil(err) assert.Length(diff.Differences(), 12) // Special case of empty arrays, objects, and null. first = []byte(`{}`) second = []byte(`{"a":[],"b":{},"c":null}`) sdocParsed, err := gjp.Parse(second, "/") assert.Nil(err) sdocMarshalled, err := sdocParsed.MarshalJSON() assert.Nil(err) assert.Equal(string(sdocMarshalled), string(second)) diff, err = gjp.Compare(first, second, "/") assert.Nil(err) assert.Length(diff.Differences(), 4) first = []byte(`[]`) diff, err = gjp.Compare(first, second, "/") assert.Nil(err) assert.Length(diff.Differences(), 4) first = []byte(`["A", "B", "C"]`) diff, err = gjp.Compare(first, second, "/") assert.Nil(err) assert.Length(diff.Differences(), 6) first = []byte(`"foo"`) diff, err = gjp.Compare(first, second, "/") assert.Nil(err) assert.Length(diff.Differences(), 4) } // TestString tests retrieving values as strings. func TestString(t *testing.T) { assert := audit.NewTestingAssertion(t, true) bs, _ := createDocument(assert) doc, err := gjp.Parse(bs, "/") assert.Nil(err) sv := doc.ValueAt("A").AsString("illegal") assert.Equal(sv, "Level One") sv = doc.ValueAt("B/0/B").AsString("illegal") assert.Equal(sv, "100") sv = doc.ValueAt("B/0/C").AsString("illegal") assert.Equal(sv, "true") sv = doc.ValueAt("B/0/D/B").AsString("illegal") assert.Equal(sv, "10.1") } // TestInt tests retrieving values as ints. func TestInt(t *testing.T) { assert := audit.NewTestingAssertion(t, true) bs, _ := createDocument(assert) doc, err := gjp.Parse(bs, "/") assert.Nil(err) iv := doc.ValueAt("A").AsInt(-1) assert.Equal(iv, -1) iv = doc.ValueAt("B/0/B").AsInt(-1) assert.Equal(iv, 100) iv = doc.ValueAt("B/0/C").AsInt(-1) assert.Equal(iv, 1) iv = doc.ValueAt("B/0/S/2").AsInt(-1) assert.Equal(iv, 1) iv = doc.ValueAt("B/0/D/B").AsInt(-1) assert.Equal(iv, 10) } // TestFloat64 tests retrieving values as float64. func TestFloat64(t *testing.T) { assert := audit.NewTestingAssertion(t, true) bs, _ := createDocument(assert) doc, err := gjp.Parse(bs, "/") assert.Nil(err) fv := doc.ValueAt("A").AsFloat64(-1.0) assert.Equal(fv, -1.0) fv = doc.ValueAt("B/1/B").AsFloat64(-1.0) assert.Equal(fv, 200.0) fv = doc.ValueAt("B/0/C").AsFloat64(-99) assert.Equal(fv, 1.0) fv = doc.ValueAt("B/0/S/3").AsFloat64(-1.0) assert.Equal(fv, 2.2) fv = doc.ValueAt("B/1/D/B").AsFloat64(-1.0) assert.Equal(fv, 20.2) } // TestBool tests retrieving values as bool. func TestBool(t *testing.T) { assert := audit.NewTestingAssertion(t, true) bs, _ := createDocument(assert) doc, err := gjp.Parse(bs, "/") assert.Nil(err) bv := doc.ValueAt("A").AsBool(false) assert.Equal(bv, false) bv = doc.ValueAt("B/0/C").AsBool(false) assert.Equal(bv, true) bv = doc.ValueAt("B/0/S/0").AsBool(false) assert.Equal(bv, false) bv = doc.ValueAt("B/0/S/2").AsBool(false) assert.Equal(bv, true) bv = doc.ValueAt("B/0/S/4").AsBool(false) assert.Equal(bv, true) } // TestQuery tests querying a document. func TestQuery(t *testing.T) { assert := audit.NewTestingAssertion(t, true) bs, _ := createDocument(assert) doc, err := gjp.Parse(bs, "/") assert.Nil(err) pvs, err := doc.Query("Z/*") assert.Nil(err) assert.Length(pvs, 0) pvs, err = doc.Query("*") assert.Nil(err) assert.Length(pvs, 27) pvs, err = doc.Query("/A") assert.Nil(err) assert.Length(pvs, 1) pvs, err = doc.Query("/B/*") assert.Nil(err) assert.Length(pvs, 24) pvs, err = doc.Query("/B/[01]/*") assert.Nil(err) assert.Length(pvs, 18) pvs, err = doc.Query("/B/[01]/*A") assert.Nil(err) assert.Length(pvs, 4) pvs, err = doc.Query("*/S/*") assert.Nil(err) assert.Length(pvs, 8) pvs, err = doc.Query("*/S/3") assert.Nil(err) assert.Length(pvs, 1) pvs, err = doc.Query("/A") assert.Nil(err) assert.Equal(pvs[0].Path, "/A") assert.Equal(pvs[0].Value.AsString(""), "Level One") } // TestBuilding tests the creation of documents. func TestBuilding(t *testing.T) { assert := audit.NewTestingAssertion(t, true) // Most simple document. doc := gjp.NewDocument("/") err := doc.SetValueAt("", "foo") assert.Nil(err) sv := doc.ValueAt("").AsString("bar") assert.Equal(sv, "foo") // Positive cases. doc = gjp.NewDocument("/") err = doc.SetValueAt("/a/b/x", 1) assert.Nil(err) err = doc.SetValueAt("/a/b/y", true) assert.Nil(err) err = doc.SetValueAt("/a/c", "quick brown fox") assert.Nil(err) err = doc.SetValueAt("/a/d/0/z", 47.11) assert.Nil(err) err = doc.SetValueAt("/a/d/1/z", nil) assert.Nil(err) iv := doc.ValueAt("a/b/x").AsInt(0) assert.Equal(iv, 1) bv := doc.ValueAt("a/b/y").AsBool(false) assert.Equal(bv, true) sv = doc.ValueAt("a/c").AsString("") assert.Equal(sv, "quick brown fox") fv := doc.ValueAt("a/d/0/z").AsFloat64(8.15) assert.Equal(fv, 47.11) nv := doc.ValueAt("a/d/1/z").IsUndefined() assert.True(nv) pvs, err := doc.Query("*x") assert.Nil(err) assert.Length(pvs, 1) // Now provoke errors. err = doc.SetValueAt("a", "stupid") assert.ErrorMatch(err, ".*corrupt.*") err = doc.SetValueAt("a/b/x/y", "stupid") assert.ErrorMatch(err, ".*corrupt.*") // Legally change values. err = doc.SetValueAt("/a/b/x", 2) assert.Nil(err) iv = doc.ValueAt("a/b/x").AsInt(0) assert.Equal(iv, 2) } // TestMarshalJSON tests building a JSON document again. func TestMarshalJSON(t *testing.T) { assert := audit.NewTestingAssertion(t, true) // Compare input and output. bsIn, _ := createDocument(assert) parsedDoc, err := gjp.Parse(bsIn, "/") assert.Nil(err) bsOut, err := parsedDoc.MarshalJSON() assert.Nil(err) assert.Equal(bsOut, bsIn) // Now create a built one. builtDoc := gjp.NewDocument("/") err = builtDoc.SetValueAt("/a/2/x", 1) assert.Nil(err) err = builtDoc.SetValueAt("/a/4/y", true) assert.Nil(err) bsIn = []byte(`{"a":[null,null,{"x":1},null,{"y":true}]}`) bsOut, err = builtDoc.MarshalJSON() assert.Nil(err) assert.Equal(bsOut, bsIn) } //-------------------- // HELPERS //-------------------- type levelThree struct { A string B float64 } type levelTwo struct { A string B int C bool D *levelThree S []string } type levelOne struct { A string B []*levelTwo D time.Duration T time.Time } func createDocument(assert audit.Assertion) ([]byte, *levelOne) { lo := &levelOne{ A: "Level One", B: []*levelTwo{ &levelTwo{ A: "Level Two - 0", B: 100, C: true, D: &levelThree{ A: "Level Three - 0", B: 10.1, }, S: []string{ "red", "green", "1", "2.2", "true", }, }, &levelTwo{ A: "Level Two - 1", B: 200, C: false, D: &levelThree{ A: "Level Three - 1", B: 20.2, }, S: []string{ "orange", "blue", "white", }, }, &levelTwo{ A: "Level Two - 2", B: 300, C: true, D: &levelThree{ A: "Level Three - 2", B: 30.3, }, }, }, D: 5 * time.Second, T: time.Date(2017, time.April, 29, 20, 30, 0, 0, time.UTC), } bs, err := json.Marshal(lo) assert.Nil(err) return bs, lo } func createCompareDocument(assert audit.Assertion) []byte { lo := &levelOne{ A: "Level One", B: []*levelTwo{ &levelTwo{ A: "Level Two - 0", B: 100, C: true, D: &levelThree{ A: "Level Three - 0", B: 10.1, }, S: []string{ "red", "green", "0", "2.2", "false", }, }, &levelTwo{ A: "Level Two - 1", B: 300, C: false, D: &levelThree{ A: "Level Three - 1", B: 99.9, }, S: []string{ "orange", "blue", "white", "red", }, }, }, D: 10 * time.Second, T: time.Date(2017, time.April, 29, 20, 59, 0, 0, time.UTC), } bs, err := json.Marshal(lo) assert.Nil(err) return bs } // EOF golib-4.24.2/gjp/processing.go000066400000000000000000000144751315505703200161760ustar00rootroot00000000000000// Tideland Go Library - Generic JSON Processor - Processing // // Copyright (C) 2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package gjp //-------------------- // IMPORTS //-------------------- import ( "strconv" "strings" "github.com/tideland/golib/errors" "github.com/tideland/golib/stringex" ) //-------------------- // PROCESSING FUNCTIONS //-------------------- // splitPath splits and cleans the path into parts. func splitPath(path, separator string) []string { return stringex.SplitFilter(path, separator, func(part string) bool { return part != "" }) } // isValue checks if the raw is a value and returns it // type-safe. Otherwise nil and false are returned. func isValue(raw interface{}) (interface{}, bool) { if raw == nil { return raw, true } _, ook := isObject(raw) _, aok := isArray(raw) if ook || aok { return nil, false } return raw, true } // isObject checks if the raw is an object and returns it // type-safe. Otherwise nil and false are returned. func isObject(raw interface{}) (map[string]interface{}, bool) { o, ok := raw.(map[string]interface{}) return o, ok } // isArray checks if the raw is an array and returns it // type-safe. Otherwise nil and false are returned. func isArray(raw interface{}) ([]interface{}, bool) { a, ok := raw.([]interface{}) return a, ok } // valueAt returns the value at the path parts. func valueAt(node interface{}, parts []string) (interface{}, error) { length := len(parts) if length == 0 { // End of the parts. return node, nil } // Further access depends on part content node and type. head, tail := ht(parts) if head == "" { return node, nil } if o, ok := isObject(node); ok { // JSON object. field, ok := o[head] if !ok { return nil, errors.New(ErrInvalidPart, errorMessages, head) } return valueAt(field, tail) } if a, ok := isArray(node); ok { // JSON array. index, err := strconv.Atoi(head) if err != nil || index >= len(a) { return nil, errors.Annotate(err, ErrInvalidPart, errorMessages, head) } return valueAt(a[index], tail) } // Parts left but field value. return nil, errors.New(ErrPathTooLong, errorMessages) } // setValueAt sets the value at the path parts. func setValueAt(root, value interface{}, parts []string) (interface{}, error) { h, t := ht(parts) return setNodeValueAt(root, value, h, t) } // ht retrieves head and tail from parts. func ht(parts []string) (string, []string) { switch len(parts) { case 0: return "", []string{} case 1: return parts[0], []string{} default: return parts[0], parts[1:] } } // setNodeValueAt is used recursively by setValueAt(). func setNodeValueAt(node, value interface{}, head string, tail []string) (interface{}, error) { // Check for nil node first. if node == nil { return addNodeValueAt(value, head, tail) } // Otherwise it should be an object or an array. if o, ok := isObject(node); ok { // JSON object. _, ok := isValue(o[head]) switch { case !ok && len(tail) == 0: return nil, errors.New(ErrCorruptingDocument, errorMessages) case ok && o[head] != nil && len(tail) > 0: return nil, errors.New(ErrCorruptingDocument, errorMessages) case ok && len(tail) == 0: o[head] = value default: h, t := ht(tail) subnode, err := setNodeValueAt(o[head], value, h, t) if err != nil { return nil, err } o[head] = subnode } return o, nil } if a, ok := isArray(node); ok { // JSON array. index, err := strconv.Atoi(head) if err != nil { return nil, errors.New(ErrInvalidPart, errorMessages, head) } a = ensureArray(a, index+1) _, ok := isValue(a[index]) switch { case !ok && len(tail) == 0: return nil, errors.New(ErrCorruptingDocument, errorMessages) case ok && a[index] != nil && len(tail) > 0: return nil, errors.New(ErrCorruptingDocument, errorMessages) case ok && len(tail) == 0: a[index] = value default: h, t := ht(tail) subnode, err := setNodeValueAt(a[index], value, h, t) if err != nil { return nil, err } a[index] = subnode } return a, nil } return nil, errors.New(ErrInvalidPath, errorMessages, head) } // addNodeValueAt is used recursively by setValueAt(). func addNodeValueAt(value interface{}, head string, tail []string) (interface{}, error) { // JSON value. if head == "" { return value, nil } index, err := strconv.Atoi(head) if err != nil { // JSON object. o := map[string]interface{}{} if len(tail) == 0 { o[head] = value return o, nil } h, t := ht(tail) subnode, err := addNodeValueAt(value, h, t) if err != nil { return nil, err } o[head] = subnode return o, nil } // JSON array. a := ensureArray([]interface{}{}, index+1) if len(tail) == 0 { a[index] = value return a, nil } h, t := ht(tail) subnode, err := addNodeValueAt(value, h, t) if err != nil { return nil, err } a[index] = subnode return a, nil } // ensureArray ensures the right len of an array. func ensureArray(a []interface{}, l int) []interface{} { if len(a) >= l { return a } b := make([]interface{}, l) copy(b, a) return b } // process processes node recursively. func process(node interface{}, parts []string, separator string, processor ValueProcessor) error { mkerr := func(err error, ps []string) error { return errors.Annotate(err, ErrProcessing, errorMessages, pathify(ps, separator)) } // First check objects and arrays. if o, ok := isObject(node); ok { if len(o) == 0 { // Empty object. return processor(pathify(parts, separator), &value{o, nil}) } for field, subnode := range o { fieldparts := append(parts, field) if err := process(subnode, fieldparts, separator, processor); err != nil { return mkerr(err, parts) } } return nil } if a, ok := isArray(node); ok { if len(a) == 0 { // Empty array. return processor(pathify(parts, separator), &value{a, nil}) } for index, subnode := range a { indexparts := append(parts, strconv.Itoa(index)) if err := process(subnode, indexparts, separator, processor); err != nil { return mkerr(err, parts) } } return nil } // Reached a value at the end. return processor(pathify(parts, separator), &value{node, nil}) } // pathify creates a path out of parts and separator. func pathify(parts []string, separator string) string { return separator + strings.Join(parts, separator) } // EOF golib-4.24.2/gjp/value.go000066400000000000000000000054261315505703200151320ustar00rootroot00000000000000// Tideland Go Library - Generic JSON Processing - Value // // Copyright (C) 2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package gjp //-------------------- // IMPORTS //-------------------- import ( "fmt" "reflect" "strconv" ) //-------------------- // VALUE //-------------------- // Value contains one JSON value. type Value interface { fmt.Stringer // IsUndefined returns true if this value is undefined. IsUndefined() bool // AsString returns the value as string. AsString(dv string) string // AsInt returns the value as int. AsInt(dv int) int // AsFloat64 returns the value as float64. AsFloat64(dv float64) float64 // AsBool returns the value as bool. AsBool(dv bool) bool // Equals compares a value with the passed one. Equals(to Value) bool } // value implements Value. type value struct { raw interface{} err error } // IsUndefined implements Value. func (v *value) IsUndefined() bool { return v.raw == nil } // AsString implements Value. func (v *value) AsString(dv string) string { if v.IsUndefined() { return dv } switch tv := v.raw.(type) { case string: return tv case int: return strconv.Itoa(tv) case float64: return strconv.FormatFloat(tv, 'f', -1, 64) case bool: return strconv.FormatBool(tv) } return dv } // AsInt implements Value. func (v *value) AsInt(dv int) int { if v.IsUndefined() { return dv } switch tv := v.raw.(type) { case string: i, err := strconv.Atoi(tv) if err != nil { return dv } return i case int: return tv case float64: return int(tv) case bool: if tv { return 1 } return 0 } return dv } // AsFloat64 implements Value. func (v *value) AsFloat64(dv float64) float64 { if v.IsUndefined() { return dv } switch tv := v.raw.(type) { case string: f, err := strconv.ParseFloat(tv, 64) if err != nil { return dv } return f case int: return float64(tv) case float64: return tv case bool: if tv { return 1.0 } return 0.0 } return dv } // AsBool implements Value. func (v *value) AsBool(dv bool) bool { if v.IsUndefined() { return dv } switch tv := v.raw.(type) { case string: b, err := strconv.ParseBool(tv) if err != nil { return dv } return b case int: return tv == 1 case float64: return tv == 1.0 case bool: return tv } return dv } // Equals implements Value. func (v *value) Equals(to Value) bool { vto, ok := to.(*value) if !ok { return false } if vv, ok := isValue(v.raw); ok { if vtov, ok := isValue(vto.raw); ok { return vv == vtov } } return reflect.DeepEqual(v.raw, vto.raw) } // String implements fmt.Stringer. func (v *value) String() string { if v.IsUndefined() { return "null" } return fmt.Sprintf("%v", v.raw) } // EOF golib-4.24.2/identifier/000077500000000000000000000000001315505703200150225ustar00rootroot00000000000000golib-4.24.2/identifier/doc.go000066400000000000000000000012611315505703200161160ustar00rootroot00000000000000// Tideland Go Library - Identifier // // Copyright (C) 2009-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. // Package identifier of the Tideland Go Library provides different ways // to produce identifiers out of diffent input data as well as UUIDs. // // The UUID generation can be done according the versions 1, 3, 4, and 5. // Other identifier types are based on passed data or types. Here // the individual parts are harmonized and concatenated by the // passed seperators. It is the users responsibility to check if // the identifier is unique in its context. package identifier // EOF golib-4.24.2/identifier/errors.go000066400000000000000000000022041315505703200166630ustar00rootroot00000000000000// Tideland Go Library - Identifier - Errors // // Copyright (C) 2009-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package identifier //-------------------- // IMPORTS //-------------------- import ( "github.com/tideland/golib/errors" ) //-------------------- // CONSTANTS //-------------------- // Error codes of the identifier package. const ( ErrInvalidHexLength = iota + 1 ErrInvalidHexValue ) var errorMessages = errors.Messages{ ErrInvalidHexLength: "invalid length of hex string, has to be 32", ErrInvalidHexValue: "invalid value of hex string", } //-------------------- // TESTING //-------------------- // IsInvalidHexLengthError returns true, if the error signals that // the passed hex string for a UUID hasn't the correct size of 32. func IsInvalidHexLengthError(err error) bool { return errors.IsError(err, ErrInvalidHexLength) } // IsInvalidHexValueError returns true, if the error signals an // invalid hex string as input. func IsInvalidHexValueError(err error) bool { return errors.IsError(err, ErrInvalidHexValue) } // EOF golib-4.24.2/identifier/identifier.go000066400000000000000000000051231315505703200174740ustar00rootroot00000000000000// Tideland Go Library - Identifier // // Copyright (C) 2009-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package identifier //-------------------- // IMPORTS //-------------------- import ( "bytes" "fmt" "reflect" "strings" "unicode" ) //-------------------- // IDENTIFIER GENERATORS //-------------------- // LimitedSepIdentifier builds an identifier out of multiple parts, // all as lowercase strings and concatenated with the separator // Non letters and digits are exchanged with dashes and // reduced to a maximum of one each. If limit is true only // 'a' to 'z' and '0' to '9' are allowed. func LimitedSepIdentifier(sep string, limit bool, parts ...interface{}) string { iparts := make([]string, 0) for _, p := range parts { tmp := strings.Map(func(r rune) rune { lcr := unicode.ToLower(r) if !limit { return lcr } // Check letter and digit. if unicode.IsLetter(lcr) || unicode.IsDigit(lcr) { // Only 'a' to 'z' and '0' to '9'. if lcr <= unicode.MaxASCII { return lcr } } // Replace with space. return ' ' }, fmt.Sprintf("%v", p)) // Only use non-empty identifier parts. if ipart := strings.Join(strings.Fields(tmp), "-"); len(ipart) > 0 { iparts = append(iparts, ipart) } } return strings.Join(iparts, sep) } // SepIdentifier builds an identifier out of multiple parts, all // as lowercase strings and concatenated with the separator // Non letters and digits are exchanged with dashes and // reduced to a maximum of one each. func SepIdentifier(sep string, parts ...interface{}) string { return LimitedSepIdentifier(sep, true, parts...) } // Identifier works like SepIdentifier but the separator // is set to be a colon. func Identifier(parts ...interface{}) string { return SepIdentifier(":", parts...) } // JoinedIdentifier builds a new identifier, joinded with the // colon as the separator. func JoinedIdentifier(identifiers ...string) string { return strings.Join(identifiers, ":") } // TypeAsIdentifierPart transforms the name of the arguments type into // a part for identifiers. It's splitted at each uppercase char, // concatenated with dashes and transferred to lowercase. func TypeAsIdentifierPart(i interface{}) string { var buf bytes.Buffer fullTypeName := reflect.TypeOf(i).String() lastDot := strings.LastIndex(fullTypeName, ".") typeName := fullTypeName[lastDot+1:] for i, r := range typeName { if unicode.IsUpper(r) { if i > 0 { buf.WriteRune('-') } } buf.WriteRune(r) } return strings.ToLower(buf.String()) } // EOF golib-4.24.2/identifier/identifier_test.go000066400000000000000000000041371315505703200205370ustar00rootroot00000000000000// Tideland Go Library - Identifier - Unit Tests // // Copyright (C) 2009-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package identifier_test //-------------------- // IMPORTS //-------------------- import ( "testing" "github.com/tideland/golib/audit" "github.com/tideland/golib/identifier" ) //-------------------- // TESTS //-------------------- // Test the creation of identifiers based on types. func TestTypeAsIdentifierPart(t *testing.T) { assert := audit.NewTestingAssertion(t, true) // Type as identifier. var tai TypeToSplitForIdentifier id := identifier.TypeAsIdentifierPart(tai) assert.Equal(id, "type-to-split-for-identifier", "wrong TypeAsIdentifierPart() result") id = identifier.TypeAsIdentifierPart(identifier.NewUUID()) assert.Equal(id, "u-u-i-d", "wrong TypeAsIdentifierPart() result") } // Test the creation of identifiers based on parts. func TestIdentifier(t *testing.T) { assert := audit.NewTestingAssertion(t, true) // Identifier. id := identifier.Identifier("One", 2, "three four") assert.Equal(id, "one:2:three-four", "wrong Identifier() result") id = identifier.Identifier(2011, 6, 22, "One, two, or three things.") assert.Equal(id, "2011:6:22:one-two-or-three-things", "wrong Identifier() result") } // Test the creation of identifiers based on parts with defined seperators. func TestSepIdentifier(t *testing.T) { assert := audit.NewTestingAssertion(t, true) id := identifier.SepIdentifier("+", 1, "oNe", 2, "TWO", "3", "ÄÖÜ") assert.Equal(id, "1+one+2+two+3", "wrong SepIdentifier() result") id = identifier.LimitedSepIdentifier("+", false, 1, "oNe", 2, "TWO", "3", "ÄÖÜ") assert.Equal(id, "1+one+2+two+3+äöü", "wrong SepIdentifier() result") id = identifier.LimitedSepIdentifier("+", true, " ", 1, "oNe", 2, "TWO", "3", "ÄÖÜ", "Four", "+#-:,") assert.Equal(id, "1+one+2+two+3+four", "wrong LimitedSepIdentifier() result") } //-------------------- // HELPER //-------------------- // Type as part of an identifier. type TypeToSplitForIdentifier bool // EOF golib-4.24.2/identifier/uuid.go000066400000000000000000000134061315505703200163230ustar00rootroot00000000000000// Tideland Go Library - Identifier - UUID // // Copyright (C) 2009-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package identifier //-------------------- // IMPORTS //-------------------- import ( "crypto/md5" "crypto/rand" "crypto/sha1" "encoding/binary" "encoding/hex" "fmt" "net" "time" "github.com/tideland/golib/errors" ) //-------------------- // UUID //-------------------- // UUID versions and variants. const ( UUIDv1 byte = 1 UUIDv3 byte = 3 UUIDv4 byte = 4 UUIDv5 byte = 5 UUIDVariantNCS byte = 0 UUIDVariantRFC4122 byte = 4 UUIDVariantMicrosoft byte = 6 UUIDVariantFuture byte = 7 ) // UUID represents a universal identifier with 16 bytes. // See http://en.wikipedia.org/wiki/Universally_unique_identifier. type UUID [16]byte // NewUUID returns a new UUID with based on the default version 4. func NewUUID() UUID { uuid, err := NewUUIDv4() if err != nil { // Panic due to compatibility reasons. panic(err) } return uuid } // NewUUIDv1 generates a new UUID based on version 1 (MAC address and // date-time). func NewUUIDv1() (UUID, error) { uuid := UUID{} epoch := int64(0x01b21dd213814000) now := uint64(time.Now().UnixNano()/100 + epoch) clockSeqRand := [2]byte{} rand.Read(clockSeqRand[:]) clockSeq := binary.LittleEndian.Uint16(clockSeqRand[:]) timeLow := uint32(now & (0x100000000 - 1)) timeMid := uint16((now >> 32) & 0xffff) timeHighVer := uint16((now >> 48) & 0x0fff) clockSeq &= 0x3fff binary.LittleEndian.PutUint32(uuid[0:4], timeLow) binary.LittleEndian.PutUint16(uuid[4:6], timeMid) binary.LittleEndian.PutUint16(uuid[6:8], timeHighVer) binary.LittleEndian.PutUint16(uuid[8:10], clockSeq) copy(uuid[10:16], cachedMACAddress) uuid.setVersion(UUIDv1) uuid.setVariant(UUIDVariantRFC4122) return uuid, nil } // NewUUIDv3 generates a new UUID based on version 3 (MD5 hash of a namespace // and a name). func NewUUIDv3(ns UUID, name []byte) (UUID, error) { uuid := UUID{} hash := md5.New() hash.Write(ns.dump()) hash.Write(name) copy(uuid[:], hash.Sum([]byte{})[:16]) uuid.setVersion(UUIDv3) uuid.setVariant(UUIDVariantRFC4122) return uuid, nil } // NewUUIDv4 generates a new UUID based on version 4 (strong random number). func NewUUIDv4() (UUID, error) { uuid := UUID{} _, err := rand.Read([]byte(uuid[:])) if err != nil { return uuid, err } uuid.setVersion(UUIDv4) uuid.setVariant(UUIDVariantRFC4122) return uuid, nil } // NewUUIDv5 generates a new UUID based on version 5 (SHA1 hash of a namespace // and a name). func NewUUIDv5(ns UUID, name []byte) (UUID, error) { uuid := UUID{} hash := sha1.New() hash.Write(ns.dump()) hash.Write(name) copy(uuid[:], hash.Sum([]byte{})[:16]) uuid.setVersion(UUIDv5) uuid.setVariant(UUIDVariantRFC4122) return uuid, nil } // NewUUIDByHex creates a UUID based on the passed hex string which has to // have the length of 32 bytes. func NewUUIDByHex(source string) (UUID, error) { uuid := UUID{} if len([]byte(source)) != 32 { return uuid, errors.New(ErrInvalidHexLength, errorMessages) } raw, err := hex.DecodeString(source) if err != nil { return uuid, errors.Annotate(err, ErrInvalidHexValue, errorMessages) } copy(uuid[:], raw) return uuid, nil } // Version returns the version number of the UUID algorithm. func (uuid UUID) Version() byte { return uuid[6] & 0xf0 >> 4 } // Variant returns the variant of the UUID. func (uuid UUID) Variant() byte { return uuid[8] & 0xe0 >> 5 } // Copy returns a copy of the UUID. func (uuid UUID) Copy() UUID { uuidCopy := uuid return uuidCopy } // Raw returns a copy of the UUID bytes. func (uuid UUID) Raw() [16]byte { uuidCopy := uuid.Copy() return [16]byte(uuidCopy) } // dump creates a copy as a byte slice. func (uuid UUID) dump() []byte { dump := make([]byte, len(uuid)) copy(dump, uuid[:]) return dump } // ShortString returns a hexadecimal string representation // without separators. func (uuid UUID) ShortString() string { return fmt.Sprintf("%x", uuid[0:16]) } // String returns a hexadecimal string representation with // standardized separators. func (uuid UUID) String() string { return fmt.Sprintf("%x-%x-%x-%x-%x", uuid[0:4], uuid[4:6], uuid[6:8], uuid[8:10], uuid[10:16]) } // setVersion sets the version part of the UUID. func (uuid *UUID) setVersion(v byte) { uuid[6] = (uuid[6] & 0x0f) | (v << 4) } // setVariant sets the variant part of the UUID. func (uuid *UUID) setVariant(v byte) { uuid[8] = (uuid[8] & 0x1f) | (v << 5) } // UUIDNamespaceDNS returns the DNS namespace UUID. func UUIDNamespaceDNS() UUID { uuid, _ := NewUUIDByHex("6ba7b8109dad11d180b400c04fd430c8") return uuid } // UUIDNamespaceURL returns the URL namespace UUID. func UUIDNamespaceURL() UUID { uuid, _ := NewUUIDByHex("6ba7b8119dad11d180b400c04fd430c8") return uuid } // UUIDNamespaceOID returns the OID namespace UUID. func UUIDNamespaceOID() UUID { uuid, _ := NewUUIDByHex("6ba7b8129dad11d180b400c04fd430c8") return uuid } // UUIDNamespaceX500 returns the X.500 namespace UUID. func UUIDNamespaceX500() UUID { uuid, _ := NewUUIDByHex("6ba7b8149dad11d180b400c04fd430c8") return uuid } //-------------------- // PRIVATE HELPERS //-------------------- // macAddress retrieves the MAC address of the computer. func macAddress() []byte { address := [6]byte{} ifaces, err := net.Interfaces() // Try to get address from interfaces. if err == nil { set := false for _, iface := range ifaces { if len(iface.HardwareAddr.String()) != 0 { copy(address[:], []byte(iface.HardwareAddr)) set = true break } } if set { // Had success. return address[:] } } // Need a random address. rand.Read(address[:]) address[0] |= 0x01 return address[:] } var cachedMACAddress []byte func init() { cachedMACAddress = macAddress() } // EOF golib-4.24.2/identifier/uuid_test.go000066400000000000000000000055221315505703200173620ustar00rootroot00000000000000// Tideland Go Library - Identifier - Unit Tests // // Copyright (C) 2009-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package identifier_test //-------------------- // IMPORTS //-------------------- import ( "testing" "github.com/tideland/golib/audit" "github.com/tideland/golib/identifier" ) //-------------------- // TESTS //-------------------- // Test the standard UUID. func TestStandardUUID(t *testing.T) { assert := audit.NewTestingAssertion(t, true) // Asserts. uuid := identifier.NewUUID() assert.Equal(uuid.Version(), identifier.UUIDv4) uuidShortStr := uuid.ShortString() uuidStr := uuid.String() assert.Equal(len(uuid), 16, "UUID length has to be 16") assert.Match(uuidShortStr, "[0-9a-f]{32}", "UUID short") assert.Match(uuidStr, "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}", "UUID long") // Check for unique creation, but only weak test. uuids := make(map[string]bool) for i := 0; i < 1000000; i++ { uuid = identifier.NewUUID() uuidStr = uuid.String() assert.False(uuids[uuidStr], "UUID collision must not happen") uuids[uuidStr] = true } // Check for copy. uuidA := identifier.NewUUID() uuidB := uuidA.Copy() for i := 0; i < len(uuidA); i++ { uuidA[i] = 0 } assert.Different(uuidA, uuidB) } // Test UUID versions. func TestUUIDVersions(t *testing.T) { assert := audit.NewTestingAssertion(t, true) ns := identifier.UUIDNamespaceOID() // Asserts. uuidV1, err := identifier.NewUUIDv1() assert.Nil(err) assert.Equal(uuidV1.Version(), identifier.UUIDv1) assert.Equal(uuidV1.Variant(), identifier.UUIDVariantRFC4122) assert.Logf("UUID V1: %v", uuidV1) uuidV3, err := identifier.NewUUIDv3(ns, []byte{4, 7, 1, 1}) assert.Nil(err) assert.Equal(uuidV3.Version(), identifier.UUIDv3) assert.Equal(uuidV3.Variant(), identifier.UUIDVariantRFC4122) assert.Logf("UUID V3: %v", uuidV3) uuidV4, err := identifier.NewUUIDv4() assert.Nil(err) assert.Equal(uuidV4.Version(), identifier.UUIDv4) assert.Equal(uuidV4.Variant(), identifier.UUIDVariantRFC4122) assert.Logf("UUID V4: %v", uuidV4) uuidV5, err := identifier.NewUUIDv5(ns, []byte{4, 7, 1, 1}) assert.Nil(err) assert.Equal(uuidV5.Version(), identifier.UUIDv5) assert.Equal(uuidV5.Variant(), identifier.UUIDVariantRFC4122) assert.Logf("UUID V5: %v", uuidV5) } // Test creating UUIDs from hex strings. func TestUUIDByHex(t *testing.T) { assert := audit.NewTestingAssertion(t, true) // Asserts. _, err := identifier.NewUUIDByHex("ffff") assert.ErrorMatch(err, `\[IDENTIFIER:.*\] invalid length of hex string, has to be 32`) _, err = identifier.NewUUIDByHex("012345678901234567890123456789zz") assert.ErrorMatch(err, `\[IDENTIFIER:.*\] invalid value of hex string: .*`) _, err = identifier.NewUUIDByHex("012345678901234567890123456789ab") assert.Nil(err) } // EOF golib-4.24.2/logger/000077500000000000000000000000001315505703200141575ustar00rootroot00000000000000golib-4.24.2/logger/doc.go000066400000000000000000000022361315505703200152560ustar00rootroot00000000000000// Tideland Go Library - Logger // // Copyright (C) 2012-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. // Package logger of the Tideland Go Library provides a flexible way // to log information with different levels and on different backends. // // The levels are Debug, Info, Warning, Error, Critical, and Fatal. // Here logger.Debugf() also logs information about file name, function // name, and line number while logger.Fatalf() may end the program // depending on the set FatalExiterFunc. // // Different backends may be set. The standard logger writes to an // io.Writer (initially os.Stdout), the go logger uses the Go log // package, and the sys logger uses the Go syslog package on the // according operating systems. For testing the test logger exists. // When created also a fetch function is return. It returns the // logged strings which can be used inside of tests then. // // Changes to the standard behavior can be made with logger.SetLevel(), // logger.SetLogger(), and logger.SetFatalExiter(). Own logger // backends and exiter can be defined. package logger // EOF golib-4.24.2/logger/logger.go000066400000000000000000000315471315505703200157770ustar00rootroot00000000000000// Tideland Go Library - Logger // // Copyright (C) 2012-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package logger //-------------------- // IMPORTS //-------------------- import ( "fmt" "io" "log" "os" "path" "runtime" "strings" "sync" "time" ) //-------------------- // TYPES AND TYPE FUNCTIONS //-------------------- // LogLevel describes the chosen log level between // debug and critical. type LogLevel int // Log levels to control the logging output. const ( LevelDebug LogLevel = iota LevelInfo LevelWarning LevelError LevelCritical LevelFatal ) // FatalExiterFunc defines a functions that will be called // in case of a Fatalf call. type FatalExiterFunc func() // OsFatalExiter exits the application with os.Exit and // the return code -1. func OsFatalExiter() { os.Exit(-1) } // PanicFatalExiter exits the application with a panic. func PanicFatalExiter() { panic("program aborted after fatal situation, see log") } // FilterFunc allows to filter the output of the logging. Filters // have to return true if the received entry shall be filtered and // not output. type FilterFunc func(level LogLevel, info, msg string) bool //-------------------- // LOG CONTROL //-------------------- // Log control variables. var ( logMux sync.RWMutex logLevel LogLevel = LevelInfo logBackend Logger = NewStandardLogger(os.Stdout) logFatalExiter FatalExiterFunc = OsFatalExiter logFilter FilterFunc ) // Level returns the current log level. func Level() LogLevel { logMux.Lock() defer logMux.Unlock() return logLevel } // SetLevel switches to a new log level and returns // the current one. func SetLevel(level LogLevel) LogLevel { logMux.Lock() defer logMux.Unlock() current := logLevel switch { case level <= LevelDebug: logLevel = LevelDebug case level >= LevelFatal: logLevel = LevelFatal default: logLevel = level } return current } // SetLevelString switches to a new log level passed as // string. Accepted are the values "debug", "info", "warning" // "error", "critical", and "fatal". The passed string will // be set to lower-case. The function is intended to be used // when then log level is read out of a configuration. func SetLevelString(levelstr string) LogLevel { logMux.Lock() defer logMux.Unlock() current := logLevel switch strings.ToLower(levelstr) { case "debug": logLevel = LevelDebug case "info": logLevel = LevelInfo case "warning": logLevel = LevelWarning case "error": logLevel = LevelError case "critical": logLevel = LevelCritical case "fatal": logLevel = LevelFatal } return current } // SetLogger sets a new logger. func SetLogger(l Logger) Logger { logMux.Lock() defer logMux.Unlock() old := logBackend logBackend = l return old } // SetFatalExiter sets the fatal exiter function and // returns the current one. func SetFatalExiter(fef FatalExiterFunc) FatalExiterFunc { logMux.Lock() defer logMux.Unlock() current := logFatalExiter logFatalExiter = fef return current } // SetFilter sets the global output filter and returns // the current one. func SetFilter(ff FilterFunc) FilterFunc { logMux.Lock() defer logMux.Unlock() current := logFilter logFilter = ff return current } // UnsetFilter removes the global output folter and // returns the current one. func UnsetFilter() FilterFunc { logMux.Lock() defer logMux.Unlock() current := logFilter logFilter = nil return current } //-------------------- // LOGGING //-------------------- // Debugf logs a message at debug level. func Debugf(format string, args ...interface{}) { logMux.RLock() defer logMux.RUnlock() if logLevel <= LevelDebug { info := retrieveCallInfo().verboseFormat() msg := fmt.Sprintf(format, args...) if shallLog(LevelDebug, info, msg) { logBackend.Debug(info, msg) } } } // Infof logs a message at info level. func Infof(format string, args ...interface{}) { logMux.RLock() defer logMux.RUnlock() if logLevel <= LevelInfo { info := retrieveCallInfo().shortFormat() msg := fmt.Sprintf(format, args...) if shallLog(LevelInfo, info, msg) { logBackend.Info(info, msg) } } } // Warningf logs a message at warning level. func Warningf(format string, args ...interface{}) { logMux.RLock() defer logMux.RUnlock() if logLevel <= LevelWarning { info := retrieveCallInfo().shortFormat() msg := fmt.Sprintf(format, args...) if shallLog(LevelWarning, info, msg) { logBackend.Warning(info, msg) } } } // Errorf logs a message at error level. func Errorf(format string, args ...interface{}) { logMux.RLock() defer logMux.RUnlock() if logLevel <= LevelError { info := retrieveCallInfo().shortFormat() msg := fmt.Sprintf(format, args...) if shallLog(LevelError, info, msg) { logBackend.Error(info, msg) } } } // Criticalf logs a message at critical level. func Criticalf(format string, args ...interface{}) { logMux.RLock() defer logMux.RUnlock() if logLevel <= LevelCritical { info := retrieveCallInfo().verboseFormat() msg := fmt.Sprintf(format, args...) if shallLog(LevelCritical, info, msg) { logBackend.Critical(info, msg) } } } // Fatalf logs a message independent of any level. After // logging the message the functions calls the fatal exiter // function, which by default means exiting the application // with error code -1. func Fatalf(format string, args ...interface{}) { logMux.RLock() defer logMux.RUnlock() info := retrieveCallInfo().verboseFormat() msg := fmt.Sprintf(format, args...) logBackend.Fatal(info, msg) logFatalExiter() } //-------------------- // LOGGER INTERFACE //-------------------- // Logger is the interface for different logger backends. type Logger interface { // Debug logs a debugging message. Debug(info, msg string) // Info logs an informational message. Info(info, msg string) // Warning logs a warning message. Warning(info, msg string) // Error logs an error message. Error(info, msg string) // Critical logs a critical error message. Critical(info, msg string) // Fatal logs a fatal error message. Fatal(info, msg string) } // defaultTimeFormat controls how the timestamp of the standard // logger is printed by default. const defaultTimeFormat = "2006-01-02 15:04:05 Z07:00" //-------------------- // STANDARD LOGGER //-------------------- // standardLogger is a simple logger writing to the given writer. Beside // the output it doesn't handle the levels differently. type standardLogger struct { mutex sync.Mutex out io.Writer timeFormat string } // NewTimeformatLogger creates a logger writing to the passed // output and with the time formatted with the passed time format. func NewTimeformatLogger(out io.Writer, timeFormat string) Logger { return &standardLogger{ out: out, timeFormat: timeFormat, } } // NewStandardLogger creates the standard logger writing // to the passed output. func NewStandardLogger(out io.Writer) Logger { return NewTimeformatLogger(out, defaultTimeFormat) } // Debug implements Logger. func (sl *standardLogger) Debug(info, msg string) { sl.writeLog("DEBUG", info, msg) } // Info implements Logger. func (sl *standardLogger) Info(info, msg string) { sl.writeLog("INFO", info, msg) } // Warning implements Logger. func (sl *standardLogger) Warning(info, msg string) { sl.writeLog("WARNING", info, msg) } // Error implements Logger. func (sl *standardLogger) Error(info, msg string) { sl.writeLog("ERROR", info, msg) } // Critical implements Logger. func (sl *standardLogger) Critical(info, msg string) { sl.writeLog("CRITICAL", info, msg) } // Fatal implements Logger. func (sl *standardLogger) Fatal(info, msg string) { sl.writeLog("FATAL", info, msg) } // writeLog writes the log output. func (sl *standardLogger) writeLog(level, info, msg string) { sl.mutex.Lock() defer sl.mutex.Unlock() io.WriteString(sl.out, time.Now().Format(sl.timeFormat)) io.WriteString(sl.out, " [") io.WriteString(sl.out, level) io.WriteString(sl.out, "] ") io.WriteString(sl.out, info) io.WriteString(sl.out, " ") io.WriteString(sl.out, msg) io.WriteString(sl.out, "\n") } // goLogger just uses the standard go log package. type goLogger struct{} // NewGoLogger returns a logger implementation using the // Go log package. func NewGoLogger() Logger { return &goLogger{} } // Debug is specified on the Logger interface. func (gl *goLogger) Debug(info, msg string) { log.Println("[DEBUG]", info, msg) } // Info is specified on the Logger interface. func (gl *goLogger) Info(info, msg string) { log.Println("[INFO]", info, msg) } // Warning is specified on the Logger interface. func (gl *goLogger) Warning(info, msg string) { log.Println("[WARNING]", info, msg) } // Error is specified on the Logger interface. func (gl *goLogger) Error(info, msg string) { log.Println("[ERROR]", info, msg) } // Critical is specified on the Logger interface. func (gl *goLogger) Critical(info, msg string) { log.Println("[CRITICAL]", info, msg) } // Fatal is specified on the Logger interface. func (gl *goLogger) Fatal(info, msg string) { log.Println("[FATAL]", info, msg) } // TestLogger extends the Logger interface with methods to retrieve // and reset the collected data. type TestLogger interface { Logger // Len returns the number of collected entries. Len() int // Entries returns the collected entries. Entries() []string // Reset clears the collected entries. Reset() } // testLogger simply collects logs to be evaluated inside of tests. type testLogger struct { mux sync.Mutex entries []string } // NewTestLogger returns a special logger for testing. func NewTestLogger() TestLogger { return &testLogger{} } // Debug implements Logger. func (tl *testLogger) Debug(info, msg string) { tl.mux.Lock() defer tl.mux.Unlock() entry := fmt.Sprintf("%d [DEBUG] %s %s", time.Now().UnixNano(), info, msg) tl.entries = append(tl.entries, entry) } // Info implements Logger. func (tl *testLogger) Info(info, msg string) { tl.mux.Lock() defer tl.mux.Unlock() entry := fmt.Sprintf("%d [INFO] %s %s", time.Now().UnixNano(), info, msg) tl.entries = append(tl.entries, entry) } // Warning implements Logger. func (tl *testLogger) Warning(info, msg string) { tl.mux.Lock() defer tl.mux.Unlock() entry := fmt.Sprintf("%d [WARNING] %s %s", time.Now().UnixNano(), info, msg) tl.entries = append(tl.entries, entry) } // Error implements Logger. func (tl *testLogger) Error(info, msg string) { tl.mux.Lock() defer tl.mux.Unlock() entry := fmt.Sprintf("%d [ERROR] %s %s", time.Now().UnixNano(), info, msg) tl.entries = append(tl.entries, entry) } // Critical implements Logger. func (tl *testLogger) Critical(info, msg string) { tl.mux.Lock() defer tl.mux.Unlock() entry := fmt.Sprintf("%d [CRITICAL] %s %s", time.Now().UnixNano(), info, msg) tl.entries = append(tl.entries, entry) } // Fatal implements Logger. func (tl *testLogger) Fatal(info, msg string) { tl.mux.Lock() defer tl.mux.Unlock() entry := fmt.Sprintf("%d [CRITICAL] %s %s", time.Now().UnixNano(), info, msg) tl.entries = append(tl.entries, entry) } // Len implements TestLogger. func (tl *testLogger) Len() int { tl.mux.Lock() defer tl.mux.Unlock() return len(tl.entries) } // Entries implements TestLogger. func (tl *testLogger) Entries() []string { tl.mux.Lock() defer tl.mux.Unlock() entries := make([]string, len(tl.entries)) copy(entries, tl.entries) return entries } // Reset implements TestLogger. func (tl *testLogger) Reset() { tl.mux.Lock() defer tl.mux.Unlock() tl.entries = nil } //-------------------- // HELPER //-------------------- // callInfo bundles the info about the call environment // when a logging statement occurred. type callInfo struct { packageName string fileName string funcName string line int } // shortFormat returns a string representation in a short variant. func (ci *callInfo) shortFormat() string { return fmt.Sprintf("[%s]", ci.packageName) } // verboseFormat returns a string representation in a more verbose variant. func (ci *callInfo) verboseFormat() string { return fmt.Sprintf("[%s] (%s:%s:%d)", ci.packageName, ci.fileName, ci.funcName, ci.line) } // retrieveCallInfo returns detailed information about // the call location. func retrieveCallInfo() *callInfo { pc, file, line, _ := runtime.Caller(2) _, fileName := path.Split(file) parts := strings.Split(runtime.FuncForPC(pc).Name(), ".") pl := len(parts) packageName := "" funcName := parts[pl-1] if parts[pl-2][0] == '(' { funcName = parts[pl-2] + "." + funcName packageName = strings.Join(parts[0:pl-2], ".") } else { packageName = strings.Join(parts[0:pl-1], ".") } return &callInfo{ packageName: packageName, fileName: fileName, funcName: funcName, line: line, } } // shallLog is used inside the logging functions to check if // logging is wanted. func shallLog(level LogLevel, info, msg string) bool { logMux.RLock() defer logMux.RUnlock() if logFilter == nil { return true } return !logFilter(level, info, msg) } // EOF golib-4.24.2/logger/logger_test.go000066400000000000000000000101771315505703200170320ustar00rootroot00000000000000// Tideland Go Library - Logger - Unit Tests // // Copyright (C) 2012-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package logger_test //-------------------- // IMPORTS //-------------------- import ( "log" "os" "testing" "github.com/tideland/golib/audit" "github.com/tideland/golib/logger" ) //-------------------- // TESTS //-------------------- // TestGetSetLevel tests the setting of the logging level. func TestGetSetLevel(t *testing.T) { assert := audit.NewTestingAssertion(t, true) level := logger.Level() defer logger.SetLevel(level) tl := logger.NewTestLogger() ol := logger.SetLogger(tl) defer logger.SetLogger(ol) logger.SetLevel(logger.LevelDebug) logger.Debugf("Debug.") logger.Infof("Info.") logger.Warningf("Warning.") logger.Errorf("Error.") logger.Criticalf("Critical.") assert.Length(tl, 5) tl.Reset() logger.SetLevel(logger.LevelError) logger.Debugf("Debug.") logger.Infof("Info.") logger.Warningf("Warning.") logger.Errorf("Error.") logger.Criticalf("Critical.") assert.Length(tl, 2) tl.Reset() } // TestGetSetLevelString tests the setting of the // logging level with a string. func TestGetSetLevelString(t *testing.T) { assert := audit.NewTestingAssertion(t, true) level := logger.Level() defer logger.SetLevel(level) tl := logger.NewTestLogger() ol := logger.SetLogger(tl) defer logger.SetLogger(ol) logger.SetLevelString("dEbUg") logger.Debugf("Debug.") logger.Infof("Info.") logger.Warningf("Warning.") logger.Errorf("Error.") logger.Criticalf("Critical.") assert.Length(tl, 5) tl.Reset() logger.SetLevelString("error") logger.Debugf("Debug.") logger.Infof("Info.") logger.Warningf("Warning.") logger.Errorf("Error.") logger.Criticalf("Critical.") assert.Length(tl, 2) tl.Reset() logger.SetLevelString("dont-know-what-you-mean") logger.Debugf("Debug.") logger.Infof("Info.") logger.Warningf("Warning.") logger.Errorf("Error.") logger.Criticalf("Critical.") assert.Length(tl, 2) tl.Reset() } // TestFiltering tests the filtering of the logging. func TestFiltering(t *testing.T) { assert := audit.NewTestingAssertion(t, true) level := logger.Level() defer logger.SetLevel(level) tl := logger.NewTestLogger() ol := logger.SetLogger(tl) defer logger.SetLogger(ol) logger.SetLevel(logger.LevelDebug) logger.SetFilter(func(level logger.LogLevel, info, msg string) bool { return level >= logger.LevelWarning && level <= logger.LevelError }) logger.Debugf("Debug.") logger.Infof("Info.") logger.Warningf("Warning.") logger.Errorf("Error.") logger.Criticalf("Critical.") assert.Length(tl, 3) tl.Reset() logger.UnsetFilter() logger.Debugf("Debug.") logger.Infof("Info.") logger.Warningf("Warning.") logger.Errorf("Error.") logger.Criticalf("Critical.") assert.Length(tl, 5) tl.Reset() } // TestGoLogger tests logging with the go logger. func TestGoLogger(t *testing.T) { level := logger.Level() defer logger.SetLevel(level) log.SetOutput(os.Stdout) logger.SetLevel(logger.LevelDebug) logger.SetLogger(logger.NewGoLogger()) logger.Debugf("Debug.") logger.Infof("Info.") logger.Warningf("Warning.") logger.Errorf("Error.") logger.Criticalf("Critical.") } // TestSysLogger tests logging with the syslogger. func TestSysLogger(t *testing.T) { assert := audit.NewTestingAssertion(t, true) level := logger.Level() defer logger.SetLevel(level) logger.SetLevel(logger.LevelDebug) sl, err := logger.NewSysLogger("GOAS") assert.Nil(err) logger.SetLogger(sl) logger.Debugf("Debug.") logger.Infof("Info.") logger.Warningf("Warning.") logger.Errorf("Error.") logger.Criticalf("Critical.") } // TestFatalExit tests the call of the fatal exiter after a // fatal error log. func TestFatalExit(t *testing.T) { assert := audit.NewTestingAssertion(t, true) level := logger.Level() defer logger.SetLevel(level) tl := logger.NewTestLogger() ol := logger.SetLogger(tl) defer logger.SetLogger(ol) exited := false fatalExiter := func() { exited = true } logger.SetFatalExiter(fatalExiter) logger.Fatalf("fatal") assert.Length(tl, 1) assert.True(exited) tl.Reset() } // EOF golib-4.24.2/logger/nosyslogger.go000066400000000000000000000031471315505703200170660ustar00rootroot00000000000000// Tideland Go Library - Logger - No SysLogger // // Copyright (C) 2012-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. // +build windows plan9 nacl package logger //-------------------- // IMPORTS //-------------------- import ( "log" ) //-------------------- // SYSLOGGER //-------------------- // SysLogger uses the Go syslog package as logging backend. It does // not work on Windows or Plan9. Here it uses the standard Go logger. type SysLogger struct { tag string } // NewGoLogger returns a logger implementation using the // Go syslog package. func NewSysLogger(tag string) (Logger, error) { if len(tag) > 0 { tag = "(" + tag + ")" } return &SysLogger{tag}, nil } // Debug is specified on the Logger interface. func (sl *SysLogger) Debug(info, msg string) { log.Println("[DEBUG]", sl.tag, info, msg) } // Info is specified on the Logger interface. func (sl *SysLogger) Info(info, msg string) { log.Println("[INFO]", sl.tag, info, msg) } // Warning is specified on the Logger interface. func (sl *SysLogger) Warning(info, msg string) { log.Println("[WARNING]", sl.tag, info, msg) } // Error is specified on the Logger interface. func (sl *SysLogger) Error(info, msg string) { log.Println("[ERROR]", sl.tag, info, msg) } // Critical is specified on the Logger interface. func (sl *SysLogger) Critical(info, msg string) { log.Println("[CRITICAL]", sl.tag, info, msg) } // Fatal is specified on the Logger interface. func (sl *SysLogger) Fatal(info, msg string) { log.Println("[FATAL]", sl.tag, info, msg) } // EOF golib-4.24.2/logger/syslogger.go000066400000000000000000000032211315505703200165220ustar00rootroot00000000000000// Tideland Go Library - Logger - SysLogger // // Copyright (C) 2012-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. // +build !windows,!nacl,!plan9 package logger //-------------------- // IMPORTS //-------------------- import ( "log" "log/syslog" ) //-------------------- // SYSLOGGER //-------------------- // SysLogger uses the Go syslog package as logging backend. It does // not work on Windows or Plan9. type SysLogger struct { writer *syslog.Writer } // NewSysLogger returns a logger implementation using the // Go syslog package. func NewSysLogger(tag string) (Logger, error) { writer, err := syslog.New(syslog.LOG_DEBUG|syslog.LOG_LOCAL0, tag) if err != nil { log.Fatalf("cannot init syslog: %v", err) return nil, err } return &SysLogger{writer}, nil } // Debug is specified on the Logger interface. func (sl *SysLogger) Debug(info, msg string) { sl.writer.Debug(info + " " + msg) } // Info is specified on the Logger interface. func (sl *SysLogger) Info(info, msg string) { sl.writer.Info(info + " " + msg) } // Warning is specified on the Logger interface. func (sl *SysLogger) Warning(info, msg string) { sl.writer.Warning(info + " " + msg) } // Error is specified on the Logger interface. func (sl *SysLogger) Error(info, msg string) { sl.writer.Err(info + " " + msg) } // Critical is specified on the Logger interface. func (sl *SysLogger) Critical(info, msg string) { sl.writer.Crit(info + " " + msg) } // Fatal is specified on the Logger interface. func (sl *SysLogger) Fatal(info, msg string) { sl.writer.Emerg(info + " " + msg) } // EOF golib-4.24.2/loop/000077500000000000000000000000001315505703200136515ustar00rootroot00000000000000golib-4.24.2/loop/doc.go000066400000000000000000000024051315505703200147460ustar00rootroot00000000000000// Tideland Go Library - Loop // // Copyright (C) 2013-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. // Package loop of the Tideland Go Library supports the // developer implementing the typical Go idiom for // concurrent applications running in a loop in the background // and doing a select on one or more channels. Stopping those // loops or getting aware of internal errors requires extra // efforts. The loop package helps to control this kind of // goroutines. // // Beside the simple controlled loops the also can be made // recoverable. In this case a user defined recovery function // gets notified if a loop ends with an error or panics. // The paseed passed list of recovering information helps // to check the reason and frequency. // // A third way are sentinels. Those can monitor multiple // loops and other sentinels. So hierarchies can be defined. // In case of no handler function an error of one monitored // instance will lead to a stop of all monitored instances. // Otherwise the user can check the error reason inside // the handler function and optionally restart the loop // or sentinel. // // See the example functions for more information. package loop // EOF golib-4.24.2/loop/errors.go000066400000000000000000000021021315505703200155070ustar00rootroot00000000000000// Tideland Go Library - Loop - Errors // // Copyright (C) 2013-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package loop //-------------------- // IMPORTS //-------------------- import ( "github.com/tideland/golib/errors" ) //-------------------- // CONSTANTS //-------------------- // Error codes of the loop package. const ( ErrLoopPanicked = iota + 1 ErrHandlingFailed ErrRestartNonStopped ErrKilledBySentinel ) var errorMessages = errors.Messages{ ErrLoopPanicked: "loop panicked: %v", ErrHandlingFailed: "error handling for %q failed", ErrRestartNonStopped: "cannot restart unstopped %q", ErrKilledBySentinel: "%q killed by sentinel", } //-------------------- // TESTING //-------------------- // IsKilledBySentinelError allows to check, if a loop or // sentinel has been stopped due to internal reasons or // after the error of another loop or sentinel. func IsKilledBySentinelError(err error) bool { return errors.IsError(err, ErrKilledBySentinel) } // EOF golib-4.24.2/loop/loop.go000066400000000000000000000233751315505703200151630ustar00rootroot00000000000000// Tideland Go Application Support - Loop // // Copyright (C) 2013-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package loop //-------------------- // IMPORTS //-------------------- import ( "fmt" "sync" "time" "github.com/tideland/golib/errors" "github.com/tideland/golib/identifier" "github.com/tideland/golib/logger" ) //-------------------- // API //-------------------- // Go starts the loop function in the background. The loop can be // stopped or killed. This leads to a signal out of the channel // Loop.ShallStop. The loop then has to end working returning // a possible error. Wait then waits until the loop ended and // returns the error. func Go(lf LoopFunc, dps ...interface{}) Loop { descr := identifier.SepIdentifier("::", dps...) return goLoop(lf, nil, nil, nil, descr) } // GoRecoverable starts the loop function in the background. The // loop can be stopped or killed. This leads to a signal out of the // channel Loop.ShallStop. The loop then has to end working returning // a possible error. Wait then waits until the loop ended and returns // the error. // // If the loop panics a Recovering is created and passed with all // Recoverings before to the RecoverFunc. If it returns nil the // loop will be started again. Otherwise the loop will be killed // with that error. func GoRecoverable(lf LoopFunc, rf RecoverFunc, dps ...interface{}) Loop { descr := identifier.SepIdentifier("::", dps...) return goLoop(lf, rf, nil, nil, descr) } // GoSentinel starts a new sentinel. It can manage loops and other sentinels // and will stop them in case of errors. func GoSentinel(dps ...interface{}) Sentinel { descr := identifier.SepIdentifier("::", dps...) return goSentinel(nil, nil, descr) } // GoNotifiedSentinel starts a new sentinel with a notification handler // function. It can manage loops and other sentinels and restart them in // case of errors, based on the notification handler function. func GoNotifiedSentinel(nhf NotificationHandlerFunc, dps ...interface{}) Sentinel { descr := identifier.SepIdentifier("::", dps...) return goSentinel(nhf, nil, descr) } //-------------------- // RECOVERING //-------------------- // Recovering stores time and reason of one of the recoverings. type Recovering struct { Time time.Time Reason interface{} } // Recoverings is a list of recoverings a loop already had. type Recoverings []*Recovering // Frequency checks if a given number of restarts happened during // a given duration. func (rs Recoverings) Frequency(num int, dur time.Duration) bool { if len(rs) >= num { first := rs[len(rs)-num].Time last := rs[len(rs)-1].Time return last.Sub(first) <= dur } return false } // Len returns the length of the recoverings. func (rs Recoverings) Len() int { return len(rs) } // Trim returns the last recoverings defined by l. This // way the recover func can con control the length and take // care that the list not grows too much. func (rs Recoverings) Trim(l int) Recoverings { if l >= len(rs) { return rs } return rs[len(rs)-l:] } // First returns the first recovering. func (rs Recoverings) First() *Recovering { if len(rs) > 0 { return rs[0] } return nil } // Last returns the last recovering. func (rs Recoverings) Last() *Recovering { if len(rs) > 0 { return rs[len(rs)-1] } return nil } // RecoverFunc decides if a loop shall be started again or // end with an error. It is also responsible to trim the // list of revocerings if needed. type RecoverFunc func(rs Recoverings) (Recoverings, error) //-------------------- // OBSERVABLE //-------------------- // Observable is a common base interface for those objects // that a sentinel can monitor. type Observable interface { fmt.Stringer // Stop tells the observable to stop working and waits until it is done. Stop() error // Kill kills the observable with the passed error. Kill(err error) // Wait blocks the caller until the observable ended and returns // a possible error. Wait() error // Restart stops the observable and restarts it afterwards. Restart() error // Error returns information about the current status and error. Error() (status int, err error) // attachSentinel attaches the observable to a sentinel. attachSentinel(s *sentinel) } //-------------------- // LOOP //-------------------- // Status of the loop. const ( Running = iota Stopping Stopped ) // LoopFunc is managed loop function. type LoopFunc func(l Loop) error // Loop manages running loops in the background as goroutines. type Loop interface { Observable // ShallStop returns a channel signalling the loop to // stop working. ShallStop() <-chan struct{} // IsStopping returns a channel that can be used to wait until // the loop is stopping or to avoid deadlocks when communicating // with the loop. IsStopping() <-chan struct{} } // Loop manages a loop function. type loop struct { mux sync.Mutex descr string status int err error loopF LoopFunc recoverF RecoverFunc recoverings Recoverings startedC chan struct{} stopC chan struct{} doneC chan struct{} owner Observable sentinel *sentinel } // goLoop starts a loop in the background. func goLoop(lf LoopFunc, rf RecoverFunc, o Observable, s *sentinel, d string) *loop { l := &loop{ descr: d, loopF: lf, recoverF: rf, startedC: make(chan struct{}), stopC: make(chan struct{}), doneC: make(chan struct{}), owner: o, sentinel: s, } // Check owner, at least we should own ourself. if l.owner == nil { l.owner = l } // Start the loop. l.logf(false, "loop %q starts", l) go l.run() <-l.startedC return l } // String implements the Stringer interface. It returns // the description of the loop. func (l *loop) String() string { return l.descr } // Stop implements the Observable interface. func (l *loop) Stop() error { l.terminate(nil) return l.Wait() } // Kill implements the Observable interface. func (l *loop) Kill(err error) { l.terminate(err) } // Wait implements the Observable interface. func (l *loop) Wait() error { <-l.doneC l.mux.Lock() defer l.mux.Unlock() err := l.err return err } // Restart implements the Observable interface. func (l *loop) Restart() error { l.mux.Lock() defer l.mux.Unlock() if l.status != Stopped { return errors.New(ErrRestartNonStopped, errorMessages, l) } l.err = nil l.recoverings = nil l.status = Running l.stopC = make(chan struct{}) l.doneC = make(chan struct{}) // Restart the goroutine. l.logf(false, "loop %q restarts", l) go l.run() <-l.startedC return nil } // Error implements the Observable interface. func (l *loop) Error() (status int, err error) { l.mux.Lock() defer l.mux.Unlock() status = l.status err = l.err return } // attachSentinel implements the Observable interface. func (l *loop) attachSentinel(s *sentinel) { l.mux.Lock() defer l.mux.Unlock() if l.sentinel != nil { l.sentinel.Forget(l) } l.sentinel = s } // ShallStop implements the Loop interface. func (l *loop) ShallStop() <-chan struct{} { return l.stopC } // IsStopping implements the Loop interface. func (l *loop) IsStopping() <-chan struct{} { return l.stopC } // run operates the loop as goroutine. func (l *loop) run() { l.status = Running // Finalize the loop. defer l.finalizeTermination() // Create a loop wrapper containing the recovering control. loopWrapper := func() { defer func() { // Check for recovering. if reason := recover(); reason != nil { l.checkTermination(reason) } }() l.checkTermination(l.loopF(l)) } // Now start running the loop wrappr. l.startedC <- struct{}{} for l.status == Running { loopWrapper() } } // terminate tells the loop to stop working and stores // the passed error if none has been stored already. func (l *loop) terminate(err error) { l.mux.Lock() defer l.mux.Unlock() if l.err == nil { l.err = err } if l.status != Running { return } l.status = Stopping select { case <-l.stopC: default: close(l.stopC) } } // checkTermination checks if an error has been the reason and if // it possibly can be recovered by a recover function. func (l *loop) checkTermination(reason interface{}) { switch { case reason == nil: // Regular end. l.status = Stopping case l.recoverF == nil: // Error but no recover function. l.status = Stopping if l.err != nil { break } if err, ok := reason.(error); ok { l.err = err } else { l.err = errors.New(ErrLoopPanicked, errorMessages, reason) } default: // Try to recover. logger.Errorf("loop %q tries to recover", l) l.recoverings = append(l.recoverings, &Recovering{time.Now(), reason}) l.recoverings, l.err = l.recoverF(l.recoverings) if l.err != nil { l.status = Stopping } else { l.logf(false, "loop %q recovered", l) } } } // finalizeTermination notifies listeners that the loop stopped // working and a potential sentinal about its status. func (l *loop) finalizeTermination() { l.status = Stopped // Close stopC in case the termination is due to an // error or internal. select { case <-l.stopC: default: close(l.stopC) } // Communicate that it's done. select { case <-l.doneC: default: close(l.doneC) } // If a sentinel monitors us then till him. if l.sentinel != nil { if l.err != nil { // Notify sentinel about error termination. l.sentinel.notifyC <- l.owner } else { // Tell sentinel to remove loop. l.sentinel.Forget(l) } } if l.err != nil { l.logf(true, "loop %q stopped with error: %v", l, l.err) } else { l.logf(false, "loop %q stopped", l) } } // log writes information or error only if the loop has a description. func (l *loop) logf(isError bool, format string, a ...interface{}) { if l.descr == "" { return } if isError { logger.Errorf(format, a...) } else { logger.Infof(format, a...) } } // EOF golib-4.24.2/loop/loop_test.go000066400000000000000000000535471315505703200162260ustar00rootroot00000000000000// Tideland Go Library - Loop - Unit Test // // Copyright (C) 2013-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package loop_test //-------------------- // IMPORTS //-------------------- import ( "errors" "testing" "time" "github.com/tideland/golib/audit" "github.com/tideland/golib/logger" "github.com/tideland/golib/loop" ) //-------------------- // CONSTANTS //-------------------- // timeout is the waitng time for events from inside of loops. var timeout time.Duration = 5 * time.Second //-------------------- // TESTS //-------------------- // TestSimpleStop tests the simple backend returning nil // after a stop. func TestSimpleStop(t *testing.T) { assert := audit.NewTestingAssertion(t, true) bundle := newChannelBundle() l := loop.Go(makeSimpleLF(bundle), "simple-stop") assert.Wait(bundle.startedc, true, timeout) assert.Nil(l.Stop(), "no error after simple stop") assert.Wait(bundle.donec, true, timeout) status, _ := l.Error() assert.Equal(loop.Stopped, status, "loop is stopped") } // TestLogging tests running a loop without logging. func TestLogging(t *testing.T) { assert := audit.NewTestingAssertion(t, true) tl := logger.NewTestLogger() ol := logger.SetLogger(tl) defer logger.SetLogger(ol) // With description, so logging. bundle := newChannelBundle() l := loop.Go(makeSimpleLF(bundle), "logger") assert.Wait(bundle.startedc, true, timeout) assert.Nil(l.Stop(), "no error after simple stop") assert.Wait(bundle.donec, true, timeout) status, _ := l.Error() assert.Equal(loop.Stopped, status, "loop is stopped") assert.Length(tl, 2) tl.Reset() // Without description, so no logging. bundle = newChannelBundle() l = loop.Go(makeSimpleLF(bundle)) assert.Wait(bundle.startedc, true, timeout) assert.Nil(l.Stop(), "no error after simple stop") assert.Wait(bundle.donec, true, timeout) status, _ = l.Error() assert.Equal(loop.Stopped, status, "loop is stopped") assert.Length(tl, 0) tl.Reset() } // TestSimpleRestart tests restarting when not stopped and // when stopped. func TestSimpleRestart(t *testing.T) { assert := audit.NewTestingAssertion(t, true) bundle := newChannelBundle() l := loop.Go(makeSimpleLF(bundle), "simple-restart") assert.Wait(bundle.startedc, true, timeout) assert.ErrorMatch(l.Restart(), ".*cannot restart unstopped.*") status, _ := l.Error() assert.Equal(loop.Running, status) assert.Nil(l.Stop()) assert.Wait(bundle.donec, true, timeout) status, _ = l.Error() assert.Equal(loop.Stopped, status) assert.Nil(l.Restart()) assert.Wait(bundle.startedc, true, timeout) status, _ = l.Error() assert.Equal(loop.Running, status) assert.Nil(l.Stop()) assert.Wait(bundle.donec, true, timeout) } // TestSimpleKill tests the simple backend returning an error // after a kill. func TestSimpleKill(t *testing.T) { assert := audit.NewTestingAssertion(t, true) bundle := newChannelBundle() l := loop.Go(makeSimpleLF(bundle), "simple-kill") assert.Wait(bundle.startedc, true, timeout) l.Kill(errors.New("ouch")) assert.ErrorMatch(l.Stop(), "ouch") assert.Wait(bundle.donec, true, timeout) status, _ := l.Error() assert.Equal(status, loop.Stopped) } // TestError tests an internal error. func TestError(t *testing.T) { assert := audit.NewTestingAssertion(t, true) bundle := newChannelBundle() l := loop.Go(makeErrorLF(bundle), "error") assert.Wait(bundle.startedc, true, timeout) bundle.errorc <- true assert.ErrorMatch(l.Stop(), "internal loop error") assert.Wait(bundle.donec, true, timeout) status, _ := l.Error() assert.Equal(status, loop.Stopped) } // TestDeferredError tests an error in a deferred function inside the loop. func TestDeferredError(t *testing.T) { assert := audit.NewTestingAssertion(t, true) bundle := newChannelBundle() l := loop.Go(makeDeferredErrorLF(bundle), "deferred-error") assert.Wait(bundle.startedc, true, timeout) assert.ErrorMatch(l.Stop(), "deferred error") assert.Wait(bundle.donec, true, timeout) status, _ := l.Error() assert.Equal(status, loop.Stopped) } // TestStopRecoverings tests the regular stop of a recovered loop. func TestStopRecoverings(t *testing.T) { assert := audit.NewTestingAssertion(t, true) lbundle := newChannelBundle() rbundle := newChannelBundle() l := loop.GoRecoverable(makeRecoverPanicLF(lbundle), makeIgnorePanicsRF(rbundle), "stop-recoverings") assert.Wait(lbundle.startedc, true, timeout) lbundle.errorc <- true assert.Nil(l.Stop()) assert.Wait(rbundle.donec, "recovered", timeout) status, _ := l.Error() assert.Equal(loop.Stopped, status, "loop is stopped") } // TestEndRecoverings tests the regular internal stop of a recovered loop. func TestEndRecoverings(t *testing.T) { assert := audit.NewTestingAssertion(t, true) lbundle := newChannelBundle() rbundle := newChannelBundle() l := loop.GoRecoverable(makeRecoverNoErrorLF(lbundle), makeIgnorePanicsRF(rbundle), "end-recoverings") assert.Wait(lbundle.startedc, true, timeout) lbundle.stopc <- true assert.Wait(lbundle.donec, true, timeout) status, _ := l.Error() assert.Equal(status, loop.Stopped) } // TestRecoveringsPanic test recoverings after panics. func TestRecoveringsPanic(t *testing.T) { assert := audit.NewTestingAssertion(t, true) lbundle := newChannelBundle() rbundle := newChannelBundle() l := loop.GoRecoverable(makeRecoverPanicLF(lbundle), makeCheckCountRF(rbundle), "recoverings-panic") go func() { for i := 0; i < 10; i++ { <-lbundle.startedc lbundle.errorc <- true <-rbundle.startedc } }() assert.Wait(rbundle.donec, 5, timeout) assert.ErrorMatch(l.Stop(), "too many panics") status, _ := l.Error() assert.Equal(status, loop.Stopped) } // TestRecoveringsError tests recoverings after errors. func TestRecoveringsError(t *testing.T) { assert := audit.NewTestingAssertion(t, true) lbundle := newChannelBundle() rbundle := newChannelBundle() l := loop.GoRecoverable(makeRecoverErrorLF(lbundle), makeCatchErrorRF(rbundle), "recoverings-error") assert.Wait(lbundle.startedc, true, timeout) lbundle.errorc <- true assert.ErrorMatch(l.Stop(), "error") assert.Wait(rbundle.donec, "error", timeout) status, _ := l.Error() assert.Equal(loop.Stopped, status, "loop is stopped") } // TestDescription tests the handling of loop and // sentinel descriptions. func TestDescription(t *testing.T) { assert := audit.NewTestingAssertion(t, true) abundle := newChannelBundle() bbundle := newChannelBundle() s := loop.GoSentinel("one") lA := loop.Go(makeSimpleLF(abundle), "two", "three", "four") lB := loop.Go(makeSimpleLF(bbundle)) assert.Wait(abundle.startedc, true, timeout) assert.Wait(bbundle.startedc, true, timeout) s.Observe(lA, lB) assert.Equal(s.String(), "one") assert.Equal(lA.String(), "two::three::four") assert.Nil(s.Stop()) } // TestSimpleSentinel tests the simple starting and // stopping of a sentinel. func TestSimpleSentinel(t *testing.T) { assert := audit.NewTestingAssertion(t, true) abundle := newChannelBundle() bbundle := newChannelBundle() cbundle := newChannelBundle() s := loop.GoSentinel("simple-sentinel") lA := loop.Go(makeSimpleLF(abundle), "loop", "a") lB := loop.Go(makeSimpleLF(bbundle), "loop", "b") lC := loop.Go(makeSimpleLF(cbundle), "loop", "c") assert.Wait(abundle.startedc, true, timeout) assert.Wait(bbundle.startedc, true, timeout) assert.Wait(cbundle.startedc, true, timeout) s.Observe(lA, lB, lC) assert.Nil(s.Stop()) assert.Wait(abundle.donec, true, timeout) assert.Wait(bbundle.donec, true, timeout) assert.Wait(cbundle.donec, true, timeout) status, _ := s.Error() assert.Equal(status, loop.Stopped) } // TestSentinelStoppingLoop tests the stopping // of a loop before sentinel stops. func TestSentinelStoppingLoop(t *testing.T) { assert := audit.NewTestingAssertion(t, true) abundle := newChannelBundle() bbundle := newChannelBundle() cbundle := newChannelBundle() s := loop.GoSentinel("sentinel-stopping-loop") lA := loop.Go(makeSimpleLF(abundle), "loop", "a") lB := loop.Go(makeSimpleLF(bbundle), "loop", "b") lC := loop.Go(makeSimpleLF(cbundle), "loop", "c") assert.Wait(abundle.startedc, true, timeout) assert.Wait(bbundle.startedc, true, timeout) assert.Wait(cbundle.startedc, true, timeout) s.Observe(lA, lB, lC) assert.Nil(lB.Stop()) assert.Wait(bbundle.donec, true, timeout) assert.Nil(s.Stop()) assert.Wait(abundle.donec, true, timeout) assert.Wait(cbundle.donec, true, timeout) status, _ := s.Error() assert.Equal(status, loop.Stopped) } // TestSentinelForget tests the forgetting of loops // by a sentinel. func TestSentineForget(t *testing.T) { assert := audit.NewTestingAssertion(t, true) abundle := newChannelBundle() bbundle := newChannelBundle() cbundle := newChannelBundle() dbundle := newChannelBundle() s := loop.GoSentinel("sentinel-forget") lA := loop.Go(makeSimpleLF(abundle), "loop", "a") lB := loop.Go(makeSimpleLF(bbundle), "loop", "b") lC := loop.Go(makeSimpleLF(cbundle), "loop", "c") lD := loop.Go(makeSimpleLF(dbundle), "loop", "d") assert.Wait(abundle.startedc, true, timeout) assert.Wait(bbundle.startedc, true, timeout) assert.Wait(cbundle.startedc, true, timeout) assert.Wait(dbundle.startedc, true, timeout) s.Observe(lA, lB, lC, lD) s.Forget(lB, lC) assert.Nil(s.Stop()) assert.Wait(abundle.donec, true, timeout) assert.Wait(dbundle.donec, true, timeout) status, _ := s.Error() assert.Equal(loop.Stopped, status) } // TestSentinelKillingLoopNoHandler tests the killing // of a loop before sentinel stops. The sentinel has // no handler and so ignores the error. func TestSentinelKillingLoopNoHandler(t *testing.T) { assert := audit.NewTestingAssertion(t, true) abundle := newChannelBundle() bbundle := newChannelBundle() cbundle := newChannelBundle() s := loop.GoSentinel("sentinel-killing-loop-no-handler") lA := loop.Go(makeSimpleLF(abundle), "loop", "a") lB := loop.Go(makeSimpleLF(bbundle), "loop", "b") lC := loop.Go(makeSimpleLF(cbundle), "loop", "c") s.Observe(lA, lB, lC) assert.Wait(abundle.startedc, true, timeout) assert.Wait(bbundle.startedc, true, timeout) assert.Wait(cbundle.startedc, true, timeout) lB.Kill(errors.New("bang!")) assert.Wait(abundle.donec, true, timeout) assert.Wait(bbundle.donec, true, timeout) assert.Wait(cbundle.donec, true, timeout) assert.ErrorMatch(s.Stop(), ".*bang!.*") status, _ := s.Error() assert.Equal(loop.Stopped, status) } // TestSentinelKillingLoopHandlerRestarts tests the killing // of a loop before sentinel stops. The sentinel has // a handler and restarts the loop. func TestSentinelKillingLoopHandlerRestarts(t *testing.T) { assert := audit.NewTestingAssertion(t, true) abundle := newChannelBundle() bbundle := newChannelBundle() cbundle := newChannelBundle() sbundle := newChannelBundle() shandler := func(s loop.Sentinel, o loop.Observable, rs loop.Recoverings) (loop.Recoverings, error) { o.Restart() sbundle.donec <- true return nil, nil } s := loop.GoNotifiedSentinel(shandler, "sentinel-killing-loop-handler-restarts") lA := loop.Go(makeSimpleLF(abundle), "loop", "a") lB := loop.Go(makeSimpleLF(bbundle), "loop", "b") lC := loop.Go(makeSimpleLF(cbundle), "loop", "c") s.Observe(lA, lB, lC) assert.Wait(abundle.startedc, true, timeout) assert.Wait(bbundle.startedc, true, timeout) assert.Wait(cbundle.startedc, true, timeout) lB.Kill(errors.New("bang!")) assert.Wait(bbundle.donec, true, timeout) assert.Wait(sbundle.donec, true, timeout) assert.Nil(s.Stop()) assert.Wait(abundle.donec, true, timeout) assert.Wait(bbundle.donec, true, timeout) assert.Wait(cbundle.donec, true, timeout) status, _ := s.Error() assert.Equal(loop.Stopped, status) } // TestSentinelKillingLoopHandlerStops tests the killing // of a loop before sentinel stops. The sentinel has // a handler which stops the processing. func TestSentinelKillingLoopHandlerStops(t *testing.T) { assert := audit.NewTestingAssertion(t, true) abundle := newChannelBundle() bbundle := newChannelBundle() cbundle := newChannelBundle() sbundle := newChannelBundle() shandler := func(s loop.Sentinel, o loop.Observable, rs loop.Recoverings) (loop.Recoverings, error) { sbundle.donec <- true return nil, errors.New("oh no!") } s := loop.GoNotifiedSentinel(shandler, "sentinel-killing-loop-with-stops") lA := loop.Go(makeSimpleLF(abundle), "loop", "a") lB := loop.Go(makeSimpleLF(bbundle), "loop", "b") lC := loop.Go(makeSimpleLF(cbundle), "loop", "c") s.Observe(lA, lB, lC) assert.Wait(abundle.startedc, true, timeout) assert.Wait(bbundle.startedc, true, timeout) assert.Wait(cbundle.startedc, true, timeout) lB.Kill(errors.New("bang!")) assert.Wait(abundle.donec, true, timeout) assert.Wait(bbundle.donec, true, timeout) assert.Wait(cbundle.donec, true, timeout) assert.Wait(sbundle.donec, true, timeout) assert.ErrorMatch(s.Stop(), ".*oh no!.*") status, _ := s.Error() assert.Equal(loop.Stopped, status) } // TestSentinelKillingLoopHandlerRestartAll tests the killing // of a loop before sentinel stops. The sentinel has // a handler which restarts all observables. func TestSentinelKillingLoopHandlerRestartAll(t *testing.T) { assert := audit.NewTestingAssertion(t, true) abundle := newChannelBundle() bbundle := newChannelBundle() cbundle := newChannelBundle() sbundle := newChannelBundle() shandler := func(s loop.Sentinel, _ loop.Observable, _ loop.Recoverings) (loop.Recoverings, error) { s.ObservablesDo(func(o loop.Observable) error { o.Restart() return nil }) sbundle.donec <- true return nil, nil } s := loop.GoNotifiedSentinel(shandler, "sentinel-killing-loop-restarting-all") lA := loop.Go(makeSimpleLF(abundle), "loop", "a") lB := loop.Go(makeSimpleLF(bbundle), "loop", "b") lC := loop.Go(makeSimpleLF(cbundle), "loop", "c") s.Observe(lA, lB, lC) assert.Wait(abundle.startedc, true, timeout) assert.Wait(bbundle.startedc, true, timeout) assert.Wait(cbundle.startedc, true, timeout) lB.Kill(errors.New("bang!")) assert.Wait(bbundle.donec, true, timeout) assert.Wait(sbundle.donec, true, timeout) assert.Nil(s.Stop()) assert.Wait(abundle.donec, true, timeout) assert.Wait(bbundle.donec, true, timeout) assert.Wait(cbundle.donec, true, timeout) status, _ := s.Error() assert.Equal(loop.Stopped, status) } // TestNestedSentinelKill tests the killing and restarting of a // nested sentinel. func TestNestedSentinelKill(t *testing.T) { assert := audit.NewTestingAssertion(t, true) abundle := newChannelBundle() bbundle := newChannelBundle() sbundle := newChannelBundle() shandler := func(s loop.Sentinel, o loop.Observable, rs loop.Recoverings) (loop.Recoverings, error) { o.Restart() sbundle.donec <- true return nil, nil } sT := loop.GoNotifiedSentinel(shandler, "nested-sentinel-kill", "top") lA := loop.Go(makeSimpleLF(abundle), "loop", "a") sN := loop.GoNotifiedSentinel(shandler, "nested-sentinel-kill", "nested") lB := loop.Go(makeSimpleLF(bbundle), "loop", "b") sT.Observe(lA, sN) sN.Observe(lB) assert.Wait(abundle.startedc, true, timeout) assert.Wait(bbundle.startedc, true, timeout) sN.Kill(errors.New("bang!")) assert.Wait(sbundle.donec, true, timeout) assert.Nil(sT.Stop()) assert.Wait(bbundle.donec, true, timeout) assert.Wait(abundle.donec, true, timeout) status, _ := sT.Error() assert.Equal(loop.Stopped, status) } // TestSentinelSwitch tests if the change of the assignment // of a sentinel is handled correctly. func TestSentinelSwitch(t *testing.T) { assert := audit.NewTestingAssertion(t, true) abundle := newChannelBundle() sA := loop.GoSentinel("sentinel-switch", "a") sB := loop.GoSentinel("sentinel-switch", "b") lA := loop.Go(makeSimpleLF(abundle), "loop", "a") sA.Observe(lA) assert.Wait(abundle.startedc, true, timeout) sB.Observe(lA) assert.Nil(sA.Stop()) assert.Nil(sB.Stop()) assert.Wait(abundle.donec, true, timeout) } //-------------------- // EXAMPLES //-------------------- // ExampleLoopFunc shows the usage of loop.Go with one // loop function and no recovery. The inner loop contains // a select listening to the channel returned by ShallStop. // Other channels are for the standard communication // with the loop. func ExampleLoopFunc() { printC := make(chan string) // Sample loop function. loopF := func(l loop.Loop) error { for { select { case <-l.ShallStop(): // We shall stop. return nil case str := <-printC: if str == "panic" { return errors.New("panic") } println(str) } } } l := loop.Go(loopF, "simple loop demo") printC <- "Hello" printC <- "World" if err := l.Stop(); err != nil { panic(err) } } // ExampleRecoverFunc demonstrates the usage of a recovery // function when using loop.GoRecoverable. Here the frequency // of the recoverings (more than five in 10 milliseconds) // or the total number is checked. If the total number is // not interesting the recoverings could be trimmed by // e.g. rs.Trim(5). The fields Time and Reason per // recovering allow even more diagnosis. func ExampleRecoverFunc() { printC := make(chan string) loopF := func(l loop.Loop) error { for { select { case <-l.ShallStop(): return nil case str := <-printC: println(str) } } } // Recovery function checking frequency and total number. recoverF := func(rs loop.Recoverings) (loop.Recoverings, error) { if rs.Frequency(5, 10*time.Millisecond) { return nil, errors.New("too high error frequency") } if rs.Len() >= 10 { return nil, errors.New("too many errors") } return rs, nil } loop.GoRecoverable(loopF, recoverF, "recoverable loop demo") } // ExampleSentinel demonstrates the monitoring of loops and sentinel // with a handler function trying to restart the faulty observable. // The nested sentinel has no handler function. An error of a monitored // observable would lead to the stop of all observables. func ExampleSentinel() { loopF := func(l loop.Loop) error { for { select { case <-l.ShallStop(): return nil } } } handleF := func(s loop.Sentinel, o loop.Observable, rs loop.Recoverings) (loop.Recoverings, error) { if rs.Frequency(5, 10*time.Millisecond) { return nil, errors.New("too high error frequency") } return nil, o.Restart() } loopA := loop.Go(loopF, "loop", "a") loopB := loop.Go(loopF, "loop", "b") loopC := loop.Go(loopF, "loop", "c") loopD := loop.Go(loopF, "loop", "d") sentinel := loop.GoNotifiedSentinel(handleF, "sentinel demo") sentinel.Observe(loopA, loopB) // Hierarchies are possible. observedSentinel := loop.GoSentinel("nested sentinel w/o handler") sentinel.Observe(observedSentinel) observedSentinel.Observe(loopC) observedSentinel.Observe(loopD) } //-------------------- // HELPERS //-------------------- // channelBundle enables communication from and to loop functions. type channelBundle struct { startedc chan interface{} donec chan interface{} stopc chan interface{} errorc chan interface{} } func newChannelBundle() *channelBundle { return &channelBundle{ startedc: audit.MakeSigChan(), donec: audit.MakeSigChan(), stopc: audit.MakeSigChan(), errorc: audit.MakeSigChan(), } } // makeSimpleLF creates a loop function doing nothing special. func makeSimpleLF(bundle *channelBundle) loop.LoopFunc { return func(l loop.Loop) error { defer func() { bundle.donec <- true }() bundle.startedc <- true for { select { case <-l.ShallStop(): return nil } } } } // makeErrorLF creates a loop function stopping with // an error after receiving a signal. func makeErrorLF(bundle *channelBundle) loop.LoopFunc { return func(l loop.Loop) error { defer func() { bundle.donec <- true }() bundle.startedc <- true for { select { case <-l.ShallStop(): return nil case <-bundle.errorc: return errors.New("internal loop error") } } } } // makeDeferredErrorLF creates a loop function returning // an error in its deferred function. func makeDeferredErrorLF(bundle *channelBundle) loop.LoopFunc { return func(l loop.Loop) (err error) { defer func() { bundle.donec <- true }() defer func() { err = errors.New("deferred error") }() bundle.startedc <- true for { select { case <-l.ShallStop(): return nil } } } } // makeRecoverPanicLF creates a loop function having a panic // but getting recovered. func makeRecoverPanicLF(bundle *channelBundle) loop.LoopFunc { return func(l loop.Loop) error { bundle.startedc <- true for { select { case <-l.ShallStop(): return nil case <-bundle.errorc: panic("panic") } } } } // makeRecoverErrorLF creates a loop function having an error // but getting recovered. func makeRecoverErrorLF(bundle *channelBundle) loop.LoopFunc { return func(l loop.Loop) error { bundle.startedc <- true for { select { case <-l.ShallStop(): return nil case <-bundle.errorc: return errors.New("error") } } } } // makeRecoverNoErrorLF creates a loop function stopping // without an error so it won't be recovered. func makeRecoverNoErrorLF(bundle *channelBundle) loop.LoopFunc { return func(l loop.Loop) error { bundle.startedc <- true <-bundle.stopc bundle.donec <- true return nil } } // makeCheckCountRF creates a recover function stopping // recovering after 5 calls. func makeCheckCountRF(bundle *channelBundle) loop.RecoverFunc { return func(rs loop.Recoverings) (loop.Recoverings, error) { bundle.startedc <- true if len(rs) >= 5 { bundle.donec <- len(rs) return nil, errors.New("too many panics") } return rs, nil } } // makeCatchErrorRF creates a recover function stopping // recovering in case of the error "error". func makeCatchErrorRF(bundle *channelBundle) loop.RecoverFunc { return func(rs loop.Recoverings) (loop.Recoverings, error) { if len(rs) > 0 { if err, ok := rs.Last().Reason.(error); ok { if err.Error() == "error" { bundle.donec <- "error" return nil, err } } } return nil, nil } } // makeIgnorePanicsRF creates a recover function always // recovering the paniced loop function. func makeIgnorePanicsRF(bundle *channelBundle) loop.RecoverFunc { return func(rs loop.Recoverings) (loop.Recoverings, error) { bundle.donec <- "recovered" return nil, nil } } // EOF golib-4.24.2/loop/sentinel.go000066400000000000000000000121251315505703200160220ustar00rootroot00000000000000// Tideland Go Application Support - Loop // // Copyright (C) 2013-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package loop //-------------------- // IMPORTS //-------------------- import ( "sync" "time" "github.com/tideland/golib/errors" "github.com/tideland/golib/logger" ) //-------------------- // SENTINEL //-------------------- // NotificationHandlerFunc allows a sentinel to react on // an observers error notification. type NotificationHandlerFunc func(s Sentinel, o Observable, rs Recoverings) (Recoverings, error) // Sentinel manages a number of loops or other sentinels. type Sentinel interface { Observable // Observe tells the sentinel to monitor the passed observables. Observe(o ...Observable) // Forget tells the sentinel to forget the passed observables. Forget(o ...Observable) // ObservablesDo executes the passed function for each observable, // e.g. to react after an error. ObservablesDo(f func(o Observable) error) error } type observableChange struct { observables []Observable doneC chan struct{} } // sentinel implements the Sentinel interface. type sentinel struct { mux sync.Mutex descr string loop *loop handlerF NotificationHandlerFunc observables map[Observable]struct{} addC chan *observableChange removeC chan *observableChange notifyC chan Observable } // goSentinel starts a new sentinel. func goSentinel(nhf NotificationHandlerFunc, ps *sentinel, d string) *sentinel { s := &sentinel{ descr: d, handlerF: nhf, observables: make(map[Observable]struct{}), addC: make(chan *observableChange), removeC: make(chan *observableChange), notifyC: make(chan Observable), } s.loop = goLoop(s.backendLoop, nil, s, ps, d) return s } // String implements the Stringer interface. It returns // the description of the sentinel. func (s *sentinel) String() string { return s.descr } // Stop implements the Observable interface. func (s *sentinel) Stop() error { return s.loop.Stop() } // Kill implements the Observable interface. func (s *sentinel) Kill(err error) { s.loop.Kill(err) } // Wait implements the Observable interface. func (s *sentinel) Wait() error { return s.loop.Wait() } // Error implements the Observable interface. func (s *sentinel) Error() (int, error) { return s.loop.Error() } // Restart implements the Observable interface. func (s *sentinel) Restart() error { logger.Infof("sentinel %q restarts", s) // Start backendLoop again. s.loop.Restart() // Now restart children. return s.ObservablesDo(func(o Observable) error { return o.Restart() }) } // attachSentinel implements the Observable interface. func (s *sentinel) attachSentinel(ps *sentinel) { s.loop.attachSentinel(ps) } // Observe implements the Sentinel interface. func (s *sentinel) Observe(os ...Observable) { change := &observableChange{ observables: os, doneC: make(chan struct{}), } s.addC <- change <-change.doneC } // Forget implements the Sentinel interface. func (s *sentinel) Forget(os ...Observable) { change := &observableChange{ observables: os, doneC: make(chan struct{}), } s.removeC <- change <-change.doneC } // ObservablesDo implements the Sentinel interface. func (s *sentinel) ObservablesDo(f func(o Observable) error) error { s.mux.Lock() defer s.mux.Unlock() var errs []error for observable := range s.observables { if err := f(observable); err != nil { errs = append(errs, err) } } if len(errs) > 0 { return errors.Collect(errs...) } return nil } // backendLoop listens to ending managed loops. func (s *sentinel) backendLoop(l Loop) error { var recoverings Recoverings for { select { case <-l.ShallStop(): // We're done. return s.ObservablesDo(func(o Observable) error { logger.Infof("sentinel %q stops observable %q", s, o) o.Stop() return nil }) case change := <-s.addC: // Add new observables. for _, o := range change.observables { s.observables[o] = struct{}{} o.attachSentinel(s) logger.Infof("started observing %q", o) } close(change.doneC) case change := <-s.removeC: // Remove observable. for _, o := range change.observables { delete(s.observables, o) logger.Infof("stopped observing %q", o) } close(change.doneC) case o := <-s.notifyC: _, err := o.Error() // First check if my own loop has troubles. if o == s { return err } // Receive notification about observable // with error. if s.handlerF != nil { // Try to handle the notification. recoverings = append(recoverings, &Recovering{time.Now(), err}) recoverings, err = s.handlerF(s, o, recoverings) } if err != nil { // Still an error, so kill all. logger.Errorf("sentinel %q kills all observables after error: %v", s, err) s.ObservablesDo(func(o Observable) error { logger.Errorf("killing %q", o) o.Kill(errors.Annotate(err, ErrKilledBySentinel, errorMessages, o)) return o.Wait() }) return errors.Annotate(err, ErrHandlingFailed, errorMessages, o) } } } } // EOF golib-4.24.2/mapreduce/000077500000000000000000000000001315505703200146455ustar00rootroot00000000000000golib-4.24.2/mapreduce/doc.go000066400000000000000000000011631315505703200157420ustar00rootroot00000000000000// Tideland Go Library - Map/Reduce // // Copyright (C) 2009-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. // Package mapreduce of the Tideland Go Library implements the Map/Reduce // algorithm for the processing and aggregation mass data. // // A type implementing the MapReducer interface has to be implemented // and passed to the MapReduce() function. The type is responsible // for the input, the mapping, the reducing and the consuming while // the package provides the runtime environment for it. package mapreduce // EOF golib-4.24.2/mapreduce/mapreduce.go000066400000000000000000000067311315505703200171500ustar00rootroot00000000000000// Tideland Go Library - Map/Reduce // // Copyright (C) 2009-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package mapreduce //-------------------- // IMPORTS //-------------------- import ( "hash/adler32" "runtime" ) //-------------------- // KEY/VALUE //-------------------- // KeyValue is a pair of a string key and any data as value. type KeyValue interface { // Key returns the key for the mapping. Key() string // Value returns the payload value for processing. Value() interface{} } // KeyValueChan is a channel for the transfer of key/value pairs. type KeyValueChan chan KeyValue // Close the channel for key/value pairs. func (c KeyValueChan) Close() { close(c) } //-------------------- // MAP/REDUCE //-------------------- // MapReducer has to be implemented to control the map/reducing. type MapReducer interface { // Input has to return the input channel for the // date to process. Input() KeyValueChan // Map maps a key/value pair to another one and emits it. Map(in KeyValue, emit KeyValueChan) // Reduce reduces the values delivered via the input // channel to the emit channel. Reduce(in, emit KeyValueChan) // Consume allows the MapReducer to consume the // processed data. Consume(in KeyValueChan) error } // MapReduce applies a map and a reduce function to keys and values in parallel. func MapReduce(mr MapReducer) error { mapEmitChan := make(KeyValueChan) reduceEmitChan := make(KeyValueChan) go performReducing(mr, mapEmitChan, reduceEmitChan) go performMapping(mr, mapEmitChan) return mr.Consume(reduceEmitChan) } //-------------------- // PRIVATE //-------------------- // closerChan signals the closing of channels. type closerChan chan struct{} // closerChan closes given channel after a number of signals. func newCloserChan(kvc KeyValueChan, size int) closerChan { signals := make(closerChan) go func() { ctr := 0 for { <-signals ctr++ if ctr == size { kvc.Close() close(signals) return } } }() return signals } // performReducing runs the reducing goroutines. func performReducing(mr MapReducer, mapEmitChan, reduceEmitChan KeyValueChan) { // Start a closer for the reduce emit chan. size := runtime.NumCPU() signals := newCloserChan(reduceEmitChan, size) // Start reduce goroutines. reduceChans := make([]KeyValueChan, size) for i := 0; i < size; i++ { reduceChans[i] = make(KeyValueChan) go func(in KeyValueChan) { mr.Reduce(in, reduceEmitChan) signals <- struct{}{} }(reduceChans[i]) } // Read map emitted data. for kv := range mapEmitChan { hash := adler32.Checksum([]byte(kv.Key())) idx := hash % uint32(size) reduceChans[idx] <- kv } // Close reduce channels. for _, reduceChan := range reduceChans { reduceChan.Close() } } // Perform the mapping. func performMapping(mr MapReducer, mapEmitChan KeyValueChan) { // Start a closer for the map emit chan. size := runtime.NumCPU() * 4 signals := newCloserChan(mapEmitChan, size) // Start map goroutines. mapChans := make([]KeyValueChan, size) for i := 0; i < size; i++ { mapChans[i] = make(KeyValueChan) go func(in KeyValueChan) { for kv := range in { mr.Map(kv, mapEmitChan) } signals <- struct{}{} }(mapChans[i]) } // Dispatch input data to map channels. idx := 0 for kv := range mr.Input() { mapChans[idx%size] <- kv idx++ } // Close map channels. for i := 0; i < size; i++ { mapChans[i].Close() } } // EOF golib-4.24.2/mapreduce/mapreduce_test.go000066400000000000000000000130631315505703200202030ustar00rootroot00000000000000// Tideland Go Library - Map/Reduce - Unit Tests // // Copyright (C) 2009-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package mapreduce_test //-------------------- // IMPORTS //-------------------- import ( "fmt" "math/rand" "strconv" "testing" "github.com/tideland/golib/audit" "github.com/tideland/golib/identifier" "github.com/tideland/golib/mapreduce" ) //-------------------- // TESTS //-------------------- // Test the MapReduce function with a scenario, where orders are analyzed // and a list of the analyzed articles will be returned func TestMapReduce(t *testing.T) { assert := audit.NewTestingAssertion(t, true) // Create MapReducer and let the show begin. mr := &OrderMapReducer{200000, make(map[int][]*OrderItem), make(map[string]*OrderItemAnalysis), assert} err := mapreduce.MapReduce(mr) // Asserts. assert.Nil(err) assert.Equal(len(mr.items), len(mr.analyses), "all items are analyzed") for _, analysis := range mr.analyses { quantity := 0 amount := 0.0 discount := 0.0 items := mr.items[analysis.ArticleNo] for _, item := range items { unitDiscount := (item.UnitPrice / 100.0) * item.DiscountPerc totalDiscount := unitDiscount * float64(item.Count) totalAmount := (item.UnitPrice - unitDiscount) * float64(item.Count) quantity += item.Count amount += totalAmount discount += totalDiscount } assert.Equal(quantity, analysis.Quantity, "quantity per article") assert.About(amount, analysis.Amount, 0.01, "amount per article") assert.About(discount, analysis.Discount, 0.01, "discount per article") } } // Benchmark the MapReduce function. func BenchmarkMapReduce(b *testing.B) { assert := audit.NewPanicAssertion() mr := &OrderMapReducer{b.N, make(map[int][]*OrderItem), make(map[string]*OrderItemAnalysis), assert} mapreduce.MapReduce(mr) } //-------------------- // HELPERS //-------------------- type OrderMapReducer struct { count int items map[int][]*OrderItem analyses map[string]*OrderItemAnalysis assert audit.Assertion } // Input has to return the input channel for the // date to process. func (o *OrderMapReducer) Input() mapreduce.KeyValueChan { input := make(mapreduce.KeyValueChan) // Generate test orders and push them into a the // input channel. articleMaxNo := 9999 unitPrices := make([]float64, articleMaxNo+1) for i := 0; i < articleMaxNo+1; i++ { unitPrices[i] = rand.Float64() * 100.0 } go func() { defer close(input) for i := 0; i < o.count; i++ { order := &Order{ OrderNo: identifier.NewUUID(), CustomerNo: rand.Intn(999) + 1, Items: make([]*OrderItem, rand.Intn(9)+1), } for j := 0; j < len(order.Items); j++ { articleNo := rand.Intn(articleMaxNo) order.Items[j] = &OrderItem{ ArticleNo: articleNo, Count: rand.Intn(9) + 1, UnitPrice: unitPrices[articleNo], DiscountPerc: rand.Float64() * 4.0, } o.items[articleNo] = append(o.items[articleNo], order.Items[j]) } input <- order } }() return input } // Map maps a key/value pair to another one and emits it. func (o *OrderMapReducer) Map(in mapreduce.KeyValue, emit mapreduce.KeyValueChan) { order := in.Value().(*Order) // Analyse items and emit results. for _, item := range order.Items { unitDiscount := (item.UnitPrice / 100.0) * item.DiscountPerc totalDiscount := unitDiscount * float64(item.Count) totalAmount := (item.UnitPrice - unitDiscount) * float64(item.Count) analysis := &OrderItemAnalysis{item.ArticleNo, item.Count, totalAmount, totalDiscount} emit <- analysis } } // Reduce reduces the values delivered via the input // channel to the emit channel. func (o *OrderMapReducer) Reduce(in, emit mapreduce.KeyValueChan) { memory := make(map[string]*OrderItemAnalysis) // Collect emitted analysis data. for kv := range in { analysis := kv.Value().(*OrderItemAnalysis) if existing, ok := memory[kv.Key()]; ok { existing.Quantity += analysis.Quantity existing.Amount += analysis.Amount existing.Discount += analysis.Discount } else { memory[kv.Key()] = analysis } } // Emit the result to the consumer. for _, analysis := range memory { emit <- analysis } } // Consume allows the MapReducer to consume the // processed data. func (o *OrderMapReducer) Consume(in mapreduce.KeyValueChan) error { for kv := range in { o.assert.Nil(o.analyses[kv.Key()], "each analysis only once") o.analyses[kv.Key()] = kv.Value().(*OrderItemAnalysis) } return nil } // Order item type. type OrderItem struct { ArticleNo int Count int UnitPrice float64 DiscountPerc float64 } // Order type. type Order struct { OrderNo identifier.UUID CustomerNo int Items []*OrderItem } // Key returns the order number as key. func (o *Order) Key() string { return o.OrderNo.String() } // Value returns the order itself. func (o *Order) Value() interface{} { return o } func (o *Order) String() string { msg := "ON: %v / CN: %4v / I: %v" return fmt.Sprintf(msg, o.OrderNo, o.CustomerNo, len(o.Items)) } // Order item analysis type. type OrderItemAnalysis struct { ArticleNo int Quantity int Amount float64 Discount float64 } // Key returns the article number as key. func (a *OrderItemAnalysis) Key() string { return strconv.Itoa(a.ArticleNo) } // Value returns the analysis itself. func (a *OrderItemAnalysis) Value() interface{} { return a } func (oia *OrderItemAnalysis) String() string { msg := "AN: %5v / Q: %4v / A: %10.2f € / D: %10.2f €" return fmt.Sprintf(msg, oia.ArticleNo, oia.Quantity, oia.Amount, oia.Discount) } // EOF golib-4.24.2/monitoring/000077500000000000000000000000001315505703200150655ustar00rootroot00000000000000golib-4.24.2/monitoring/doc.go000066400000000000000000000014451315505703200161650ustar00rootroot00000000000000// Tideland Go Library - Monitoring // // Copyright (C) 2009-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. // Package monitoring of the Tideland Go Library supports three kinds of // system monitoring. They are helpful to understand what's happening // inside a system during runtime. So execution times can be measured // and analyzed, stay-set variables integrated and dynamic control // value retrieval provided. The backend is exchangeable. So the // StandardBackend workes like described above, the NullBackend does // nothing, and own implementations can integrate external systems. // Additionally filters can be added to reduce the monitoring to // the points of interest. package monitoring // EOF golib-4.24.2/monitoring/errors.go000066400000000000000000000044171315505703200167360ustar00rootroot00000000000000// Tideland Go Library - Monitoring // // Copyright (C) 2009-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package monitoring //-------------------- // IMPORTS //-------------------- import ( "github.com/tideland/golib/errors" ) //-------------------- // CONSTANTS //-------------------- // Error codes of the monitoring package. const ( ErrMonitoringPanicked = iota + 1 ErrMonitoringCannotBeRecovered ErrMeasuringPointNotExists ErrStaySetVariableNotExists ErrDynamicStatusNotExists ) var errorMessages = errors.Messages{ ErrMonitoringPanicked: "monitoring backend panicked", ErrMonitoringCannotBeRecovered: "monitoring backend cannot be recovered: %v", ErrMeasuringPointNotExists: "measuring point %q does not exist", ErrStaySetVariableNotExists: "stay-set variable %q does not exist", ErrDynamicStatusNotExists: "dynamic status %q does not exist", } //-------------------- // TESTING //-------------------- // IsMonitoringPanickedError returns true, if the error signals that // the monitoring backend panicked. func IsMonitoringPanickedError(err error) bool { return errors.IsError(err, ErrMonitoringPanicked) } // IsMonitoringCannotBeRecoveredError returns true, if the error signals that // the monitoring backend has panicked to often and cannot be recovered. func IsMonitoringCannotBeRecoveredError(err error) bool { return errors.IsError(err, ErrMonitoringCannotBeRecovered) } // IsMeasuringPointNotExistsError returns true, if the error signals that // a wanted measuring point cannot be retrieved because it doesn't exists. func IsMeasuringPointNotExistsError(err error) bool { return errors.IsError(err, ErrMeasuringPointNotExists) } // IsStaySetVariableNotExistsError returns true, if the error signals that // a wanted stay-set variable cannot be retrieved because it doesn't exists. func IsStaySetVariableNotExistsError(err error) bool { return errors.IsError(err, ErrStaySetVariableNotExists) } // IsDynamicStatusNotExistsError returns true, if the error signals that // a wanted dynamic status cannot be retrieved because it doesn't exists. func IsDynamicStatusNotExistsError(err error) bool { return errors.IsError(err, ErrDynamicStatusNotExists) } // EOF golib-4.24.2/monitoring/monitoring.go000066400000000000000000000303511315505703200176030ustar00rootroot00000000000000// Tideland Go Library - Monitoring // // Copyright (C) 2009-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package monitoring //-------------------- // IMPORTS //-------------------- import ( "fmt" "io" "os" "sync" "time" ) //-------------------- // CONSTANTS //-------------------- const ( etmTLine = "+----------------------------------------------------+------------+--------------------+--------------------+--------------------+\n" etmHeader = "| Measuring Point Name | Count | Min Dur | Max Dur | Avg Dur |\n" etmFormat = "| %-50s | %10d | %18s | %18s | %18s |\n" ssvTLine = "+----------------------------------------------------+-----------+---------------+---------------+---------------+---------------+\n" ssvHeader = "| Stay-Set Variable Name | Count | Act Value | Min Value | Max Value | Avg Value |\n" ssvFormat = "| %-50s | %9d | %13d | %13d | %13d | %13d |\n" dsrTLine = "+----------------------------------------------------+---------------------------------------------------------------------------+\n" dsrHeader = "| Dynamic Status | Value |\n" dsrFormat = "| %-50s | %-73s |\n" ) //-------------------- // INTERFACES //-------------------- // IDFilter allows to add filter for execution time measurings, // stay-set values, and dynamic status retriever. If set only // monitorings with the filter returning true will be done. type IDFilter func(id string) bool // Measuring defines one execution time measuring containing the ID and // the starting time of the measuring and able to pass this data after // the end of the measuring to its backend. type Measuring interface { // EndMeasuring ends the measuring and passes its // data to the backend. EndMeasuring() time.Duration } // MeasuringPoint defines the collected information for one execution // time measuring point. type MeasuringPoint interface { fmt.Stringer // ID returns the identifier of the measuring point. ID() string // Count returns how often this point has been measured. Count() int64 // MinDuration returns the shortest execution time. MinDuration() time.Duration // MaxDuration returns the longest execution time. MaxDuration() time.Duration // AvgDuration returns the average execution time. AvgDuration() time.Duration } // MeasuringPoints is a set of measuring points. type MeasuringPoints []MeasuringPoint // Implement the sort interface. func (m MeasuringPoints) Len() int { return len(m) } func (m MeasuringPoints) Swap(i, j int) { m[i], m[j] = m[j], m[i] } func (m MeasuringPoints) Less(i, j int) bool { return m[i].ID() < m[j].ID() } // StaySetVariable contains the cumulated values // for one stay-set variable. type StaySetVariable interface { fmt.Stringer // ID returns the identifier of the stay-set variable. ID() string // Count returns how often the value has been changed. Count() int64 // ActValue returns the current value of the variable. ActValue() int64 // MinValue returns the minimum value of the variable. MinValue() int64 // MaxValue returns the maximum value of the variable. MaxValue() int64 // MinValue returns the average value of the variable. AvgValue() int64 } // StaySetVariables is a set of stay-set variables. type StaySetVariables []StaySetVariable // Implement the sort interface. func (s StaySetVariables) Len() int { return len(s) } func (s StaySetVariables) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func (s StaySetVariables) Less(i, j int) bool { return s[i].ID() < s[j].ID() } // DynamicStatusRetriever is called by the server and // returns a current status as string. type DynamicStatusRetriever func() (string, error) // DynamicStatusValue contains one retrieved value. type DynamicStatusValue interface { fmt.Stringer // ID returns the identifier of the status value. ID() string // Value returns the retrieved value as string. Value() string } // DynamicStatusValues is a set of dynamic status values. type DynamicStatusValues []DynamicStatusValue // Implement the sort interface. func (d DynamicStatusValues) Len() int { return len(d) } func (d DynamicStatusValues) Swap(i, j int) { d[i], d[j] = d[j], d[i] } func (d DynamicStatusValues) Less(i, j int) bool { return d[i].ID() < d[j].ID() } // Backend defines the interface for a type managing all // the information provided or needed by the public functions // of the monitoring package. type Backend interface { // BeginMeasuring starts a new measuring with a given id. BeginMeasuring(id string) Measuring // ReadMeasuringPoint returns the measuring point for an id. ReadMeasuringPoint(id string) (MeasuringPoint, error) // MeasuringPointsDo performs the function f for // all measuring points. MeasuringPointsDo(f func(MeasuringPoint)) error // SetVariable sets a value of a stay-set variable. SetVariable(id string, v int64) // IncrVariable increases a variable. IncrVariable(id string) // DecrVariable decreases a variable. DecrVariable(id string) // ReadVariable returns the stay-set variable for an id. ReadVariable(id string) (StaySetVariable, error) // StaySetVariablesDo performs the function f for all // variables. StaySetVariablesDo(f func(StaySetVariable)) error // Register registers a new dynamic status retriever function. Register(id string, rf DynamicStatusRetriever) // ReadStatus returns the dynamic status for an id. ReadStatus(id string) (string, error) // DynamicStatusValuesDo performs the function f for all // status values. DynamicStatusValuesDo(f func(DynamicStatusValue)) error // SetMeasuringFilter sets the new filter for measurings // and returns the current one. SetMeasuringsFilter(f IDFilter) IDFilter // SetMeasuringFilter sets the new filter for variables // and returns the current one. SetVariablesFilter(f IDFilter) IDFilter // SetRetrieversFilter sets the new filter for status retrievers // and returns the current one. SetRetrieversFilter(f IDFilter) IDFilter // Reset clears all monitored values. Reset() error // Stop tells the backend that a new one has been set. Stop() } //-------------------- // MONITORING API //-------------------- // monitoring manages the global monitor. type monitoring struct { sync.RWMutex b Backend } // backend ensures, that a backend is set. By default // it's the standard one. func (m *monitoring) backend() Backend { if m.b == nil { m.b = NewStandardBackend() } return m.b } // setBackend sets the current backend. If one // is already set it will be stopped. func (m *monitoring) setBackend(mb Backend) { if m.b != nil { m.b.Stop() } m.b = mb } // monitor is the global monitor. var monitor = &monitoring{} // SetBackend allows to switch the monitoring backend. func SetBackend(mb Backend) { monitor.Lock() defer monitor.Unlock() monitor.setBackend(mb) } // BeginMeasuring starts a new measuring with a given id. // All measurings with the same id will be aggregated. func BeginMeasuring(id string) Measuring { monitor.RLock() defer monitor.RUnlock() return monitor.backend().BeginMeasuring(id) } // Measure the execution of a function. func Measure(id string, f func()) time.Duration { m := BeginMeasuring(id) f() return m.EndMeasuring() } // ReadMeasuringPoint returns the measuring point for an id. func ReadMeasuringPoint(id string) (MeasuringPoint, error) { monitor.RLock() defer monitor.RUnlock() return monitor.backend().ReadMeasuringPoint(id) } // MeasuringPointsDo performs the function f for // all measuring points. func MeasuringPointsDo(f func(MeasuringPoint)) error { monitor.RLock() defer monitor.RUnlock() return monitor.backend().MeasuringPointsDo(f) } // MeasuringPointsWrite prints the measuring points for which // the passed function returns true to the passed writer. func MeasuringPointsWrite(w io.Writer, ff func(MeasuringPoint) bool) error { fmt.Fprint(w, etmTLine) fmt.Fprint(w, etmHeader) fmt.Fprint(w, etmTLine) if err := MeasuringPointsDo(func(mp MeasuringPoint) { if ff(mp) { fmt.Fprintf(w, etmFormat, mp.ID(), mp.Count(), mp.MinDuration(), mp.MaxDuration(), mp.AvgDuration()) } }); err != nil { return err } fmt.Fprint(w, etmTLine) return nil } // MeasuringPointsPrintAll prints all measuring points // to STDOUT. func MeasuringPointsPrintAll() error { return MeasuringPointsWrite(os.Stdout, func(mp MeasuringPoint) bool { return true }) } // SetVariable sets a value of a stay-set variable. func SetVariable(id string, v int64) { monitor.RLock() defer monitor.RUnlock() monitor.backend().SetVariable(id, v) } // IncrVariable increases a stay-set variable. func IncrVariable(id string) { monitor.RLock() defer monitor.RUnlock() monitor.backend().IncrVariable(id) } // DecrVariable decreases a stay-set variable. func DecrVariable(id string) { monitor.RLock() defer monitor.RUnlock() monitor.backend().DecrVariable(id) } // ReadVariable returns the stay-set variable for an id. func ReadVariable(id string) (StaySetVariable, error) { monitor.RLock() defer monitor.RUnlock() return monitor.backend().ReadVariable(id) } // StaySetVariablesDo performs the function f for all // variables. func StaySetVariablesDo(f func(StaySetVariable)) error { monitor.RLock() defer monitor.RUnlock() return monitor.backend().StaySetVariablesDo(f) } // StaySetVariablesWrite prints the stay-set variables for which // the passed function returns true to the passed writer. func StaySetVariablesWrite(w io.Writer, ff func(StaySetVariable) bool) error { fmt.Fprint(w, ssvTLine) fmt.Fprint(w, ssvHeader) fmt.Fprint(w, ssvTLine) if err := StaySetVariablesDo(func(ssv StaySetVariable) { if ff(ssv) { fmt.Fprintf(w, ssvFormat, ssv.ID(), ssv.Count(), ssv.ActValue(), ssv.MinValue(), ssv.MaxValue(), ssv.AvgValue()) } }); err != nil { return err } fmt.Fprint(w, ssvTLine) return nil } // StaySetVariablesPrintAll prints all stay-set variables // to STDOUT. func StaySetVariablesPrintAll() error { return StaySetVariablesWrite(os.Stdout, func(ssv StaySetVariable) bool { return true }) } // Register registers a new dynamic status retriever function. func Register(id string, rf DynamicStatusRetriever) { monitor.RLock() defer monitor.RUnlock() monitor.backend().Register(id, rf) } // ReadStatus returns the dynamic status for an id. func ReadStatus(id string) (string, error) { monitor.RLock() defer monitor.RUnlock() return monitor.backend().ReadStatus(id) } // DynamicStatusValuesDo performs the function f for all // status values. func DynamicStatusValuesDo(f func(DynamicStatusValue)) error { monitor.RLock() defer monitor.RUnlock() return monitor.backend().DynamicStatusValuesDo(f) } // DynamicStatusValuesWrite prints the status values for which // the passed function returns true to the passed writer. func DynamicStatusValuesWrite(w io.Writer, ff func(DynamicStatusValue) bool) error { fmt.Fprint(w, dsrTLine) fmt.Fprint(w, dsrHeader) fmt.Fprint(w, dsrTLine) if err := DynamicStatusValuesDo(func(dsv DynamicStatusValue) { if ff(dsv) { fmt.Fprintf(w, dsrFormat, dsv.ID(), dsv.Value()) } }); err != nil { return err } fmt.Fprint(w, dsrTLine) return nil } // DynamicStatusValuesPrintAll prints all status values to STDOUT. func DynamicStatusValuesPrintAll() error { return DynamicStatusValuesWrite(os.Stdout, func(dsv DynamicStatusValue) bool { return true }) } // SetMeasuringsFilter sets the new filter for measurings // and returns the current one. func SetMeasuringsFilter(f IDFilter) IDFilter { monitor.RLock() defer monitor.RUnlock() return monitor.backend().SetMeasuringsFilter(f) } // SetVariablesFilter sets the new filter for variables // and returns the current one. func SetVariablesFilter(f IDFilter) IDFilter { monitor.RLock() defer monitor.RUnlock() return monitor.backend().SetVariablesFilter(f) } // SetRetrieversFilter sets the new filter for status retrievers // and returns the current one. func SetRetrieversFilter(f IDFilter) IDFilter { monitor.RLock() defer monitor.RUnlock() return monitor.backend().SetRetrieversFilter(f) } // Reset clears all monitored values. func Reset() error { monitor.RLock() defer monitor.RUnlock() return monitor.backend().Reset() } // EOF golib-4.24.2/monitoring/monitoring_test.go000066400000000000000000000153771315505703200206550ustar00rootroot00000000000000// Tideland Go Library - Monitoring - Unit Tests // // Copyright (C) 2009-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package monitoring_test //-------------------- // IMPORTS //-------------------- import ( "fmt" "math/rand" "testing" "time" "github.com/tideland/golib/audit" "github.com/tideland/golib/monitoring" ) //-------------------- // TESTS //-------------------- // Test of the ETM monitor. func TestETMMonitor(t *testing.T) { assert := audit.NewTestingAssertion(t, true) monitoring.SetBackend(monitoring.NewStandardBackend()) // Generate measurings. for i := 0; i < 500; i++ { n := rand.Intn(10) id := fmt.Sprintf("mp:task:%d", n) m := monitoring.BeginMeasuring(id) work(n * 5000) m.EndMeasuring() } // Need some time to let that backend catch up queued mesurings. time.Sleep(time.Millisecond) // Asserts. mp, err := monitoring.ReadMeasuringPoint("foo") assert.ErrorMatch(err, `.* measuring point "foo" does not exist`) mp, err = monitoring.ReadMeasuringPoint("mp:task:5") assert.Nil(err, "No error expected.") assert.Equal(mp.ID(), "mp:task:5", "should get the right one") assert.True(mp.Count() > 0, "should be measured several times") assert.Match(mp.String(), `Measuring Point "mp:task:5" \(.*\)`, "string representation should look fine") monitoring.MeasuringPointsDo(func(mp monitoring.MeasuringPoint) { assert.Match(mp.ID(), "mp:task:[0-9]", "id has to match the pattern") assert.True(mp.MinDuration() <= mp.AvgDuration() && mp.AvgDuration() <= mp.MaxDuration(), "avg should be somewhere between min and max") }) } // Test of the SSI monitor. func TestSSIMonitor(t *testing.T) { assert := audit.NewTestingAssertion(t, true) monitoring.SetBackend(monitoring.NewStandardBackend()) // Generate values. for i := 0; i < 500; i++ { n := rand.Intn(10) id := fmt.Sprintf("ssv:value:%d", n) monitoring.SetVariable(id, rand.Int63n(2001)-1000) } // Need some time to let that backend catch up queued mesurings. time.Sleep(time.Millisecond) // Asserts. ssv, err := monitoring.ReadVariable("foo") assert.ErrorMatch(err, `.* stay-set variable "foo" does not exist`) ssv, err = monitoring.ReadVariable("ssv:value:5") assert.Nil(err, "no error expected") assert.Equal(ssv.ID(), "ssv:value:5", "should get the right one") assert.True(ssv.Count() > 0, "should be set several times") assert.Match(ssv.String(), `Stay-Set Variable "ssv:value:5" (.*)`) monitoring.StaySetVariablesDo(func(ssv monitoring.StaySetVariable) { assert.Match(ssv.ID(), "ssv:value:[0-9]", "id has to match the pattern") assert.True(ssv.MinValue() <= ssv.AvgValue() && ssv.AvgValue() <= ssv.MaxValue(), "avg should be somewhere between min and max") }) } // Test of the DSR monitor. func TestDSRMonitor(t *testing.T) { assert := audit.NewTestingAssertion(t, true) monitoring.SetBackend(monitoring.NewStandardBackend()) // Register monitoring funcs. monitoring.Register("dsr:a", func() (string, error) { return "A", nil }) monitoring.Register("dsr:b", func() (string, error) { return "4711", nil }) monitoring.Register("dsr:c", func() (string, error) { return "2012-02-15", nil }) monitoring.Register("dsr:d", func() (string, error) { a := 1; a = a / (a - a); return fmt.Sprintf("%d", a), nil }) // Need some time to let that backend catch up queued registerings. time.Sleep(time.Millisecond) // Asserts. dsv, err := monitoring.ReadStatus("foo") assert.ErrorMatch(err, `.* dynamic status "foo" does not exist`) dsv, err = monitoring.ReadStatus("dsr:b") assert.Nil(err, "no error expected") assert.Equal(dsv, "4711", "status value should be correct") _, err = monitoring.ReadStatus("dsr:d") assert.NotNil(err, "error should be returned") assert.ErrorMatch(err, `.* monitoring backend panicked`) } // TestStandardInternalPanic tests the clean handling of panics // when retrieving a status with the standard backend. func TestInternalPanic(t *testing.T) { assert := audit.NewTestingAssertion(t, true) monitoring.SetBackend(monitoring.NewStandardBackend()) // Register monitoring func with panic. monitoring.Register("panic", func() (string, error) { panic("ouch") }) // Need some time to let that backend catch up queued registering. time.Sleep(time.Millisecond) // Asserts. status, err := monitoring.ReadStatus("panic") assert.Empty(status, "no dynamic status value") assert.ErrorMatch(err, `.* monitoring backend panicked`) } // TestBackendSwitch tests the correct switching between backends. func TestBackendSwitch(t *testing.T) { assert := audit.NewTestingAssertion(t, true) sleep := 10 * time.Millisecond // First standard. monitoring.SetBackend(monitoring.NewStandardBackend()) monitoring.Measure("test-a", func() { time.Sleep(sleep) }) time.Sleep(sleep) mp, err := monitoring.ReadMeasuringPoint("test-a") assert.Nil(err) assert.Equal(mp.Count(), int64(1)) assert.True(sleep <= mp.AvgDuration() && mp.AvgDuration() <= 2*sleep) // Then null. monitoring.SetBackend(monitoring.NewNullBackend()) monitoring.Measure("test", func() { time.Sleep(sleep) }) mp, err = monitoring.ReadMeasuringPoint("test") assert.Nil(err) assert.Equal(mp.ID(), "null") assert.Equal(mp.Count(), int64(0)) // Finally standard again. monitoring.SetBackend(monitoring.NewStandardBackend()) monitoring.Measure("test-b", func() { time.Sleep(sleep) }) time.Sleep(sleep) mp, err = monitoring.ReadMeasuringPoint("test-a") assert.ErrorMatch(err, `.* measuring point "test-a" does not exist`) mp, err = monitoring.ReadMeasuringPoint("test-b") assert.Nil(err) assert.Equal(mp.Count(), int64(1)) assert.True(sleep <= mp.AvgDuration() && mp.AvgDuration() <= 2*sleep) } //-------------------- // BENCHMARKS //-------------------- // BenchmarkStandardBackendETM checks the performance of the ETM of the // standard backend. func BenchmarkStandardBackendETM(b *testing.B) { monitoring.SetBackend(monitoring.NewStandardBackend()) for i := 0; i < b.N; i++ { monitoring.Measure("test", func() {}) } } // BenchmarkFilteredStandardBackendETM checks the performance of the ETM // of the standard backend when filtered. func BenchmarkFilteredStandardBackendETM(b *testing.B) { monitoring.SetBackend(monitoring.NewStandardBackend()) monitoring.SetMeasuringsFilter(func(id string) bool { return id != "test" }) defer monitoring.SetMeasuringsFilter(nil) for i := 0; i < b.N; i++ { monitoring.Measure("test", func() {}) } } // BenchmarkNullBackendETM checks the performance of the ETM of the // null backend. func BenchmarkNullBackendETM(b *testing.B) { monitoring.SetBackend(monitoring.NewNullBackend()) for i := 0; i < b.N; i++ { monitoring.Measure("test", func() {}) } } //-------------------- // HELPERS //-------------------- // Do some work. func work(n int) int { if n < 0 { return 0 } return n * work(n-1) } // EOF golib-4.24.2/monitoring/nullbackend.go000066400000000000000000000125011315505703200176750ustar00rootroot00000000000000// Tideland Go Library - Monitoring - Null Backend // // Copyright (C) 2009-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package monitoring //-------------------- // IMPORTS //-------------------- import ( "time" ) //-------------------- // MEASURING //-------------------- // nullMeasuring implements the Measuring interface. type nullMeasuring struct{} // EndMEasuring implements the Measuring interface. func (m *nullMeasuring) EndMeasuring() time.Duration { return 0 } //-------------------- // MEASURING POINT //-------------------- // nullMeasuringPoint implements the MeasuringPoint interface. type nullMeasuringPoint struct{} // ID implements the MeasuringPoint interface. func (mp *nullMeasuringPoint) ID() string { return "null" } // Count implements the MeasuringPoint interface. func (mp *nullMeasuringPoint) Count() int64 { return 0 } // MinDuration implements the MeasuringPoint interface. func (mp *nullMeasuringPoint) MinDuration() time.Duration { return 0 } // MaxDuration implements the MeasuringPoint interface. func (mp *nullMeasuringPoint) MaxDuration() time.Duration { return 0 } // AvgDuration implements the MeasuringPoint interface. func (mp *nullMeasuringPoint) AvgDuration() time.Duration { return 0 } // String implements the Stringer interface. func (mp *nullMeasuringPoint) String() string { return "Null Measuring Point" } //-------------------- // STAY-SET VARIABLE //-------------------- // nullStaySetVariable implements the StaySetVariable interface. type nullStaySetVariable struct{} // ID implements the StaySetVariable interface. func (ssv *nullStaySetVariable) ID() string { return "null" } // Count implements the StaySetVariable interface. func (ssv *nullStaySetVariable) Count() int64 { return 0 } // ActValue implements the StaySetVariable interface. func (ssv *nullStaySetVariable) ActValue() int64 { return 0 } // MinValue implements the StaySetVariable interface. func (ssv *nullStaySetVariable) MinValue() int64 { return 0 } // MaxValue implements the StaySetVariable interface. func (ssv *nullStaySetVariable) MaxValue() int64 { return 0 } // MinValue implements the StaySetVariable interface. func (ssv *nullStaySetVariable) AvgValue() int64 { return 0 } // String implements the Stringer interface. func (ssv *nullStaySetVariable) String() string { return "Null Stay-Set Variable" } //-------------------- // DYNAMIC STATUS RETRIEVER //-------------------- // nullDynamicStatusValue implements the DynamicStatusValue interface. type nullDynamicStatusValue struct{} // ID implements the DynamicStatusValue interface. func (dsv *nullDynamicStatusValue) ID() string { return "null" } // Value implements the DynamicStatusValue interface. func (dsv *nullDynamicStatusValue) Value() string { return "" } // String implements the Stringer interface. func (dsv *nullDynamicStatusValue) String() string { return "Null Dynamic Status Value" } //-------------------- // MONITORING BACKEND //-------------------- // nullBackend implements the Backend interface. type nullBackend struct{} // NewNullBackend starts the null monitoring backend doing nothing. func NewNullBackend() Backend { return &nullBackend{} } // BeginMeasuring implements the MonitorBackend interface. func (b *nullBackend) BeginMeasuring(id string) Measuring { return &nullMeasuring{} } // ReadMeasuringPoint implements the MonitorBackend interface. func (b *nullBackend) ReadMeasuringPoint(id string) (MeasuringPoint, error) { return &nullMeasuringPoint{}, nil } // MeasuringPointsDo implements the MonitorBackend interface. func (b *nullBackend) MeasuringPointsDo(f func(MeasuringPoint)) error { return nil } // SetVariable implements the MonitorBackend interface. func (b *nullBackend) SetVariable(id string, v int64) {} // IncrVariable implements the MonitorBackend interface. func (b *nullBackend) IncrVariable(id string) {} // DecrVariable implements the MonitorBackend interface. func (b *nullBackend) DecrVariable(id string) {} // ReadVariable implements the MonitorBackend interface. func (b *nullBackend) ReadVariable(id string) (StaySetVariable, error) { return &nullStaySetVariable{}, nil } // StaySetVariablesDo implements the MonitorBackend interface. func (b *nullBackend) StaySetVariablesDo(f func(StaySetVariable)) error { return nil } // Register implements the MonitorBackend interface. func (b *nullBackend) Register(id string, rf DynamicStatusRetriever) {} // ReadStatus implements the MonitorBackend interface. func (b *nullBackend) ReadStatus(id string) (string, error) { return "", nil } // DynamicStatusValuesDo implements the MonitorBackend interface. func (b *nullBackend) DynamicStatusValuesDo(f func(DynamicStatusValue)) error { return nil } // SetMeasuringsFilter implements the MonitorBackend interface. func (b *nullBackend) SetMeasuringsFilter(f IDFilter) IDFilter { return nil } // SetVariablesFilter implements the MonitorBackend interface. func (b *nullBackend) SetVariablesFilter(f IDFilter) IDFilter { return nil } // SetRetrieversFilter implements the MonitorBackend interface. func (b *nullBackend) SetRetrieversFilter(f IDFilter) IDFilter { return nil } // Reset implements the MonitorBackend interface. func (b *nullBackend) Reset() error { return nil } // Stop implements the MonitorBackend interface. func (b *nullBackend) Stop() {} // EOF golib-4.24.2/monitoring/standardbackend.go000066400000000000000000000353021315505703200205270ustar00rootroot00000000000000// Tideland Go Library - Monitoring - Standard Backend // // Copyright (C) 2009-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package monitoring //-------------------- // IMPORTS //-------------------- import ( "fmt" "sort" "time" "github.com/tideland/golib/errors" "github.com/tideland/golib/logger" "github.com/tideland/golib/loop" ) //-------------------- // CONSTANTS //-------------------- const ( cmdReset = iota cmdMeasuringPointRead cmdMeasuringPointsReadAll cmdStaySetVariableRead cmdStaySetVariablesReadAll cmdDynamicStatusRetrieverRead cmdDynamicStatusRetrieversReadAll ) //-------------------- // COMMAND //-------------------- // command encapsulated the data for any command. type command struct { opCode int args interface{} respChan chan interface{} } // respond allow to simply respond to a command. func (c *command) respond(v interface{}) { c.respChan <- v } // ok is a simple positive response. func (c *command) ok() { c.respond(true) } // close closes the response channel. func (c *command) close() { close(c.respChan) } //-------------------- // MEASURING //-------------------- // stdMeasuring implements the Measuring interface. type stdMeasuring struct { backend *stdBackend id string startTime time.Time duration time.Duration } // EndMEasuring implements the Measuring interface. func (m *stdMeasuring) EndMeasuring() time.Duration { if m.backend == nil { return 0 } m.duration = time.Since(m.startTime) m.backend.measuringC <- m return m.duration } //-------------------- // MEASURING POINT //-------------------- // stdMeasuringPoint implements the MeasuringPoint interface. type stdMeasuringPoint struct { id string count int64 minDuration time.Duration maxDuration time.Duration avgDuration time.Duration } // newStdMeasuringPoint creates a new measuring point out of a measuring. func newStdMeasuringPoint(m *stdMeasuring) *stdMeasuringPoint { return &stdMeasuringPoint{ id: m.id, count: 1, minDuration: m.duration, maxDuration: m.duration, avgDuration: m.duration, } } // ID implements the MeasuringPoint interface. func (mp *stdMeasuringPoint) ID() string { return mp.id } // Count implements the MeasuringPoint interface. func (mp *stdMeasuringPoint) Count() int64 { return mp.count } // MinDuration implements the MeasuringPoint interface. func (mp *stdMeasuringPoint) MinDuration() time.Duration { return mp.minDuration } // MaxDuration implements the MeasuringPoint interface. func (mp *stdMeasuringPoint) MaxDuration() time.Duration { return mp.maxDuration } // AvgDuration implements the MeasuringPoint interface. func (mp *stdMeasuringPoint) AvgDuration() time.Duration { return mp.avgDuration } // Uupdate a measuring point with a measuring. func (mp *stdMeasuringPoint) update(m *stdMeasuring) { average := mp.avgDuration.Nanoseconds() mp.count++ if mp.minDuration > m.duration { mp.minDuration = m.duration } if mp.maxDuration < m.duration { mp.maxDuration = m.duration } mp.avgDuration = time.Duration((average + m.duration.Nanoseconds()) / 2) } // String implements the Stringer interface. func (mp *stdMeasuringPoint) String() string { return fmt.Sprintf("Measuring Point %q (%dx / min %s / max %s / avg %s)", mp.id, mp.count, mp.minDuration, mp.maxDuration, mp.avgDuration) } //-------------------- // STAY-SET VARIABLE //-------------------- // stdSSVChange represents the change of a stay-set variable. type stdSSVChange struct { id string absolute bool variable int64 } // stdStaySetVariable implements the StaySetVariable interface. type stdStaySetVariable struct { id string count int64 actValue int64 minValue int64 maxValue int64 avgValue int64 total int64 } // newStdStaySetVariable creates a new stay-set variable out of a variable. func newStdStaySetVariable(v *stdSSVChange) *stdStaySetVariable { return &stdStaySetVariable{ id: v.id, count: 1, actValue: v.variable, minValue: v.variable, maxValue: v.variable, avgValue: v.variable, } } // ID implements the StaySetVariable interface. func (ssv *stdStaySetVariable) ID() string { return ssv.id } // Count implements the StaySetVariable interface. func (ssv *stdStaySetVariable) Count() int64 { return ssv.count } // ActValue implements the StaySetVariable interface. func (ssv *stdStaySetVariable) ActValue() int64 { return ssv.actValue } // MinValue implements the StaySetVariable interface. func (ssv *stdStaySetVariable) MinValue() int64 { return ssv.minValue } // MaxValue implements the StaySetVariable interface. func (ssv *stdStaySetVariable) MaxValue() int64 { return ssv.maxValue } // MinValue implements the StaySetVariable interface. func (ssv *stdStaySetVariable) AvgValue() int64 { return ssv.avgValue } // update a stay-set variable with a change. func (ssv *stdStaySetVariable) update(chg *stdSSVChange) { ssv.count++ if chg.absolute { ssv.actValue = chg.variable } else { ssv.actValue += chg.variable } ssv.total += chg.variable if ssv.minValue > ssv.actValue { ssv.minValue = ssv.actValue } if ssv.maxValue < ssv.actValue { ssv.maxValue = ssv.actValue } ssv.avgValue = ssv.total / ssv.count } // String implements the Stringer interface. func (ssv *stdStaySetVariable) String() string { return fmt.Sprintf("Stay-Set Variable %q (%dx / act %d / min %d / max %d / avg %d)", ssv.id, ssv.count, ssv.actValue, ssv.minValue, ssv.maxValue, ssv.avgValue) } //-------------------- // DYNAMIC STATUS RETRIEVER //-------------------- // stdRetrieverRegistration allows the registration of a retriever function. type stdRetrieverRegistration struct { id string dsr DynamicStatusRetriever } // stdDynamicStatusValue implements the DynamicStatusValue interface. type stdDynamicStatusValue struct { id string value string } // ID implements the DynamicStatusValue interface. func (dsv *stdDynamicStatusValue) ID() string { return dsv.id } // Value implements the DynamicStatusValue interface. func (dsv *stdDynamicStatusValue) Value() string { return dsv.value } // String implements the Stringer interface. func (dsv *stdDynamicStatusValue) String() string { return fmt.Sprintf("Dynamic Status Value %q (value = %q)", dsv.id, dsv.value) } //-------------------- // BACKEND //-------------------- // stdBackend implements the Backend interface. type stdBackend struct { etmFilter IDFilter ssvFilter IDFilter dsrFilter IDFilter etmData map[string]*stdMeasuringPoint ssvData map[string]*stdStaySetVariable dsrData map[string]DynamicStatusRetriever measuringC chan *stdMeasuring ssvChangeC chan *stdSSVChange retrieverRegistrationC chan *stdRetrieverRegistration commandC chan *command backend loop.Loop } // NewStandardBackend starts the standard monitoring backend. func NewStandardBackend() Backend { m := &stdBackend{ measuringC: make(chan *stdMeasuring, 1024), ssvChangeC: make(chan *stdSSVChange, 1024), retrieverRegistrationC: make(chan *stdRetrieverRegistration), commandC: make(chan *command), } m.backend = loop.GoRecoverable(m.backendLoop, m.checkRecovering, "monitoring backend") return m } // BeginMeasuring implements the MonitorBackend interface. func (b *stdBackend) BeginMeasuring(id string) Measuring { if b.etmFilter != nil && !b.etmFilter(id) { return &stdMeasuring{} } return &stdMeasuring{b, id, time.Now(), 0} } // ReadMeasuringPoint implements the MonitorBackend interface. func (b *stdBackend) ReadMeasuringPoint(id string) (MeasuringPoint, error) { resp, err := b.command(cmdMeasuringPointRead, id) if err != nil { return nil, err } return resp.(MeasuringPoint), nil } // MeasuringPointsDo implements the MonitorBackend interface. func (b *stdBackend) MeasuringPointsDo(f func(MeasuringPoint)) error { resp, err := b.command(cmdMeasuringPointsReadAll, nil) if err != nil { return err } mps := resp.(MeasuringPoints) for _, mp := range mps { f(mp) } return nil } // SetVariable implements the MonitorBackend interface. func (b *stdBackend) SetVariable(id string, v int64) { if b.ssvFilter != nil && !b.ssvFilter(id) { return } b.ssvChangeC <- &stdSSVChange{id, true, v} } // IncrVariable implements the MonitorBackend interface. func (b *stdBackend) IncrVariable(id string) { if b.ssvFilter != nil && !b.ssvFilter(id) { return } b.ssvChangeC <- &stdSSVChange{id, false, 1} } // DecrVariable implements the MonitorBackend interface. func (b *stdBackend) DecrVariable(id string) { if b.ssvFilter != nil && !b.ssvFilter(id) { return } b.ssvChangeC <- &stdSSVChange{id, false, -1} } // ReadVariable implements the MonitorBackend interface. func (b *stdBackend) ReadVariable(id string) (StaySetVariable, error) { resp, err := b.command(cmdStaySetVariableRead, id) if err != nil { return nil, err } return resp.(StaySetVariable), nil } // StaySetVariablesDo implements the MonitorBackend interface. func (b *stdBackend) StaySetVariablesDo(f func(StaySetVariable)) error { resp, err := b.command(cmdStaySetVariablesReadAll, nil) if err != nil { return err } ssvs := resp.(StaySetVariables) for _, ssv := range ssvs { f(ssv) } return nil } // Register implements the MonitorBackend interface. func (b *stdBackend) Register(id string, rf DynamicStatusRetriever) { if b.dsrFilter != nil && !b.dsrFilter(id) { return } b.retrieverRegistrationC <- &stdRetrieverRegistration{id, rf} } // ReadStatus implements the MonitorBackend interface. func (b *stdBackend) ReadStatus(id string) (string, error) { resp, err := b.command(cmdDynamicStatusRetrieverRead, id) if err != nil { return "", err } return resp.(string), nil } // DynamicStatusValuesDo implements the MonitorBackend interface. func (b *stdBackend) DynamicStatusValuesDo(f func(DynamicStatusValue)) error { resp, err := b.command(cmdDynamicStatusRetrieversReadAll, f) if err != nil { return err } dsvs := resp.(DynamicStatusValues) for _, dsv := range dsvs { f(dsv) } return nil } // SetMeasuringsFilter implements the MonitorBackend interface. func (b *stdBackend) SetMeasuringsFilter(f IDFilter) IDFilter { old := b.etmFilter b.etmFilter = f return old } // SetVariablesFilter implements the MonitorBackend interface. func (b *stdBackend) SetVariablesFilter(f IDFilter) IDFilter { old := b.ssvFilter b.ssvFilter = f return old } // SetRetrieversFilter implements the MonitorBackend interface. func (b *stdBackend) SetRetrieversFilter(f IDFilter) IDFilter { old := b.dsrFilter b.dsrFilter = f return old } // Reset implements the MonitorBackend interface. func (b *stdBackend) Reset() error { _, err := b.command(cmdReset, nil) if err != nil { return err } return nil } // Stop implements the MonitorBackend interface. func (b *stdBackend) Stop() { b.backend.Stop() } // command sends a command to the system monitor and waits for a response. func (b *stdBackend) command(opCode int, args interface{}) (interface{}, error) { cmd := &command{opCode, args, make(chan interface{})} b.commandC <- cmd resp, ok := <-cmd.respChan if !ok { return nil, errors.New(ErrMonitoringPanicked, errorMessages) } if err, ok := resp.(error); ok { return nil, err } return resp, nil } // init the system monitor. func (b *stdBackend) init() { b.etmData = make(map[string]*stdMeasuringPoint) b.ssvData = make(map[string]*stdStaySetVariable) b.dsrData = make(map[string]DynamicStatusRetriever) } // backendLoop runs the system monitor. func (b *stdBackend) backendLoop(l loop.Loop) error { // Init the monitor. b.init() // Run loop. for { select { case <-l.ShallStop(): return nil case measuring := <-b.measuringC: // Received a new measuring. if mp, ok := b.etmData[measuring.id]; ok { mp.update(measuring) } else { b.etmData[measuring.id] = newStdMeasuringPoint(measuring) } case ssvChange := <-b.ssvChangeC: // Received a new change. if ssv, ok := b.ssvData[ssvChange.id]; ok { ssv.update(ssvChange) } else { b.ssvData[ssvChange.id] = newStdStaySetVariable(ssvChange) } case registration := <-b.retrieverRegistrationC: // Received a new retriever for registration. b.dsrData[registration.id] = registration.dsr case cmd := <-b.commandC: // Received a command to process. b.processCommand(cmd) } } } // processCommand handles the received commands of the monitor. func (b *stdBackend) processCommand(cmd *command) { defer cmd.close() switch cmd.opCode { case cmdReset: // Reset monitoring. b.init() cmd.ok() case cmdMeasuringPointRead: // Read just one measuring point. id := cmd.args.(string) if mp, ok := b.etmData[id]; ok { // Measuring point found. clone := *mp cmd.respond(&clone) } else { // Measuring point does not exist. cmd.respond(errors.New(ErrMeasuringPointNotExists, errorMessages, id)) } case cmdMeasuringPointsReadAll: // Read all measuring points. resp := MeasuringPoints{} for _, mp := range b.etmData { clone := *mp resp = append(resp, &clone) } sort.Sort(resp) cmd.respond(resp) case cmdStaySetVariableRead: // Read just one stay-set variable. id := cmd.args.(string) if ssv, ok := b.ssvData[id]; ok { // Variable found. clone := *ssv cmd.respond(&clone) } else { // Variable does not exist. cmd.respond(errors.New(ErrStaySetVariableNotExists, errorMessages, id)) } case cmdStaySetVariablesReadAll: // Read all stay-set variables. resp := StaySetVariables{} for _, mp := range b.ssvData { clone := *mp resp = append(resp, &clone) } sort.Sort(resp) cmd.respond(resp) case cmdDynamicStatusRetrieverRead: // Read just one dynamic status value. id := cmd.args.(string) if dsr, ok := b.dsrData[id]; ok { // Dynamic status found. v, err := dsr() if err != nil { cmd.respond(err) } else { cmd.respond(v) } } else { // Dynamic status does not exist. cmd.respond(errors.New(ErrDynamicStatusNotExists, errorMessages, id)) } case cmdDynamicStatusRetrieversReadAll: // Read all dynamic status values. resp := DynamicStatusValues{} for id, dsr := range b.dsrData { v, err := dsr() if err != nil { cmd.respond(err) } dsv := &stdDynamicStatusValue{id, v} resp = append(resp, dsv) } sort.Sort(resp) cmd.respond(resp) } } // checkRecovering checks if the backend can be recovered. func (b *stdBackend) checkRecovering(rs loop.Recoverings) (loop.Recoverings, error) { if rs.Frequency(12, time.Minute) { logger.Errorf("standard monitor cannot be recovered: %v", rs.Last().Reason) return nil, errors.New(ErrMonitoringCannotBeRecovered, errorMessages, rs.Last().Reason) } logger.Warningf("standard monitor recovered: %v", rs.Last().Reason) return rs.Trim(12), nil } // EOF golib-4.24.2/numerics/000077500000000000000000000000001315505703200145255ustar00rootroot00000000000000golib-4.24.2/numerics/doc.go000066400000000000000000000005161315505703200156230ustar00rootroot00000000000000// Tideland Go Library - Numerics // // Copyright (C) 2009-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. // Package numerics of the Tideland Go Library contains some functions // to support statistical analysis. package numerics // EOF golib-4.24.2/numerics/numerics.go000066400000000000000000000323121315505703200167020ustar00rootroot00000000000000// Tideland Go Library - Numerics // // Copyright (C) 2009-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package numerics //-------------------- // IMPORTS //-------------------- import ( "fmt" "math" "sort" ) //-------------------- // POINT //-------------------- // Point is just one point in a 2D coordinate system. The // values for x or x are read-only. type Point struct { x float64 y float64 } // NewPoint creates a new point. func NewPoint(x, y float64) *Point { return &Point{x, y} } // IsInf checks if x or y is infinite. func (p Point) IsInf() bool { return math.IsInf(p.x, 0) || math.IsInf(p.y, 0) } // IsNaN checks if x or y is not a number. func (p Point) IsNaN() bool { return math.IsNaN(p.x) || math.IsNaN(p.y) } // X returns the x value of the point. func (p Point) X() float64 { return p.x } // Y returns the y value of the point. func (p Point) Y() float64 { return p.y } // DistanceTo takes another point and calculates the // geometric distance. func (p Point) DistanceTo(op *Point) float64 { dx := p.x - op.x dy := p.y - op.y return math.Sqrt(dx*dx + dy*dy) } // VectorTo returns the vector to another point. func (p Point) VectorTo(op *Point) *Vector { return NewVector(op.X()-p.x, op.Y()-p.y) } // String returns the string representation of the coordinates. func (p Point) String() string { return fmt.Sprintf("(%f, %f)", p.x, p.y) } // MiddlePoint returns the middle point between two points. func MiddlePoint(a, b *Point) *Point { return NewPoint((a.x+b.x)/2, (a.y+b.y)/2) } // PointVector returns the vector between two poins. func PointVector(a, b *Point) *Vector { return a.VectorTo(b) } //-------------------- // POINTS //-------------------- // Points is just a set of points. type Points []*Point // NewPoints creates a set of points. func NewPoints(p ...*Point) Points { if len(p) > 0 { return p } return []*Point{} } // XAt returns the X value of the point at a given index. func (ps Points) XAt(idx int) float64 { return ps[idx].X() } // YAt returns the Y value of the point at a given index. func (ps Points) YAt(idx int) float64 { return ps[idx].Y() } // XDifference returns the difference between two X // values of the set. func (ps Points) XDifference(idxA, idxB int) float64 { return ps[idxA].X() - ps[idxB].X() } // YDifference returns the difference between two Y // values of the set. func (ps Points) YDifference(idxA, idxB int) float64 { return ps[idxA].Y() - ps[idxB].Y() } // XInRange tests if an X value is in the range of X // values of the set. func (ps Points) XInRange(x float64) bool { minX := ps[0].X() maxX := ps[0].X() for _, p := range ps[1:] { if p.X() < minX { minX = p.X() } if p.X() > maxX { maxX = p.X() } } return minX <= x && x <= maxX } // SearchNextIndex searches the next index fo a // given X value. func (ps Points) SearchNextIndex(x float64) int { sf := func(i int) bool { return x < ps[i].X() } return sort.Search(len(ps), sf) } // Len returns the number of points in the set. func (ps Points) Len() int { return len(ps) } // Less returns true if the point with index i is less then the // one with index j. It first looks for X, then for Y. func (ps Points) Less(i, j int) bool { // Check X first. switch { case ps[i].x < ps[j].x: return true case ps[i].x > ps[j].x: return false } // Now check Y. switch { case ps[i].y < ps[j].y: return true case ps[i].y > ps[j].y: return false } return false } // Swap swaps two points of the set. func (ps Points) Swap(i, j int) { ps[i], ps[j] = ps[j], ps[i] } // CubicSplineFunction returns a cubic spline function based on the points. func (ps Points) CubicSplineFunction() *CubicSplineFunction { return NewCubicSplineFunction(ps) } // LeastSquaresFunction returns a least squares function based on the points. func (ps Points) LeastSquaresFunction() *LeastSquaresFunction { return NewLeastSquaresFunction(ps) } // String returns the string representation of the set. func (ps Points) String() string { pss := "{" for _, p := range ps { pss += p.String() } pss += "}" return pss } //-------------------- // VECTOR //-------------------- // Vector represents a vector in a coordinate system. The // values are read-only. type Vector struct { x float64 y float64 } // NewVector creates a new vector. func NewVector(x, y float64) *Vector { return &Vector{x, y} } // X returns the x value of the vector. func (v Vector) X() float64 { return v.x } // Y returns the y value of the vector. func (v Vector) Y() float64 { return v.y } // Len returns the length of the vector. func (v Vector) Len() float64 { return math.Sqrt(v.x*v.x + v.y*v.y) } // String returns the string representation of the vector. func (v Vector) String() string { return fmt.Sprintf("<%f, %f>", v.x, v.y) } // AddVectors returns a new vector as addition of two vectors. func AddVectors(a, b *Vector) *Vector { return NewVector(a.x+b.x, a.y+b.y) } // SubVectors returns a new vector as subtraction of two vectors. func SubVectors(a, b *Vector) *Vector { return NewVector(a.x-b.x, a.y-b.y) } // ScaleVector multiplies a vector with a float and returns // the new vector. func ScaleVector(v *Vector, s float64) *Vector { return NewVector(v.x*s, v.y*s) } //-------------------- // FUNCTION //-------------------- // Function is the standard interface the nmerical // functions have to implement. type Function interface { // Eval evaluates a function for the value x. Eval(x float64) float64 // EvalPoint evaluates a function for the value // x and returns the result as point. EvalPoint(x float64) *Point // EvalPoints evaluates the function count times // with values between fromX and toX. The result is // returned as a set of pints. EvalPoints(fromX, toX float64, count int) Points } //-------------------- // POLYNOMIAL FUNCTION //-------------------- // PolynomialFunction is a polynomial function based on a number // of coefficients. type PolynomialFunction struct { coefficients []float64 } // NewPolynomialFunction creates a new polynomial function. func NewPolynomialFunction(coefficients []float64) *PolynomialFunction { if len(coefficients) < 1 { return nil } pf := &PolynomialFunction{ coefficients: coefficients, } return pf } // Eval evaluates the function for a given X value and // returns the Y value. func (pf PolynomialFunction) Eval(x float64) float64 { n := len(pf.coefficients) result := pf.coefficients[n-1] for i := n - 2; i >= 0; i-- { result = x*result + pf.coefficients[i] } return result } // EvalPoint evaluates the function for a given X value // and returns the result as a point. func (pf PolynomialFunction) EvalPoint(x float64) *Point { return NewPoint(x, pf.Eval(x)) } // EvalPoints evaluates the function for a range of X values // and returns the result as a set of points. func (pf PolynomialFunction) EvalPoints(fromX, toX float64, count int) Points { return evalPoints(pf, fromX, toX, count) } // Differentiate differentiates the polynomial and returns the // new polynomial. func (pf PolynomialFunction) Differentiate() *PolynomialFunction { n := len(pf.coefficients) if n == 1 { return NewPolynomialFunction([]float64{0.0}) } newCoefficients := make([]float64, n-1) for i := n - 1; i > 0; i-- { newCoefficients[i-1] = float64(i) * pf.coefficients[i] } return NewPolynomialFunction(newCoefficients) } // String returns the string representation of the function // as f(x) := 2.9x^3+x^2-3.3x+1.0. func (pf PolynomialFunction) String() string { pfs := "f(x) := " for i := len(pf.coefficients) - 1; i > 0; i-- { if pf.coefficients[i] != 0.0 { pfs += fmt.Sprintf("%vx", pf.coefficients[i]) if i > 1 { pfs += fmt.Sprintf("^%v", i) } if pf.coefficients[i-1] > 0 { pfs += "+" } } } if pf.coefficients[0] != 0.0 { pfs += fmt.Sprintf("%v", pf.coefficients[0]) } return pfs } //-------------------- // CUBIC SPLINE FUNCTION //-------------------- // CubicSplineFunction is a function based on polynamial functions // and a set of points it is going through. type CubicSplineFunction struct { polynomials []*PolynomialFunction points Points } // NewCubicSplineFunction creates a cubic spline function based on a // set of points. func NewCubicSplineFunction(points Points) *CubicSplineFunction { if points.Len() < 3 { return nil } csf := &CubicSplineFunction{ points: points, } // Calculating differences between points. intervals := points.Len() - 1 differences := make([]float64, intervals) for i := 0; i < intervals; i++ { differences[i] = points[i+1].X() - points[i].X() } mu := make([]float64, intervals) z := make([]float64, points.Len()) var g float64 for i := 1; i < intervals; i++ { g = 2.0*points.XDifference(i+1, i-1) - differences[i-1]*mu[i-1] mu[i] = differences[i] / g z[i] = (3.0*(points.YAt(i+1)*differences[i-1]-points.YAt(i)* points.XDifference(i+1, i-1)+points.YAt(i-1)*differences[i])/ (differences[i-1]*differences[i]) - differences[i-1]*z[i-1]) / g } // Cubic spline coefficients (b is linear, c is quadratic, d is cubic). b := make([]float64, intervals) c := make([]float64, points.Len()) d := make([]float64, intervals) for i := intervals - 1; i >= 0; i-- { c[i] = z[i] - mu[i]*c[i+1] b[i] = points.YDifference(i+1, i)/differences[i] - differences[i]*(c[i+1]+2.0*c[i])/3.0 d[i] = (c[i+1] - c[i]) / (3.0 * differences[i]) } // Build polymonials. csf.polynomials = make([]*PolynomialFunction, intervals) coefficients := make([]float64, 4) for i := 0; i < intervals; i++ { coefficients[0] = points.YAt(i) coefficients[1] = b[i] coefficients[2] = c[i] coefficients[3] = d[i] csf.polynomials[i] = NewPolynomialFunction(coefficients) } return csf } // Eval evaluates the function for a given X value and // returns the Y value. func (csf *CubicSplineFunction) Eval(x float64) float64 { if !csf.points.XInRange(x) { panic("X out of range!") } idx := csf.points.SearchNextIndex(x) if idx >= len(csf.polynomials) { idx = len(csf.polynomials) - 1 } return csf.polynomials[idx].Eval(x - csf.points.XAt(idx)) } // EvalPoint evaluates the function for a given X value // and returns the result as a point. func (csf *CubicSplineFunction) EvalPoint(x float64) *Point { return NewPoint(x, csf.Eval(x)) } // EvalPoints evaluates the function for a range of X values // and returns the result as a set of points. func (csf *CubicSplineFunction) EvalPoints(fromX, toX float64, count int) Points { return evalPoints(csf, fromX, toX, count) } //-------------------- // LEAST SQUARES FUNCTION //-------------------- // LeastSquaresFunction is a function for approximation. type LeastSquaresFunction struct { sumX, sumXX float64 sumY, sumYY float64 sumXY float64 xBar, yBar float64 count int } // NewLeastSquaresFunction creates a new least squares function based // on a set of points. func NewLeastSquaresFunction(points Points) *LeastSquaresFunction { lsf := new(LeastSquaresFunction) if points != nil { lsf.AppendPoints(points) } return lsf } // AppendPoint appends one point to the function. func (lsf *LeastSquaresFunction) AppendPoint(x, y float64) { p := NewPoint(x, y) if lsf.count == 0 { lsf.xBar = p.X() lsf.yBar = p.Y() } else { dx := p.X() - lsf.xBar dy := p.Y() - lsf.yBar lsf.sumXX += dx * dx * float64(lsf.count) / float64(lsf.count+1.0) lsf.sumYY += dy * dy * float64(lsf.count) / float64(lsf.count+1.0) lsf.sumXY += dx * dy * float64(lsf.count) / float64(lsf.count+1.0) lsf.xBar += dx / float64(lsf.count+1.0) lsf.yBar += dy / float64(lsf.count+1.0) } lsf.sumX += p.X() lsf.sumY += p.Y() lsf.count++ } // AppendPoints appends a set of points to the function. func (lsf *LeastSquaresFunction) AppendPoints(points Points) { for _, p := range points { lsf.AppendPoint(p.X(), p.Y()) } } // Eval evaluates the function for a given X value and // returns the Y value. func (lsf *LeastSquaresFunction) Eval(x float64) float64 { slope := lsf.slope() result := lsf.intercept(slope) + slope*x return result } // EvalPoint evaluates the function for a given X value // and returns the result as a point. func (lsf *LeastSquaresFunction) EvalPoint(x float64) *Point { return NewPoint(x, lsf.Eval(x)) } // EvalPoints evaluates the function for a range of X values // and returns the result as a set of points. func (lsf *LeastSquaresFunction) EvalPoints(fromX, toX float64, count int) Points { return evalPoints(lsf, fromX, toX, count) } // slope returns the slope of the least square function. func (lsf *LeastSquaresFunction) slope() float64 { if lsf.count < 2 { // Not enough points added. return math.NaN() } if math.Abs(lsf.sumXX) < 10*math.SmallestNonzeroFloat64 { // Not enough variation in X. return math.NaN() } return lsf.sumXY / lsf.sumXX } // intercept returns the intercept for a given slope. func (lsf *LeastSquaresFunction) intercept(slope float64) float64 { return (lsf.sumY - slope*lsf.sumX) / float64(lsf.count) } //-------------------- // HELPERS //-------------------- // evalPoints evaluate a function for a range and a // number of evaluations. func evalPoints(f Function, fromX, toX float64, count int) Points { interval := (toX - fromX) / float64(count) ps := NewPoints() for x := fromX; x < toX; x += interval { y := f.Eval(x) ps = append(ps, NewPoint(x, y)) } return ps } // EOF golib-4.24.2/numerics/numerics_test.go000066400000000000000000000113241315505703200177410ustar00rootroot00000000000000// Tideland Go Library - Numerics - Unit Tests // // Copyright (C) 2009-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package numerics //-------------------- // IMPORTS //-------------------- import ( "sort" "testing" "github.com/tideland/golib/audit" ) //-------------------- // TESTS //-------------------- // Test simple point. func TestSimplePoint(t *testing.T) { assert := audit.NewTestingAssertion(t, true) // Create some points. pa := NewPoint(1.0, 5.0) pb := NewPoint(2.0, 2.0) pc := NewPoint(3.0, 4.0) // Asserts. assert.Equal(pa.X(), 1.0, "X of point A") assert.Equal(pa.Y(), 5.0, "Y of point A") assert.About(pb.DistanceTo(pc), 2.236, 0.0001, "distance B to C") assert.Equal(MiddlePoint(pb, pc).X(), 2.5, "X value of middle point") assert.Equal(MiddlePoint(pb, pc).Y(), 3.0, "Y value of middle point") assert.Equal(PointVector(pb, pc).String(), "<1.000000, 2.000000>", "string representation of vector") } // Test simple point array. func TestSimplePointArray(t *testing.T) { assert := audit.NewTestingAssertion(t, true) // Create some points. ps := NewPoints() assert.Empty(ps, "No points yet.") ps = append(ps, NewPoint(2.0, 2.0)) ps = append(ps, NewPoint(5.0, 1.0)) ps = append(ps, NewPoint(4.0, 2.0)) ps = append(ps, NewPoint(3.0, 3.0)) ps = append(ps, NewPoint(1.0, 1.0)) // Asserts. assert.Equal(ps.Len(), 5, "now with points") sort.Sort(ps) assert.Equal(ps[0], NewPoint(1.0, 1.0), "first point") assert.Equal(ps.Len(), 5, "length") assert.Equal(ps[3].X(), 4.0, "X of 4th point") } // Test simple polynomial function. func TestSimplePolynomialFunction(t *testing.T) { assert := audit.NewTestingAssertion(t, true) // Some polynominal function evaluations. p := NewPolynomialFunction([]float64{2.0, 2.0}) fa := p.Eval(-2.0) fb := p.Eval(-1.0) fc := p.Eval(0.0) fd := p.Eval(2.0) // Asserts. assert.Equal(fa, -2.0, "f(a)") assert.Equal(fb, 0.0, "f(b)") assert.Equal(fc, 2.0, "f(c)") assert.Equal(fd, 6.0, "f(d)") } // Test polynomial function printing. func TestPolynomialFunctionPrinting(t *testing.T) { assert := audit.NewTestingAssertion(t, true) p := NewPolynomialFunction([]float64{-7.55, 2.0, -3.1, 2.66, -3.45}) // Asserts. assert.Equal(p.String(), "f(x) := -3.45x^4+2.66x^3-3.1x^2+2x-7.55", "string representation") } // Test quadratic polynomial function. func TestQuadraticPolynomialFunction(t *testing.T) { assert := audit.NewTestingAssertion(t, true) // Some polynominal function evaluations. p := NewPolynomialFunction([]float64{0.0, 0.0, 1.0}) fa := p.Eval(-1.0) fb := p.Eval(2.0) fc := p.Eval(-3.0) // Asserts. assert.Equal(fa, 1.0, "f(a)") assert.Equal(fb, 4.0, "f(b)") assert.Equal(fc, 9.0, "f(c)") } // Test polynomial function differentiation. func TestPolynomialFunctionDifferentiation(t *testing.T) { assert := audit.NewTestingAssertion(t, true) p := NewPolynomialFunction([]float64{1.0, 2.0, 1.0, 3.0}) dp := p.Differentiate() // Asserts. assert.Equal(dp.String(), "f(x) := 9x^2+2x+2") } // Test interpolation. func TestInterpolation(t *testing.T) { assert := audit.NewTestingAssertion(t, true) // Build a cubic spline function. ps := NewPoints() ps = append(ps, NewPoint(1.0, 1.0)) ps = append(ps, NewPoint(2.0, 2.0)) ps = append(ps, NewPoint(3.0, 3.0)) ps = append(ps, NewPoint(4.0, 2.0)) ps = append(ps, NewPoint(5.0, 1.0)) f := NewCubicSplineFunction(ps) // Asserts. assert.About(f.EvalPoint(3.5).Y(), 2.7678, 0.0001, "f(3.5)") } // Test points evaluation. func TestPointsEvaluation(t *testing.T) { assert := audit.NewTestingAssertion(t, true) // Build a least squares function out of the results // off a cubic spline function. ps := NewPoints() ps = append(ps, NewPoint(0.0, 0.7)) ps = append(ps, NewPoint(1.0, 1.1)) ps = append(ps, NewPoint(2.0, 0.0)) ps = append(ps, NewPoint(3.0, -0.5)) ps = append(ps, NewPoint(4.0, -2.0)) ps = append(ps, NewPoint(5.0, -1.0)) ps = append(ps, NewPoint(6.0, 0.2)) ps = append(ps, NewPoint(7.0, 0.3)) ps = append(ps, NewPoint(8.0, -0.4)) ps = append(ps, NewPoint(9.0, -0.5)) lsf := ps.CubicSplineFunction().EvalPoints(0.7, 8.1, 50).LeastSquaresFunction() // Asserts. assert.About(lsf.Eval(15.0), -0.0407, 0.0001, "f(15.0)") } // Test least squares function. func TestLeastSquaresFunction(t *testing.T) { assert := audit.NewTestingAssertion(t, true) // Build a leas squares function. lsf := NewLeastSquaresFunction(nil) lsf.AppendPoint(1.0, 1.0) lsf.AppendPoint(2.0, 0.5) lsf.AppendPoint(3.0, 2.0) lsf.AppendPoint(4.0, 2.5) lsf.AppendPoint(5.0, 1.5) lsf.AppendPoint(6.0, 1.0) lsf.AppendPoint(7.0, 1.5) // Asserts. assert.About(lsf.Eval(9.0), 1.7857, 0.0001, "f(9.0)") assert.About(lsf.Eval(4.5), 1.4642, 0.0001, "f(4.5)") } // EOF golib-4.24.2/redis/000077500000000000000000000000001315505703200140065ustar00rootroot00000000000000golib-4.24.2/redis/commands_test.go000066400000000000000000000303671315505703200172060ustar00rootroot00000000000000// Tideland Go Library - Redis Client - Unit Tests - Commands // // Copyright (C) 2009-2017 Frank Mueller / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package redis_test //-------------------- // IMPORTS //-------------------- import ( "testing" "time" "github.com/tideland/golib/audit" "github.com/tideland/golib/errors" "github.com/tideland/golib/redis" ) //-------------------- // TESTS //-------------------- func TestSimpleKeyOperations(t *testing.T) { assert := audit.NewTestingAssertion(t, true) conn, restore := connectDatabase(assert) defer restore() ok, err := conn.DoOK("set", "sko:a", 1) assert.Nil(err) assert.True(ok) skoA, err := conn.DoInt("get", "sko:a") assert.Nil(err) assert.Equal(skoA, 1) exists, err := conn.DoBool("exists", "sko:a") assert.Nil(err) assert.True(exists) conn.Do("set", "sko:b", 2) conn.Do("set", "sko:c", 3) conn.Do("set", "sko:d", 4) conn.Do("set", "sko:e", 5) dbSize, err := conn.DoInt("dbsize") assert.Nil(err) assert.Equal(dbSize, 5) keys, err := conn.DoStrings("keys", "sko:*") assert.Nil(err) assert.Length(keys, 5) deleted, err := conn.DoInt("del", "sko:c", "sko:d", "sko:z") assert.Nil(err) assert.Equal(deleted, 2) keys, err = conn.DoStrings("keys", "sko:*") assert.Nil(err) assert.Length(keys, 3) h := redis.NewFilledHash(map[string]interface{}{ "sko:f": 6, "sko:g": 7, "sko:h": 8, }) conn.Do("mset", h) keys, err = conn.DoStrings("keys", "sko:*") assert.Nil(err) assert.Length(keys, 6) ssIn := []string{"do", "re", "mi", "fa", "sol", "la", "ti"} conn.Do("set", "sko:zz", ssIn) vOut, err := conn.DoValue("get", "sko:zz") assert.Nil(err) ssOut := vOut.StringSlice() assert.Equal(ssOut, ssIn) } func TestScan(t *testing.T) { assert := audit.NewTestingAssertion(t, true) conn, restore := connectDatabase(assert) defer restore() for i := 97; i < 123; i++ { for j := 97; j < 123; j++ { value := string([]byte{byte(i), byte(j)}) key := "scan:" + value conn.Do("set", key, value) } } cursor, result, err := conn.DoScan("scan", 0, "match", "scan:*", "count", 5) assert.Nil(err) assert.True(cursor != 0) assert.True(result.Len() > 0) loopCursor := 0 loopCount := 0 valueCount := 0 for { cursor, result, err := conn.DoScan("scan", loopCursor, "match", "scan:*", "count", 5) assert.Nil(err) loopCount += 1 valueCount += result.Len() if cursor == 0 { break } loopCursor = cursor } assert.True(loopCount > 1) assert.Equal(valueCount, 26*26) } func TestHash(t *testing.T) { assert := audit.NewTestingAssertion(t, true) conn, restore := connectDatabase(assert) defer restore() e := map[string]string{ "e1": "foo", "e2": "bar", "e3": "yadda", } ok, err := conn.DoOK("hmset", "hash", "a", "foo", "b", 2, "c", 3.3, "d", true, "e", e) assert.Nil(err) assert.True(ok) valueA, err := conn.DoString("hget", "hash", "a") assert.Nil(err) assert.Equal(valueA, "foo") hash, err := conn.DoHash("hgetall", "hash") assert.Nil(err) assert.Length(hash, 5) valueA, err = hash.String("a") assert.Nil(err) assert.Equal(valueA, "foo") valueB, err := hash.Int("b") assert.Nil(err) assert.Equal(valueB, 2) valueC, err := hash.Float64("c") assert.Nil(err) assert.Equal(valueC, 3.3) valueD, err := hash.Bool("d") assert.Nil(err) assert.True(valueD) valueE := hash.StringMap("e") assert.Equal(valueE, e) } func TestHScan(t *testing.T) { assert := audit.NewTestingAssertion(t, true) conn, restore := connectDatabase(assert) defer restore() for i := 97; i < 123; i++ { for j := 97; j < 123; j++ { value := string([]byte{byte(i), byte(j)}) field := "field:" + value conn.Do("hset", "scan-hash", field, value) } } cursor, result, err := conn.DoScan("hscan", "scan-hash", 0, "match", "field:*", "count", 5) assert.Nil(err) assert.True(cursor != 0) assert.True(result.Len() > 0) loopCursor := 0 loopCount := 0 valueCount := 0 for { cursor, result, err := conn.DoScan("hscan", "scan-hash", loopCursor, "match", "field:*", "count", 5) assert.Nil(err) hash, err := result.Hash() assert.Nil(err) loopCount += 1 valueCount += hash.Len() if cursor == 0 { break } loopCursor = cursor } assert.True(loopCount > 1) assert.Equal(valueCount, 26*26) } func TestList(t *testing.T) { assert := audit.NewTestingAssertion(t, true) conn, restore := connectDatabase(assert) defer restore() pushed, err := conn.DoInt("lpush", "list", 1, 2, 3, 4, 5) assert.Nil(err) assert.Equal(pushed, 5) popped, err := conn.DoInt("lpop", "list") assert.Nil(err) assert.Equal(popped, 5) } func TestSet(t *testing.T) { assert := audit.NewTestingAssertion(t, true) conn, restore := connectDatabase(assert) defer restore() added, err := conn.DoInt("sadd", "set", 1, 2, 3, 4, 5) assert.Nil(err) assert.Equal(added, 5) is, err := conn.DoBool("sismember", "set", 2) assert.Nil(err) assert.True(is) is, err = conn.DoBool("sismember", "set", 99) assert.Nil(err) assert.False(is) rand, err := conn.DoInt("srandmember", "set") assert.Nil(err) assert.True(rand >= 1 && rand <= 5) } func TestSScan(t *testing.T) { assert := audit.NewTestingAssertion(t, true) conn, restore := connectDatabase(assert) defer restore() for i := 97; i < 123; i++ { for j := 97; j < 123; j++ { value := string([]byte{byte(i), byte(j)}) conn.Do("sadd", "scan-set", value) } } cursor, result, err := conn.DoScan("sscan", "scan-set", 0, "match", "*", "count", 5) assert.Nil(err) assert.True(cursor != 0) assert.True(result.Len() > 0) loopCursor := 0 loopCount := 0 valueCount := 0 for { cursor, result, err := conn.DoScan("sscan", "scan-set", loopCursor, "match", "*", "count", 5) assert.Nil(err) loopCount += 1 valueCount += result.Len() if cursor == 0 { break } loopCursor = cursor } assert.True(loopCount > 1) assert.Equal(valueCount, 26*26) } func TestSortedSet(t *testing.T) { assert := audit.NewTestingAssertion(t, true) conn, restore := connectDatabase(assert) defer restore() added, err := conn.DoInt("zadd", "sorted-set", 1, "a", 2, "b", 3, "c", 4, "d", 5, "e") assert.Nil(err) assert.Equal(added, 5) scoredValues, err := conn.DoScoredValues("zrange", "sorted-set", 2, 4) assert.Nil(err) assert.Length(scoredValues, 3) assert.Equal(scoredValues[0].Score, 0.0) scoredValues, err = conn.DoScoredValues("zrange", "sorted-set", 2, 4, "withscores") assert.Nil(err) assert.Length(scoredValues, 3) assert.Equal(scoredValues[0].Score, 3.0) assert.Equal(scoredValues[1].Score, 4.0) assert.Equal(scoredValues[2].Score, 5.0) } func TestZScan(t *testing.T) { assert := audit.NewTestingAssertion(t, true) conn, restore := connectDatabase(assert) defer restore() for i := 97; i < 123; i++ { for j := 97; j < 123; j++ { score := i * j value := string([]byte{byte(i), byte(j)}) conn.Do("zadd", "scan-sorted-set", score, value) } } cursor, result, err := conn.DoScan("zscan", "scan-sorted-set", 0, "match", "*", "count", 5) assert.Nil(err) assert.True(cursor != 0) assert.True(result.Len() > 0) loopCursor := 0 loopCount := 0 valueCount := 0 for { cursor, result, err := conn.DoScan("zscan", "scan-sorted-set", loopCursor, "match", "*", "count", 5) assert.Nil(err) loopCount += 1 scoredValues, err := result.ScoredValues(true) assert.Nil(err) valueCount += scoredValues.Len() if cursor == 0 { break } loopCursor = cursor } assert.True(loopCount > 1) assert.Equal(valueCount, 26*26) } func TestTransactionConnection(t *testing.T) { assert := audit.NewTestingAssertion(t, true) conn, restore := connectDatabase(assert) defer restore() ok, err := conn.DoOK("multi") assert.Nil(err) assert.True(ok) conn.Do("set", "tx:a", 1) conn.Do("set", "tx:b", 2) conn.Do("set", "tx:c", 3) result, err := conn.Do("exec") assert.Nil(err) assert.Length(result, 3) valueB, err := conn.DoInt("get", "tx:b") assert.Nil(err) assert.Equal(valueB, 2) ok, err = conn.DoOK("multi") assert.Nil(err) assert.True(ok) conn.Do("set", "tx:d", 4) conn.Do("set", "tx:e", 5) conn.Do("set", "tx:f", 6) ok, err = conn.DoOK("discard") assert.Nil(err) assert.True(ok) valueE, err := conn.DoValue("get", "tx:e") assert.Nil(err) assert.True(valueE.IsNil()) sig := make(chan struct{}) go func() { asyncConn, restore := connectDatabase(assert) defer restore() <-sig asyncConn.Do("set", "tx:h", 99) sig <- struct{}{} }() conn.Do("watch", "tx:h") ok, err = conn.DoOK("multi") assert.Nil(err) assert.True(ok) conn.Do("set", "tx:g", 4) conn.Do("set", "tx:h", 5) sig <- struct{}{} conn.Do("set", "tx:i", 6) <-sig _, err = conn.Do("exec") assert.True(errors.IsError(err, redis.ErrTimeout)) valueH, err := conn.DoInt("get", "tx:h") assert.Nil(err) assert.Equal(valueH, 99) } func TestTransactionPipeline(t *testing.T) { assert := audit.NewTestingAssertion(t, true) conn, connRestore := connectDatabase(assert) defer connRestore() ppl, pplRestore := pipelineDatabase(assert) defer pplRestore() err := ppl.Do("multi") assert.Nil(err) ppl.Do("set", "pipeline:a", 1) ppl.Do("set", "pipeline:b", 2) ppl.Do("set", "pipeline:c", 3) ppl.Do("exec") results, err := ppl.Collect() assert.Nil(err) assert.Length(results, 5) valueB, err := conn.DoInt("get", "pipeline:b") assert.Nil(err) assert.Equal(valueB, 2) err = ppl.Do("multi") assert.Nil(err) ppl.Do("set", "pipeline:d", 4) ppl.Do("set", "pipeline:e", 5) ppl.Do("set", "pipeline:f", 6) ppl.Do("discard") results, err = ppl.Collect() assert.Nil(err) assert.Length(results, 5) valueE, err := conn.DoValue("get", "pipeline:e") assert.Nil(err) assert.True(valueE.IsNil()) } func TestTransactionPipelineWatch(t *testing.T) { assert := audit.NewTestingAssertion(t, true) sigC := audit.MakeSigChan() // Background tasks. bgConn, bgConnRestore := connectDatabase(assert) defer bgConnRestore() go func() { <-sigC bgConn.Do("set", "watch:b", 99) sigC <- struct{}{} }() // Foreground tasks. fgConn, fgConnRestore := pipelineDatabase(assert) defer fgConnRestore() fgConn.Do("set", "watch:b", 0) fgConn.Do("watch", "watch:b") err := fgConn.Do("multi") assert.Nil(err) fgConn.Do("set", "watch:a", 1) fgConn.Do("set", "watch:b", 2) sigC <- struct{}{} fgConn.Do("set", "watch:c", 3) <-sigC fgConn.Do("exec") _, err = fgConn.Collect() assert.True(errors.IsError(err, redis.ErrTimeout)) valueB, err := bgConn.DoInt("get", "watch:b") assert.Nil(err) assert.Equal(valueB, 99) } func TestScripting(t *testing.T) { assert := audit.NewTestingAssertion(t, true) conn, restore := connectDatabase(assert) defer restore() script := "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" result, err := conn.Do("eval", script, 2, "key1", "key2", 1, "two") assert.Nil(err) assert.Length(result, 4) key1, err := result.StringAt(0) assert.Nil(err) assert.Equal(key1, "key1") key2, err := result.StringAt(1) assert.Nil(err) assert.Equal(key2, "key2") argv1, err := result.IntAt(2) assert.Nil(err) assert.Equal(argv1, 1) argv2, err := result.StringAt(3) assert.Nil(err) assert.Equal(argv2, "two") script = "return {redis.error_reply('x'), 'x', redis.status_reply('x')}" result, err = conn.Do("eval", script, 0) assert.Nil(err) assert.Length(result, 3) reply1, err := result.StringAt(0) assert.Nil(err) assert.Equal(reply1, "-x") reply2, err := result.StringAt(1) assert.Nil(err) assert.Equal(reply2, "x") reply3, err := result.StringAt(2) assert.Nil(err) assert.Equal(reply3, "+x") } func TestPubSub(t *testing.T) { assert := audit.NewTestingAssertion(t, true) conn, connRestore := connectDatabase(assert) defer connRestore() sub, subRestore := subscribeDatabase(assert) defer subRestore() _, err := conn.Do("subscribe", "pubsub") assert.True(errors.IsError(err, redis.ErrUseSubscription)) err = sub.Subscribe("pubsub") assert.Nil(err) pv, err := sub.Pop() assert.Nil(err) assert.Equal(pv.Kind, "subscribe") assert.Equal(pv.Channel, "pubsub") assert.Equal(pv.Count, 1) go func() { for i := 0; i < 10; i++ { time.Sleep(50 * time.Millisecond) receivers, err := conn.DoInt("publish", "pubsub", i) assert.Nil(err) assert.Equal(receivers, 1) } }() sleep := 1 * time.Millisecond for i := 0; i < 10; i++ { time.Sleep(sleep) pv, err := sub.Pop() assert.Nil(err) assert.Equal(pv.Kind, "message") assert.Equal(pv.Channel, "pubsub") value, err := pv.Value.Int() assert.Nil(err) assert.Equal(value, i) sleep *= 2 } } // EOF golib-4.24.2/redis/connection.go000066400000000000000000000122461315505703200165010ustar00rootroot00000000000000// Tideland Go Library - Redis Client - Connection // // Copyright (C) 2009-2017 Frank Mueller / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package redis //-------------------- // IMPORTS //-------------------- import ( "strings" "github.com/tideland/golib/errors" "github.com/tideland/golib/identifier" "github.com/tideland/golib/monitoring" ) //-------------------- // CONNECTION //-------------------- // Connection manages one connection to a Redis database. type Connection struct { database *Database resp *resp } // newConnection creates a new connection instance. func newConnection(db *Database) (*Connection, error) { conn := &Connection{ database: db, } err := conn.ensureProtocol() if err != nil { return nil, err } // Perform authentication and database selection. err = conn.resp.authenticate() if err != nil { conn.database.pool.kill(conn.resp) return nil, err } err = conn.resp.selectDatabase() if err != nil { conn.database.pool.kill(conn.resp) return nil, err } return conn, nil } // Do executes one Redis command and returns // the result as result set. func (conn *Connection) Do(cmd string, args ...interface{}) (*ResultSet, error) { cmd = strings.ToLower(cmd) if strings.Contains(cmd, "subscribe") { return nil, errors.New(ErrUseSubscription, errorMessages) } err := conn.ensureProtocol() if err != nil { return nil, err } if conn.database.monitoring { m := monitoring.BeginMeasuring(identifier.Identifier("redis", "command", cmd)) defer m.EndMeasuring() } err = conn.resp.sendCommand(cmd, args...) logCommand(cmd, args, err, conn.database.logging) if err != nil { return nil, err } result, err := conn.resp.receiveResultSet() return result, err } // DoValue executes one Redis command and returns a single value. func (conn *Connection) DoValue(cmd string, args ...interface{}) (Value, error) { result, err := conn.Do(cmd, args...) if err != nil { return nil, err } return result.ValueAt(0) } // DoOK executes one Redis command and checks if // it returns the OK string. func (conn *Connection) DoOK(cmd string, args ...interface{}) (bool, error) { value, err := conn.DoValue(cmd, args...) if err != nil { return false, err } return value.IsOK(), nil } // DoBool executes one Redis command and interpretes // the result as bool value. func (conn *Connection) DoBool(cmd string, args ...interface{}) (bool, error) { result, err := conn.Do(cmd, args...) if err != nil { return false, err } return result.BoolAt(0) } // DoInt executes one Redis command and interpretes // the result as int value. func (conn *Connection) DoInt(cmd string, args ...interface{}) (int, error) { result, err := conn.Do(cmd, args...) if err != nil { return 0, err } return result.IntAt(0) } // DoString executes one Redis command and interpretes // the result as string value. func (conn *Connection) DoString(cmd string, args ...interface{}) (string, error) { result, err := conn.Do(cmd, args...) if err != nil { return "", err } return result.StringAt(0) } // DoStrings executes one Redis command and interpretes // the result as a slice of strings. func (conn *Connection) DoStrings(cmd string, args ...interface{}) ([]string, error) { result, err := conn.Do(cmd, args...) if err != nil { return nil, err } return result.Strings(), nil } // DoKeyValues executes on Redis command and interpretes // the result as a list of keys and values. func (conn *Connection) DoKeyValues(cmd string, args ...interface{}) (KeyValues, error) { result, err := conn.Do(cmd, args...) if err != nil { return nil, err } return result.KeyValues() } // DoHash executes on Redis command and interpretes // the result as a hash. func (conn *Connection) DoHash(cmd string, args ...interface{}) (Hash, error) { result, err := conn.Do(cmd, args...) if err != nil { return nil, err } return result.Hash() } // DoScoredValues executes on Redis command and interpretes // the result as scored values. func (conn *Connection) DoScoredValues(cmd string, args ...interface{}) (ScoredValues, error) { var withScores bool for _, arg := range args { if s, ok := arg.(string); ok { if strings.ToLower(s) == "withscores" { withScores = true break } } } result, err := conn.Do(cmd, args...) if err != nil { return nil, err } return result.ScoredValues(withScores) } // DoScan executes one Redis command which should be one of the // scan commands. It returns the cursor and the result set containing // the key, values or scored values depending on the scan command. func (conn *Connection) DoScan(cmd string, args ...interface{}) (int, *ResultSet, error) { result, err := conn.Do(cmd, args...) if err != nil { return 0, nil, err } return result.Scanned() } // Return passes the connection back into the database pool. func (conn *Connection) Return() error { err := conn.database.pool.push(conn.resp) conn.resp = nil return err } // ensureProtocol retrieves a protocol from the pool if needed. func (conn *Connection) ensureProtocol() error { if conn.resp == nil { p, err := conn.database.pool.pull(unforcedPull) if err != nil { return err } conn.resp = p } return nil } // EOF golib-4.24.2/redis/doc.go000066400000000000000000000031071315505703200151030ustar00rootroot00000000000000// Tideland Go Library - Redis Client // // Copyright (C) 2009-2017 Frank Mueller / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. // Package redis of the Tideland Go Library provides a very powerful as well // as convenient client for the Redis database. // // After opening the database with Open() a pooled connection can be // retrieved using db.Connection(). It has be returnded to the pool with // with conn.Return(), optimally done using a defer after retrieving. The // connection provides a conn.Do() method to execute any command. It returns // a result set with helpers to access the returned values and convert // them into Go types. For typical returnings there are conn.DoXxx() methods. // // All conn.Do() methods work atomically and are able to run all commands // except subscriptions. Also the execution of scripts is possible that // way. Additionally the execution of commands can be pipelined. The // pipeline can be retrieved db.Pipeline(). It provides a ppl.Do() // method for the execution of individual commands. Their results can // be collected with ppl.Collect(), which returns a sice of result sets // containing the responses of the commands. // // Due to the nature of the subscription the client provides an own // type which can be retrieved with db.Subscription(). Here channels, // in the sense of the Redis Pub/Sub, can be subscribed or unsubscribed. // Published values can be retrieved with sub.Pop(). If the subscription // is not needed anymore it can be closed using sub.Close(). package redis // EOF golib-4.24.2/redis/errors.go000066400000000000000000000031101315505703200156440ustar00rootroot00000000000000// Tideland Go Library - Redis Client - Errors // // Copyright (C) 2009-2017 Frank Mueller / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package redis //-------------------- // IMPORTS //-------------------- import ( "github.com/tideland/golib/errors" ) //-------------------- // CONSTANTS //-------------------- // Error codes. const ( ErrInvalidConfiguration = iota ErrPoolLimitReached ErrConnectionEstablishing ErrConnectionBroken ErrInvalidResponse ErrServerResponse ErrTimeout ErrAuthenticate ErrSelectDatabase ErrUseSubscription ErrInvalidType ErrInvalidKey ErrIllegalItemIndex ErrIllegalItemType ) var errorMessages = errors.Messages{ ErrInvalidConfiguration: "invalid configuration value in field %q: %v", ErrPoolLimitReached: "connection pool limit (%d) reached", ErrConnectionEstablishing: "cannot establish connection", ErrConnectionBroken: "cannot %s, connection is broken", ErrInvalidResponse: "invalid server response: %q", ErrServerResponse: "server responded error: %v", ErrTimeout: "timeout waiting for response", ErrAuthenticate: "cannot authenticate", ErrSelectDatabase: "cannot select database", ErrUseSubscription: "use subscription type for subscriptions", ErrInvalidType: "invalid type conversion of \"%v\" to %q", ErrInvalidKey: "invalid key %q", ErrIllegalItemIndex: "item index %d is illegal for result set size %d", ErrIllegalItemType: "item at index %d is no %s", } // EOF golib-4.24.2/redis/options.go000066400000000000000000000060321315505703200160310ustar00rootroot00000000000000// Tideland Go Library - Redis Client - Arguments // // Copyright (C) 2009-2017 Frank Mueller / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package redis //-------------------- // IMPORTS //-------------------- import ( "time" "github.com/tideland/golib/errors" ) //-------------------- // OPTIONS //-------------------- const ( defaultAddress = "127.0.0.1:6379" defaultSocket = "/tmp/redis.sock" defaultNetwork = "unix" defaultTimeout = 30 * time.Second defaultIndex = 0 defaultPassword = "" defaultPoolSize = 10 defaultLogging = false defaultMonitoring = false ) // Options is returned when calling Options() on Database to // provide information about the database configuration. type Options struct { Address string Network string Timeout time.Duration Index int Password string PoolSize int Logging bool Monitoring bool } // Option defines a function setting an option. type Option func(d *Database) error // TcpConnection sets the connection to use TCP/IP. The default address // is "127.0.0.1:6379". The default timeout to connect are 30 seconds. func TcpConnection(address string, timeout time.Duration) Option { return func(d *Database) error { if address == "" { address = defaultAddress } d.address = address d.network = "tcp" if timeout < 0 { return errors.New(ErrInvalidConfiguration, errorMessages, "timeout", timeout) } else if timeout == 0 { timeout = defaultTimeout } d.timeout = timeout return nil } } // UnixConnection sets the connection to use a Unix socket. The default // is "/tmp/redis.sock". The default timeout to connect are 30 seconds. func UnixConnection(socket string, timeout time.Duration) Option { return func(d *Database) error { if socket == "" { socket = defaultSocket } d.address = socket d.network = "unix" if timeout < 0 { return errors.New(ErrInvalidConfiguration, errorMessages, "timeout", timeout) } else if timeout == 0 { timeout = defaultTimeout } d.timeout = timeout return nil } } // Index selects the database and sets the authentication. The // default database is the 0, the default password is empty. func Index(index int, password string) Option { return func(d *Database) error { if index < 0 { return errors.New(ErrInvalidConfiguration, errorMessages, "index", index) } d.index = index d.password = password return nil } } // PoolSize sets the pool size of the database. The default is 10. func PoolSize(poolsize int) Option { return func(d *Database) error { if poolsize < 0 { return errors.New(ErrInvalidConfiguration, errorMessages, "pool size", poolsize) } else if poolsize == 0 { poolsize = defaultPoolSize } d.poolsize = poolsize return nil } } // Monitoring sets logging and monitoring, logging and // monitoring are switched off by default. func Monitoring(logging, monitoring bool) Option { return func(d *Database) error { d.logging = logging d.monitoring = monitoring return nil } } // EOF golib-4.24.2/redis/pipeline.go000066400000000000000000000050451315505703200161460ustar00rootroot00000000000000// Tideland Go Library - Redis Client - Pipeline // // Copyright (C) 2009-2017 Frank Mueller / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package redis //-------------------- // IMPORTS //-------------------- import ( "strings" "github.com/tideland/golib/errors" "github.com/tideland/golib/identifier" "github.com/tideland/golib/monitoring" ) //-------------------- // CONNECTION //-------------------- // Pipeline manages a Redis connection executing // pipelined commands. type Pipeline struct { database *Database resp *resp counter int } // newPipeline creates a new pipeline instance. func newPipeline(db *Database) (*Pipeline, error) { ppl := &Pipeline{ database: db, } err := ppl.ensureProtocol() if err != nil { return nil, err } // Perform authentication and database selection. if err != nil { return nil, err } err = ppl.resp.authenticate() if err != nil { ppl.database.pool.kill(ppl.resp) return nil, err } err = ppl.resp.selectDatabase() if err != nil { ppl.database.pool.kill(ppl.resp) return nil, err } return ppl, nil } // Do executes one Redis command and returns // the result as result set. func (ppl *Pipeline) Do(cmd string, args ...interface{}) error { cmd = strings.ToLower(cmd) if strings.Contains(cmd, "subscribe") { return errors.New(ErrUseSubscription, errorMessages) } err := ppl.ensureProtocol() if err != nil { return err } if ppl.database.monitoring { m := monitoring.BeginMeasuring(identifier.Identifier("redis", "command", cmd)) defer m.EndMeasuring() } err = ppl.resp.sendCommand(cmd, args...) logCommand(cmd, args, err, ppl.database.logging) if err != nil { return err } ppl.counter++ return err } // Collect collects all the result sets of the commands and returns // the connection back into the pool. func (ppl *Pipeline) Collect() ([]*ResultSet, error) { defer func() { ppl.resp = nil }() err := ppl.ensureProtocol() if err != nil { return nil, err } results := []*ResultSet{} for i := ppl.counter; i > 0; i-- { result, err := ppl.resp.receiveResultSet() if err != nil { ppl.database.pool.kill(ppl.resp) return nil, err } results = append(results, result) } ppl.database.pool.push(ppl.resp) return results, nil } // ensureProtocol retrieves a protocol from the pool if needed. func (ppl *Pipeline) ensureProtocol() error { if ppl.resp == nil { p, err := ppl.database.pool.pull(unforcedPull) if err != nil { return err } ppl.resp = p ppl.counter = 0 } return nil } // EOF golib-4.24.2/redis/pool.go000066400000000000000000000106041315505703200153070ustar00rootroot00000000000000// Tideland Go Library - Redis Client - resp Pool // // Copyright (C) 2009-2017 Frank Mueller / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package redis //-------------------- // IMPORTS //-------------------- import ( "time" "github.com/tideland/golib/errors" "github.com/tideland/golib/loop" ) //-------------------- // CONNECTION POOL //-------------------- const ( forcedPull = true unforcedPull = false forcedPullRequest = iota unforcedPullRequest pushRequest killRequest closeRequest ) // poolResponse is returned as result of a pool request. type poolResponse struct { resp *resp err error } type poolRequest struct { kind int resp *resp responseChan chan *poolResponse } // pool manages a number of Redis resp instances. type pool struct { database *Database available map[*resp]*resp inUse map[*resp]*resp backend loop.Loop requestChan chan *poolRequest } // newPool creates a connection pool with uninitialized // protocol instances. func newPool(db *Database) *pool { p := &pool{ database: db, available: make(map[*resp]*resp), inUse: make(map[*resp]*resp), requestChan: make(chan *poolRequest), } p.backend = loop.Go(p.backendLoop, "redis", db.address, db.index) return p } // pull returns a protocol out of the pool. If none is available // but the configured pool sized isn't reached a new one will be // established. func (p *pool) pull(forced bool) (*resp, error) { if forced { return p.do(forcedPullRequest, nil) } wait := 5 * time.Millisecond for i := 0; i < 5; i++ { resp, err := p.do(unforcedPullRequest, nil) if err != nil { return nil, err } if resp != nil { return resp, nil } time.Sleep(wait) wait = wait * 2 } return nil, errors.New(ErrPoolLimitReached, errorMessages, p.database.poolsize) } // push returns a protocol back into the pool. func (p *pool) push(resp *resp) error { _, err := p.do(pushRequest, resp) return err } // kill closes the connection and removes it from the pool. func (p *pool) kill(resp *resp) error { _, err := p.do(killRequest, resp) return err } // close closes all pooled protocol instances, first the available ones, // then the ones in use. func (p *pool) close() error { _, err := p.do(closeRequest, nil) return err } // do executes one request. func (p *pool) do(kind int, resp *resp) (*resp, error) { request := &poolRequest{ kind: kind, resp: resp, responseChan: make(chan *poolResponse, 1), } p.requestChan <- request response := <-request.responseChan return response.resp, response.err } // respond answers to a request. func (p *pool) respond(request *poolRequest, resp *resp, err error) { response := &poolResponse{ resp: resp, err: err, } request.responseChan <- response } // backendLoop manages the pool in a serialized way. func (p *pool) backendLoop(l loop.Loop) error { for { select { case <-l.ShallStop(): return nil case request := <-p.requestChan: // Handle the request. switch request.kind { case forcedPullRequest: // Always return a new protocol. resp, err := newResp(p.database) if err != nil { p.respond(request, nil, err) } else { p.respond(request, resp, nil) } case unforcedPullRequest: // Fetch a protocol out of the pool. switch { case len(p.available) > 0: fetch: for resp := range p.available { delete(p.available, resp) p.inUse[resp] = resp p.respond(request, resp, nil) break fetch } case len(p.inUse) < p.database.poolsize: resp, err := newResp(p.database) if err != nil { p.respond(request, nil, err) } else { p.respond(request, resp, nil) } default: p.respond(request, nil, nil) } case pushRequest: // Return a protocol. delete(p.inUse, request.resp) if len(p.available) < p.database.poolsize { p.available[request.resp] = request.resp p.respond(request, nil, nil) } else { p.respond(request, nil, request.resp.close()) } case killRequest: // Close w/o reusing. delete(p.inUse, request.resp) p.respond(request, nil, request.resp.close()) case closeRequest: // Close all protocols. for resp := range p.available { resp.close() } for resp := range p.inUse { resp.close() } p.respond(request, nil, nil) } } } } // EOF golib-4.24.2/redis/redis.go000066400000000000000000000051251315505703200154460ustar00rootroot00000000000000// Tideland Go Library - Redis Client // // Copyright (C) 2009-2017 Frank Mueller / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package redis //-------------------- // IMPORTS //-------------------- import ( "fmt" "sync" "time" ) //-------------------- // DATABASE //-------------------- // Database provides access to a Redis database. type Database struct { mux sync.Mutex address string network string timeout time.Duration index int password string poolsize int logging bool monitoring bool pool *pool } // Open opens the connection to a Redis database based on the // passed options. func Open(options ...Option) (*Database, error) { db := &Database{ address: defaultSocket, network: defaultNetwork, timeout: defaultTimeout, index: defaultIndex, password: defaultPassword, poolsize: defaultPoolSize, logging: defaultLogging, monitoring: defaultMonitoring, } for _, option := range options { if err := option(db); err != nil { return nil, err } } db.pool = newPool(db) return db, nil } // Options returns the configuration of the database. func (db *Database) Options() Options { db.mux.Lock() defer db.mux.Unlock() return Options{ Address: db.address, Network: db.network, Timeout: db.timeout, Index: db.index, Password: db.password, PoolSize: db.poolsize, Logging: db.logging, Monitoring: db.monitoring, } } // Connection returns one of the pooled connections to the Redis // server. It has to be returned with conn.Return() after usage. func (db *Database) Connection() (*Connection, error) { db.mux.Lock() defer db.mux.Unlock() return newConnection(db) } // Pipeline returns one of the pooled connections to the Redis // server running in pipeline mode. Calling ppl.Collect() // collects all results and returns the connection. func (db *Database) Pipeline() (*Pipeline, error) { db.mux.Lock() defer db.mux.Unlock() return newPipeline(db) } // Subscription returns a subscription with a connection to the // Redis server. It has to be closed with sub.Close() after usage. func (db *Database) Subscription() (*Subscription, error) { db.mux.Lock() defer db.mux.Unlock() return newSubscription(db) } // Close closes the database client. func (db *Database) Close() error { db.mux.Lock() defer db.mux.Unlock() return db.pool.close() } // String implements the Stringer interface and returns address // plus index. func (db *Database) String() string { return fmt.Sprintf("%s:%d", db.address, db.index) } // EOF golib-4.24.2/redis/redis_test.go000066400000000000000000000146751315505703200165170ustar00rootroot00000000000000// Tideland Go Library - Redis Client - Unit Tests // // Copyright (C) 2009-2017 Frank Mueller / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package redis_test //-------------------- // IMPORTS //-------------------- import ( "testing" "time" "github.com/tideland/golib/audit" "github.com/tideland/golib/logger" "github.com/tideland/golib/redis" ) //-------------------- // TESTS //-------------------- func TestUnixSocketConnection(t *testing.T) { assert := audit.NewTestingAssertion(t, true) conn, restore := connectDatabase(assert, redis.UnixConnection("", 0)) defer restore() result, err := conn.Do("echo", "Hello, World!") assert.Nil(err) assertEqualString(assert, result, 0, "Hello, World!") result, err = conn.Do("ping") assert.Nil(err) assertEqualString(assert, result, 0, "+PONG") } func BenchmarkUnixConnection(b *testing.B) { assert := audit.NewTestingAssertion(b, true) conn, restore := connectDatabase(assert, redis.UnixConnection("", 0)) defer restore() for i := 0; i < b.N; i++ { result, err := conn.Do("ping") assert.Nil(err) assertEqualString(assert, result, 0, "+PONG") } } func TestTcpConnection(t *testing.T) { assert := audit.NewTestingAssertion(t, true) conn, restore := connectDatabase(assert, redis.TcpConnection("", 0)) defer restore() result, err := conn.Do("echo", "Hello, World!") assert.Nil(err) assertEqualString(assert, result, 0, "Hello, World!") result, err = conn.Do("ping") assert.Nil(err) assertEqualString(assert, result, 0, "+PONG") } func BenchmarkTcpConnection(b *testing.B) { assert := audit.NewTestingAssertion(b, true) conn, restore := connectDatabase(assert, redis.TcpConnection("", 0)) defer restore() for i := 0; i < b.N; i++ { result, err := conn.Do("ping") assert.Nil(err) assertEqualString(assert, result, 0, "+PONG") } } func TestPipelining(t *testing.T) { assert := audit.NewTestingAssertion(t, true) ppl, restore := pipelineDatabase(assert) defer restore() for i := 0; i < 1000; i++ { err := ppl.Do("ping") assert.Nil(err) } results, err := ppl.Collect() assert.Nil(err) assert.Length(results, 1000) for _, result := range results { assertEqualString(assert, result, 0, "+PONG") } } func BenchmarkPipelining(b *testing.B) { assert := audit.NewTestingAssertion(b, true) ppl, restore := pipelineDatabase(assert) defer restore() for i := 0; i < b.N; i++ { err := ppl.Do("ping") assert.Nil(err) } results, err := ppl.Collect() assert.Nil(err) assert.Length(results, b.N) for _, result := range results { assertEqualString(assert, result, 0, "+PONG") } } func TestOptions(t *testing.T) { assert := audit.NewTestingAssertion(t, true) db, err := redis.Open(redis.UnixConnection("", 0), redis.PoolSize(5)) assert.Nil(err) defer db.Close() options := db.Options() assert.Equal(options.Address, "/tmp/redis.sock") assert.Equal(options.Network, "unix") assert.Equal(options.Timeout, 30*time.Second) assert.Equal(options.Index, 0) assert.Equal(options.Password, "") assert.Equal(options.PoolSize, 5) assert.Equal(options.Logging, false) assert.Equal(options.Monitoring, false) } func TestConcurrency(t *testing.T) { assert := audit.NewTestingAssertion(t, true) db, err := redis.Open(redis.UnixConnection("", 0), redis.PoolSize(5)) assert.Nil(err) defer db.Close() for i := 0; i < 500; i++ { go func() { conn, err := db.Connection() assert.Nil(err) defer conn.Return() result, err := conn.Do("ping") assert.Nil(err) assertEqualString(assert, result, 0, "+PONG") time.Sleep(10 * time.Millisecond) }() } } //-------------------- // TOOLS //-------------------- func init() { logger.SetLevel(logger.LevelDebug) } // testDatabaseIndex defines the database index for the tests to not // get in conflict with existing databases. const testDatabaseIndex = 0 // connectDatabase connects to a Redis database with the given options // and returns a connection and a function for closing. This function // shall be called with defer. func connectDatabase(assert audit.Assertion, options ...redis.Option) (*redis.Connection, func()) { // Open and connect database. options = append(options, redis.Index(testDatabaseIndex, "")) db, err := redis.Open(options...) assert.Nil(err) conn, err := db.Connection() assert.Nil(err) // Flush all keys to get a clean testing environment. _, err = conn.Do("flushdb") assert.Nil(err) // Return connection and cleanup function. return conn, func() { conn.Return() db.Close() } } // pipelineDatabase connects to a Redis database with the given options // and returns a pipeling and a function for closing. This function // shall be called with a defer. func pipelineDatabase(assert audit.Assertion, options ...redis.Option) (*redis.Pipeline, func()) { // Open and connect database. options = append(options, redis.Index(testDatabaseIndex, "")) db, err := redis.Open(options...) assert.Nil(err) ppl, err := db.Pipeline() assert.Nil(err) // Return pipeline and cleanup function. return ppl, func() { db.Close() } } // subscribeDatabase connects to a Redis database with the given options // and returns a subscription and a function for closing. This function // shall be called with a defer. func subscribeDatabase(assert audit.Assertion, options ...redis.Option) (*redis.Subscription, func()) { // Open and connect database. options = append(options, redis.Index(testDatabaseIndex, "")) db, err := redis.Open(options...) assert.Nil(err) sub, err := db.Subscription() assert.Nil(err) // Return subscription and cleanup function. return sub, func() { sub.Close() db.Close() } } // assertEqualString checks if the result at index is value. func assertEqualString(assert audit.Assertion, result *redis.ResultSet, index int, value string) { s, err := result.StringAt(index) assert.Nil(err) assert.Equal(s, value) } // assertEqualBool checks if the result at index is value. func assertEqualBool(assert audit.Assertion, result *redis.ResultSet, index int, value bool) { b, err := result.BoolAt(index) assert.Nil(err) assert.Equal(b, value) } // assertEqualInt checks if the result at index is value. func assertEqualInt(assert audit.Assertion, result *redis.ResultSet, index, value int) { i, err := result.IntAt(index) assert.Nil(err) assert.Equal(i, value) } // assertNil checks if the result at index is nil. func assertNil(assert audit.Assertion, result *redis.ResultSet, index int) { v, err := result.ValueAt(index) assert.Nil(err) assert.Nil(v) } // EOF golib-4.24.2/redis/resp.go000066400000000000000000000202031315505703200153030ustar00rootroot00000000000000// Tideland Go Library - Redis Client - resp // // Copyright (C) 2009-2017 Frank Mueller / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package redis //-------------------- // IMPORTS //-------------------- import ( "bufio" "fmt" "io" "net" "strconv" "github.com/tideland/golib/errors" ) //-------------------- // respONSE //-------------------- // responseKind classifies a response of Redis. type responseKind int const ( receivingError responseKind = iota timeoutError statusResponse errorResponse integerResponse bulkResponse nullBulkResponse arrayResponse ) var responseKindDescr = map[responseKind]string{ receivingError: "receiving error", timeoutError: "timeout error", statusResponse: "status", errorResponse: "error", integerResponse: "integer", bulkResponse: "bulk", nullBulkResponse: "null-bulk", arrayResponse: "array", } // response contains one Redis response. type response struct { kind responseKind length int data []byte err error } // value returns the data as value. func (r *response) value() Value { return Value(r.data) } // errorValue returns the error as value. func (r *response) errorValue() Value { errdata := []byte(r.err.Error()) return Value(errdata) } // String creates a string representation of the response. func (r *response) String() string { descr := responseKindDescr[r.kind] return fmt.Sprintf("RESPONSE (Kind: %s / Length: %d / Value: %v / Error: %v)", descr, r.length, r.value(), r.err) } //-------------------- // REDIS SERIALIZATION PROTOCOL //-------------------- // resp implements the Redis Serialization Protocol. type resp struct { database *Database conn net.Conn reader *bufio.Reader cmd string } // newResp establishes a connection to a Redis database // based on the configuration of the passed database // configuration. func newResp(db *Database) (*resp, error) { // Dial the database and create the protocol instance. conn, err := net.DialTimeout(db.network, db.address, db.timeout) if err != nil { return nil, errors.Annotate(err, ErrConnectionEstablishing, errorMessages) } r := &resp{ database: db, conn: conn, reader: bufio.NewReader(conn), } return r, nil } // sendCommand sends a command and possible arguments to the server. func (r *resp) sendCommand(cmd string, args ...interface{}) error { r.cmd = cmd lengthPart := r.buildLengthPart(args) cmdPart := r.buildValuePart(cmd) argsPart := r.buildArgumentsPart(args) packet := join(lengthPart, cmdPart, argsPart) _, err := r.conn.Write(packet) if err != nil { return errors.Annotate(err, ErrConnectionBroken, errorMessages, "send "+r.cmd) } return nil } // receiveResponse retrieves a response from the server. func (r *resp) receiveResponse() *response { // Receive first line. line, err := r.reader.ReadBytes('\n') if err != nil { rerr := errors.Annotate(err, ErrConnectionBroken, errorMessages, "receive after "+r.cmd) return &response{receivingError, 0, nil, rerr} } content := line[1 : len(line)-2] // First byte defines kind. switch line[0] { case '+': // Status response. return &response{statusResponse, 0, line[:len(line)-2], nil} case '-': // Error response. return &response{errorResponse, 0, line[:len(line)-2], nil} case ':': // Integer response. return &response{integerResponse, 0, content, nil} case '$': // Bulk response or null bulk response. count, err := strconv.Atoi(string(content)) if err != nil { return &response{receivingError, 0, nil, errors.Annotate(err, ErrServerResponse, errorMessages)} } if count == -1 { // Null bulk response. return &response{nullBulkResponse, 0, nil, nil} } // Receive the bulk data. toRead := count + 2 buffer := make([]byte, toRead) n, err := io.ReadFull(r.reader, buffer) if err != nil { return &response{receivingError, 0, nil, err} } if n < toRead { return &response{receivingError, 0, nil, errors.New(ErrServerResponse, errorMessages)} } return &response{bulkResponse, 0, buffer[0:count], nil} case '*': // Array reply. Check for timeout. length, err := strconv.Atoi(string(content)) if err != nil { return &response{receivingError, 0, nil, errors.Annotate(err, ErrServerResponse, errorMessages)} } if length == -1 { // Timeout. return &response{timeoutError, 0, nil, nil} } return &response{arrayResponse, length, nil, nil} } return &response{receivingError, 0, nil, errors.New(ErrInvalidResponse, errorMessages, string(line))} } // receiveResultSet receives all responses and converts them into a result set. func (r *resp) receiveResultSet() (*ResultSet, error) { defer func() { r.cmd = "-none-" }() result := newResultSet() current := result for { response := r.receiveResponse() switch response.kind { case receivingError: return nil, response.err case timeoutError: return nil, errors.New(ErrTimeout, errorMessages) case statusResponse, errorResponse, integerResponse, bulkResponse, nullBulkResponse: current.append(response.value()) case arrayResponse: switch { case current == result && current.Len() == 0: current.length = response.length case !current.allReceived(): next := newResultSet() next.parent = current current.append(next) current = next current.length = response.length } } // Check if all values are received. current = current.nextResultSet() if current == nil { return result, nil } } } // buildLengthPart creates the length part of a command. func (r *resp) buildLengthPart(args []interface{}) []byte { length := 1 for _, arg := range args { switch typedArg := arg.(type) { case valuer: length += typedArg.Len() case Hash: length += typedArg.Len() * 2 case Hashable: length += typedArg.Len() * 2 default: length++ } } return join("*", length, "\r\n") } // buildValuePart creates one value part of a command. func (r *resp) buildValuePart(value interface{}) []byte { var raw []byte if v, ok := value.(Value); ok { raw = []byte(v) } else { raw = valueToBytes(value) } return join("$", len(raw), "\r\n", raw, "\r\n") } // buildArgumentsPart creates the the arguments parts of a command. func (r *resp) buildArgumentsPart(args []interface{}) []byte { buildValuesPart := func(vs valuer) []byte { tmp := []byte{} for _, value := range vs.Values() { tmp = append(tmp, r.buildValuePart(value)...) } return tmp } buildHashPart := func(h Hash) []byte { tmp := []byte{} for key, value := range h { tmp = append(tmp, r.buildValuePart(key)...) tmp = append(tmp, r.buildValuePart(value)...) } return tmp } tmp := []byte{} part := []byte{} for _, arg := range args { switch typedArg := arg.(type) { case valuer: part = buildValuesPart(typedArg) case Hash: part = buildHashPart(typedArg) case Hashable: part = buildHashPart(typedArg.GetHash()) default: part = r.buildValuePart(arg) } tmp = append(tmp, part...) } return tmp } // authenticate authenticates against the server if configured. func (r *resp) authenticate() error { if r.database.password != "" { err := r.sendCommand("auth", r.database.password) if err != nil { return errors.Annotate(err, ErrAuthenticate, errorMessages) } result, err := r.receiveResultSet() if err != nil { return errors.Annotate(err, ErrAuthenticate, errorMessages) } value, err := result.ValueAt(0) if err != nil { return errors.Annotate(err, ErrAuthenticate, errorMessages) } if !value.IsOK() { return errors.New(ErrAuthenticate, errorMessages) } } return nil } // selectDatabase selects the database. func (r *resp) selectDatabase() error { err := r.sendCommand("select", r.database.index) if err != nil { return errors.Annotate(err, ErrSelectDatabase, errorMessages) } result, err := r.receiveResultSet() if err != nil { return errors.Annotate(err, ErrSelectDatabase, errorMessages) } value, err := result.ValueAt(0) if err != nil { return errors.Annotate(err, ErrSelectDatabase, errorMessages) } if !value.IsOK() { return errors.New(ErrSelectDatabase, errorMessages) } return nil } // close ends the connection to Redis. func (r *resp) close() error { return r.conn.Close() } // EOF golib-4.24.2/redis/resultset.go000066400000000000000000000135601315505703200163740ustar00rootroot00000000000000// Tideland Go Library - Redis Client - Result Set // // Copyright (C) 2009-2017 Frank Mueller / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package redis //-------------------- // IMPORTS //-------------------- import ( "fmt" "strings" "github.com/tideland/golib/errors" ) //-------------------- // RESULT SET //-------------------- // ResultSet contains a number of values or nested result sets. type ResultSet struct { parent *ResultSet items []interface{} length int } // newResultSet creates a new result set. func newResultSet() *ResultSet { return &ResultSet{nil, []interface{}{}, 1} } // append adds a value/result set to the result set. It panics if it's // neither a value, even as a byte slice, nor an array. func (rs *ResultSet) append(item interface{}) { switch i := item.(type) { case Value, *ResultSet: rs.items = append(rs.items, i) case []byte: rs.items = append(rs.items, Value(i)) case ResultSet: rs.items = append(rs.items, &i) default: panic("illegal result set item type") } } // allReceived answers with true if all expected items are received. func (rs *ResultSet) allReceived() bool { return len(rs.items) >= rs.length } // nextResultSet returns the parent stack upwards as long as all expected // items are received. func (rs *ResultSet) nextResultSet() *ResultSet { if !rs.allReceived() { return rs } if rs.parent == nil { return nil } return rs.parent.nextResultSet() } // Len returns the number of items in the result set. func (rs *ResultSet) Len() int { return len(rs.items) } // ValueAt returns the value at index. func (rs *ResultSet) ValueAt(index int) (Value, error) { if len(rs.items) < index+1 { return nil, errors.New(ErrIllegalItemIndex, errorMessages, index, len(rs.items)) } value, ok := rs.items[index].(Value) if !ok { return nil, errors.New(ErrIllegalItemType, errorMessages, index, "value") } return value, nil } // BoolAt returns the value at index as bool. This is a convenience // method as the bool is needed very often. func (rs *ResultSet) BoolAt(index int) (bool, error) { value, err := rs.ValueAt(index) if err != nil { return false, err } return value.Bool() } // IntAt returns the value at index as int. This is a convenience // method as the integer is needed very often. func (rs *ResultSet) IntAt(index int) (int, error) { value, err := rs.ValueAt(index) if err != nil { return 0, err } return value.Int() } // StringAt returns the value at index as string. This is a convenience // method as the string is needed very often. func (rs *ResultSet) StringAt(index int) (string, error) { value, err := rs.ValueAt(index) if err != nil { return "", err } return value.String(), nil } // ResultSetAt returns the nested result set at index. func (rs *ResultSet) ResultSetAt(index int) (*ResultSet, error) { if len(rs.items) < index-1 { return nil, errors.New(ErrIllegalItemIndex, errorMessages, index, len(rs.items)) } resultSet, ok := rs.items[index].(*ResultSet) if !ok { return nil, errors.New(ErrIllegalItemType, errorMessages, index, "result set") } return resultSet, nil } // Values returnes a flattened list of all values. func (rs *ResultSet) Values() Values { values := []Value{} for _, item := range rs.items { switch i := item.(type) { case Value: values = append(values, i) case *ResultSet: values = append(values, i.Values()...) } } return values } // KeyValues returns the alternating values as key/value slice. func (rs *ResultSet) KeyValues() (KeyValues, error) { kvs := KeyValues{} key := "" for index, item := range rs.items { value, ok := item.(Value) if !ok { return nil, errors.New(ErrIllegalItemType, errorMessages, index, "value") } if index%2 == 0 { key = value.String() } else { kvs = append(kvs, KeyValue{key, value}) } } return kvs, nil } // ScoredValues returns the alternating values as scored values slice. If // withscores is false the result set contains no scores and so they are // set to 0.0 in the returned scored values. func (rs *ResultSet) ScoredValues(withscores bool) (ScoredValues, error) { svs := ScoredValues{} sv := ScoredValue{} for index, item := range rs.items { value, ok := item.(Value) if !ok { return nil, errors.New(ErrIllegalItemType, errorMessages, index, "value") } if withscores { // With scores, so alternating values and scores. if index%2 == 0 { sv.Value = value } else { score, err := value.Float64() if err != nil { return nil, err } sv.Score = score svs = append(svs, sv) sv = ScoredValue{} } } else { // No scores, only values. sv.Value = value svs = append(svs, sv) sv = ScoredValue{} } } return svs, nil } // Hash returns the values of the result set as hash. func (rs *ResultSet) Hash() (Hash, error) { hash := make(Hash) key := "" for index, item := range rs.items { value, ok := item.(Value) if !ok { return nil, errors.New(ErrIllegalItemType, errorMessages, index, "value") } if index%2 == 0 { key = value.String() } else { hash.Set(key, value.Bytes()) } } return hash, nil } // Scanned returns the cursor and the keys or values of a // scan operation. func (rs *ResultSet) Scanned() (int, *ResultSet, error) { cursor, err := rs.IntAt(0) if err != nil { return 0, nil, err } result, err := rs.ResultSetAt(1) return cursor, result, err } // Strings returns all values/arrays of the array as a slice of strings. func (rs *ResultSet) Strings() []string { ss := make([]string, len(rs.items)) for index, item := range rs.items { s, ok := item.(fmt.Stringer) if !ok { // Must not happen! panic("illegal type in array") } ss[index] = s.String() } return ss } // String returns the result set in a human readable form. func (rs *ResultSet) String() string { out := "RESULT SET (" ss := rs.Strings() return out + strings.Join(ss, " / ") + ")" } // EOF golib-4.24.2/redis/subscription.go000066400000000000000000000066571315505703200170770ustar00rootroot00000000000000// Tideland Go Library - Redis Client - Subscription // // Copyright (C) 2009-2017 Frank Mueller / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package redis //-------------------- // IMPORTS //-------------------- import ( "strings" "github.com/tideland/golib/errors" ) //-------------------- // SUBSCRIPTION //-------------------- // Subscription manages a subscription to Redis channels and allows // to subscribe and unsubscribe from channels. type Subscription struct { database *Database resp *resp } // newSubscription creates a new subscription. func newSubscription(db *Database) (*Subscription, error) { sub := &Subscription{ database: db, } err := sub.ensureProtocol() if err != nil { return nil, err } // Perform authentication and database selection. err = sub.resp.authenticate() if err != nil { sub.database.pool.kill(sub.resp) return nil, err } return sub, nil } // Subscribe adds one or more channels to the subscription. func (sub *Subscription) Subscribe(channels ...string) error { return sub.subUnsub("subscribe", channels...) } // Unsubscribe removes one or more channels from the subscription. func (sub *Subscription) Unsubscribe(channels ...string) error { return sub.subUnsub("unsubscribe", channels...) } // subUnsub is the generic subscription and unsubscription method. func (sub *Subscription) subUnsub(cmd string, channels ...string) error { err := sub.ensureProtocol() if err != nil { return err } pattern := false args := []interface{}{} for _, channel := range channels { if containsPattern(channel) { pattern = true } args = append(args, channel) } if pattern { cmd = "p" + cmd } err = sub.resp.sendCommand(cmd, args...) logCommand(cmd, args, err, sub.database.logging) return err } // Pop waits for a published value and returns it. func (sub *Subscription) Pop() (*PublishedValue, error) { err := sub.ensureProtocol() if err != nil { return nil, err } result, err := sub.resp.receiveResultSet() if err != nil { return nil, err } // Analyse the result. kind, err := result.StringAt(0) if err != nil { return nil, err } switch { case strings.Contains(kind, "message"): channel, err := result.StringAt(1) if err != nil { return nil, err } value, err := result.ValueAt(2) if err != nil { return nil, err } return &PublishedValue{ Kind: kind, Channel: channel, Value: value, }, nil case strings.Contains(kind, "subscribe"): channel, err := result.StringAt(1) if err != nil { return nil, err } count, err := result.IntAt(2) if err != nil { return nil, err } return &PublishedValue{ Kind: kind, Channel: channel, Count: count, }, nil default: return nil, errors.New(ErrInvalidResponse, errorMessages, result) } } // Close ends the subscription. func (sub *Subscription) Close() error { err := sub.ensureProtocol() if err != nil { return err } err = sub.resp.sendCommand("punsubscribe") if err != nil { return err } for { pv, err := sub.Pop() if err != nil { return err } if pv.Kind == "punsubscribe" { break } } sub.database.pool.push(sub.resp) return nil } // ensureProtocol retrieves a protocol from the pool if needed. func (sub *Subscription) ensureProtocol() error { if sub.resp == nil { p, err := sub.database.pool.pull(forcedPull) if err != nil { return err } sub.resp = p } return nil } // EOF golib-4.24.2/redis/tools.go000066400000000000000000000071331315505703200155010ustar00rootroot00000000000000// Tideland Go Library - Redis Client - Tools // // Copyright (C) 2009-2017 Frank Mueller / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package redis //-------------------- // IMPORTS //-------------------- import ( "fmt" "strconv" "strings" "github.com/tideland/golib/errors" "github.com/tideland/golib/logger" ) //-------------------- // TOOLS //-------------------- // valuer describes any type able to return a list of values. type valuer interface { Len() int Values() []Value } // join builds a byte slice out of some parts. func join(parts ...interface{}) []byte { tmp := []byte{} for _, part := range parts { switch typedPart := part.(type) { case []byte: tmp = append(tmp, typedPart...) case string: tmp = append(tmp, []byte(typedPart)...) case int: tmp = append(tmp, []byte(strconv.Itoa(typedPart))...) default: tmp = append(tmp, []byte(fmt.Sprintf("%v", typedPart))...) } } return tmp } // valueToBytes converts a value into a byte slice. func valueToBytes(value interface{}) []byte { switch typedValue := value.(type) { case string: return []byte(typedValue) case []byte: return typedValue case []string: return []byte(strings.Join(typedValue, "\r\n")) case map[string]string: tmp := make([]string, len(typedValue)) i := 0 for k, v := range typedValue { tmp[i] = fmt.Sprintf("%v:%v", k, v) i++ } return []byte(strings.Join(tmp, "\r\n")) case Hash: tmp := []byte{} for k, v := range typedValue { kb := valueToBytes(k) vb := valueToBytes(v) tmp = append(tmp, kb...) tmp = append(tmp, vb...) } return tmp } return []byte(fmt.Sprintf("%v", value)) } // keyValueArgsToKeys converts a mixed number of keys and values // into a slice containing the keys. func keyValueArgsToKeys(kvs ...interface{}) []string { keys := []string{} ok := true for _, k := range kvs { if ok { key := string(valueToBytes(k)) keys = append(keys, key) } ok = !ok } return keys } // buildInterfaces creates a slice of interfaces out // of the passed arguments. Found string or interface // slices are flattened. func buildInterfaces(values ...interface{}) []interface{} { ifcs := []interface{}{} for _, value := range values { switch v := value.(type) { case []string: for _, s := range v { ifcs = append(ifcs, s) } case []interface{}: for _, i := range v { ifcs = append(ifcs, i) } default: ifcs = append(ifcs, v) } } return ifcs } // containsPatterns checks, if the channel contains a pattern // to subscribe to or unsubscribe from multiple channels. func containsPattern(channel interface{}) bool { ch := channel.(string) if strings.IndexAny(ch, "*?[") != -1 { return true } return false } // logCommand logs a command and its execution status. func logCommand(cmd string, args []interface{}, err error, log bool) { // Format the command for the log entry. formatArgs := func() string { if args == nil || len(args) == 0 { return "(none)" } output := make([]string, len(args)) for i, arg := range args { output[i] = string(valueToBytes(arg)) } return strings.Join(output, " / ") } logOutput := func() string { format := "CMD %s ARGS %s %s" if err == nil { return fmt.Sprintf(format, cmd, formatArgs(), "OK") } return fmt.Sprintf(format, cmd, formatArgs(), "ERROR "+err.Error()) } // Log positive commands only if wanted, errors always. if err != nil { if errors.IsError(err, ErrServerResponse) || errors.IsError(err, ErrTimeout) { return } logger.Errorf(logOutput()) } else if log { logger.Infof(logOutput()) } } // EOF golib-4.24.2/redis/values.go000066400000000000000000000170001315505703200156320ustar00rootroot00000000000000// Tideland Go Library - Redis Client - Values // // Copyright (C) 2009-2017 Frank Mueller / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package redis //-------------------- // IMPORTS //-------------------- import ( "fmt" "strconv" "strings" "github.com/tideland/golib/errors" ) //-------------------- // VALUE //-------------------- // Value is simply a byte slice. type Value []byte // NewValue creates a value out of the passed data. func NewValue(value interface{}) Value { return Value(valueToBytes(value)) } // String returns the value as string (alternative to type conversion). func (v Value) String() string { if v == nil { return "(nil)" } return string([]byte(v)) } // IsOK returns true if the value is the Redis OK value. func (v Value) IsOK() bool { return v.String() == "+OK" } // IsNil returns true if the value is the Redis nil value. func (v Value) IsNil() bool { return v == nil } // Bool return the value as bool. func (v Value) Bool() (bool, error) { b, err := strconv.ParseBool(v.String()) if err != nil { return false, v.invalidTypeError(err, "bool") } return b, nil } // Int returns the value as int. func (v Value) Int() (int, error) { i, err := strconv.Atoi(v.String()) if err != nil { return 0, v.invalidTypeError(err, "int") } return i, nil } // Int64 returns the value as int64. func (v Value) Int64() (int64, error) { i, err := strconv.ParseInt(v.String(), 10, 64) if err != nil { return 0, v.invalidTypeError(err, "int64") } return i, nil } // Uint64 returns the value as uint64. func (v Value) Uint64() (uint64, error) { i, err := strconv.ParseUint(v.String(), 10, 64) if err != nil { return 0, v.invalidTypeError(err, "uint64") } return i, nil } // Float64 returns the value as float64. func (v Value) Float64() (float64, error) { f, err := strconv.ParseFloat(v.String(), 64) if err != nil { return 0.0, v.invalidTypeError(err, "float64") } return f, nil } // Bytes returns the value as byte slice. func (v Value) Bytes() []byte { return []byte(v) } // StringSlice returns the value as slice of strings when separated by CRLF. func (v Value) StringSlice() []string { return strings.Split(v.String(), "\r\n") } // StringMap returns the value as a map of strings when separated by CRLF // and colons between key and value. func (v Value) StringMap() map[string]string { tmp := v.StringSlice() m := make(map[string]string, len(tmp)) for _, s := range tmp { kv := strings.Split(s, ":") if len(kv) > 1 { m[strings.TrimSpace(kv[0])] = strings.TrimSpace(kv[1]) } } return m } // Unpack removes the braces of a list value. func (v Value) Unpack() Value { if len(v) > 2 && v[0] == '[' && v[len(v)-1] == ']' { return Value(v[1 : len(v)-1]) } return v } // invalidTypeError returns an annotated error if a value access has // been unsuccessful. func (v Value) invalidTypeError(err error, descr string) error { return errors.Annotate(err, ErrInvalidType, errorMessages, v.String(), descr) } // Values is a set of values. type Values []Value // Len returns the number of values. func (vs Values) Len() int { return len(vs) } // Strings returns all values as strings. func (vs Values) Strings() []string { ss := make([]string, len(vs)) for i, v := range vs { ss[i] = v.String() } return ss } //-------------------- // KEY/VALUE //-------------------- // KeyValue combines a key and a value type KeyValue struct { Key string Value Value } // String returs the key/value pair as string. func (kv KeyValue) String() string { return fmt.Sprintf("%s = %v", kv.Key, kv.Value) } // KeyValues is a set of KeyValues. type KeyValues []KeyValue // Len returns the number of keys and values in the set. func (kvs KeyValues) Len() int { return len(kvs) } // String returs the key/value pairs as string. func (kvs KeyValues) String() string { kvss := []string{} for _, kv := range kvs { kvss = append(kvss, kv.String()) } return fmt.Sprintf("[%s]", strings.Join(kvss, " / ")) } //-------------------- // SCORED VALUE //-------------------- // ScoredValue helps to add a set member together with its score. type ScoredValue struct { Score float64 Value Value } // String returs the scored value as string. func (sv ScoredValue) String() string { return fmt.Sprintf("%v (%f)", sv.Value, sv.Score) } // ScoredValues is a set of ScoreValues. type ScoredValues []ScoredValue // Len returns the number of scored values in the set. func (svs ScoredValues) Len() int { return len(svs) } // String returs the scored values as string. func (svs ScoredValues) String() string { svss := []string{} for _, sv := range svs { svss = append(svss, sv.String()) } return fmt.Sprintf("[%s]", strings.Join(svss, " / ")) } //-------------------- // HASH //-------------------- // Hash maps multiple fields of a hash to the // according result values. type Hash map[string]Value // NewHash creates a new empty hash. func NewHash() Hash { return make(Hash) } // NewFilledHash creates a hash with the passed keys and values. func NewFilledHash(kvs map[string]interface{}) Hash { h := NewHash() for k, v := range kvs { h.Set(k, v) } return h } // Len returns the number of elements in the hash. func (h Hash) Len() int { return len(h) } // Set sets a key to the given value. func (h Hash) Set(key string, value interface{}) Hash { h[key] = Value(valueToBytes(value)) return h } // String returns the value of a key as string. func (h Hash) String(key string) (string, error) { if value, ok := h[key]; ok { return value.String(), nil } return "", errors.New(ErrInvalidKey, errorMessages, key) } // Bool returns the value of a key as bool. func (h Hash) Bool(key string) (bool, error) { if value, ok := h[key]; ok { return value.Bool() } return false, errors.New(ErrInvalidKey, errorMessages, key) } // Int returns the value of a key as int. func (h Hash) Int(key string) (int, error) { if value, ok := h[key]; ok { return value.Int() } return 0, errors.New(ErrInvalidKey, errorMessages, key) } // Int64 returns the value of a key as int64. func (h Hash) Int64(key string) (int64, error) { if value, ok := h[key]; ok { return value.Int64() } return 0, errors.New(ErrInvalidKey, errorMessages, key) } // Uint64 returns the value of a key as uint64. func (h Hash) Uint64(key string) (uint64, error) { if value, ok := h[key]; ok { return value.Uint64() } return 0, errors.New(ErrInvalidKey, errorMessages, key) } // Float64 returns the value of a key as float64. func (h Hash) Float64(key string) (float64, error) { if value, ok := h[key]; ok { return value.Float64() } return 0.0, errors.New(ErrInvalidKey, errorMessages, key) } // Bytes returns the value of a key as byte slice. func (h Hash) Bytes(key string) []byte { if value, ok := h[key]; ok { return value.Bytes() } return []byte{} } // StringSlice returns the value of a key as string slice. func (h Hash) StringSlice(key string) []string { if value, ok := h[key]; ok { return value.StringSlice() } return []string{} } // StringMap returns the value of a key as string map. func (h Hash) StringMap(key string) map[string]string { if value, ok := h[key]; ok { return value.StringMap() } return map[string]string{} } // Hashable represents types for Redis hashes. type Hashable interface { Len() int GetHash() Hash SetHash(h Hash) } //-------------------- // PUBLISHED VALUE //-------------------- // PublishedValue contains a published value and its channel // channel pattern. type PublishedValue struct { Kind string Channel string Count int Value Value } // EOF golib-4.24.2/scene/000077500000000000000000000000001315505703200137755ustar00rootroot00000000000000golib-4.24.2/scene/doc.go000066400000000000000000000054041315505703200150740ustar00rootroot00000000000000// Tideland Go Library - Scene // // Copyright (C) 2014-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. // Package scene of the Tideland Go Library provides a shared access to common // used data in a larger context. // // By definition a scene is a sequence of continuous action in a play, // movie, opera, or book. Applications do know these kind of scenes too, // especially in concurrent software. Here aspects of the action have to // passed between the actors in a secure way, very often they are interwoven // and depending. // // Here the scene package helps. Beside a simple atomic way to store and // fetch information together with optional cleanup functions it handles // inactivity and absolute timeouts. // // A scene without timeouts is started with // // scn := scene.Start() // // Now props can be stored, fetched, and disposed. // // err := scn.Store("foo", myFoo) // foo, err := scn.Fetch("foo") // foo, err := scn.Dispose("foo") // // It's also possible to cleanup if a prop is disposed or the whole // scene is stopped or aborted. // // myCleanup := func(key string, prop interface{}) error { // // Cleanup, e.g. return the prop into a pool // // or close handles. // ... // return nil // } // err := scn.StoreClean("foo", myFoo, myCleanup) // // The cleanup is called individually per prop when disposing it, when the // scene ends due to a timeout, or when it is stopped with // // err := scn.Stop() // // or // // scn.Abort(myError) // // Another functionality of the scene is the signaling of a topic. So // multiple goroutines can wait for a signal with a topic, all will be // notified after the topic has been signaled. Additionally they can wait // with a timeout. // // go func() { // err := scn.WaitFlag("foo") // ... // }() // go func() { // err := scn.WaitFlagLimited("foo", 5 * time.Second) // ... // }() // err := scn.Flag("foo") // // In case a flag is already signaled wait immediatily returns. Store() // and Flag() can also be combined to StoreAndFlag(). This way the key // will be used as flag topic and a waiter knows that the information is // available. // // A scene knows two different timeouts. The first is the time of inactivity, // the second is the absolute maximum time of a scene. // // inactivityTimeout := 5 * time.Minutes // absoluteTimeout := 60 * time.Minutes // scn := scene.StartLimited(inactivityTimeout, absoluteTimeout) // // Now the scene is stopped after 5 minutes without any access or at the // latest 60 minutes after the start. Both value may be zero if not needed. // So scene.StartLimited(0, 0) is the same as scene.Start(). package scene // EOF golib-4.24.2/scene/errors.go000066400000000000000000000040131315505703200156360ustar00rootroot00000000000000// Tideland Go Library - Scene // // Copyright (C) 2014-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package scene //-------------------- // IMPORTS //-------------------- import ( "github.com/tideland/golib/errors" ) //-------------------- // CONSTANTS //-------------------- // Error codes of the scene package. const ( ErrSceneEnded = iota + 1 ErrTimeout ErrPropAlreadyExist ErrPropNotFound ErrCleanupFailed ErrWaitedTooLong ) var errorMessages = errors.Messages{ ErrSceneEnded: "scene already ended", ErrTimeout: "scene %s timeout reached at %v", ErrPropAlreadyExist: "property %q already exist", ErrPropNotFound: "property %q does not exist", ErrCleanupFailed: "cleanup of property %q failed", ErrWaitedTooLong: "waiting for signal %q timed out", } //-------------------- // TESTING //-------------------- // IsSceneEndedError returns true, if the error signals that // the scene isn't active anymore. func IsSceneEndedError(err error) bool { return errors.IsError(err, ErrSceneEnded) } // IsTimeoutError returns true, if the error signals that // the scene end after an absolute timeout. func IsTimeoutError(err error) bool { return errors.IsError(err, ErrTimeout) } // IsPropAlreadyExistError returns true, if the error signals a // double prop key. func IsPropAlreadyExistError(err error) bool { return errors.IsError(err, ErrPropAlreadyExist) } // IsPropNotFoundError returns true, if the error signals a // non-existing prop. func IsPropNotFoundError(err error) bool { return errors.IsError(err, ErrPropNotFound) } // IsCleanupFailedError returns true, if the error signals the // failing of a prop error. func IsCleanupFailedError(err error) bool { return errors.IsError(err, ErrCleanupFailed) } // IsWaitedTooLongError returns true, if the error signals a // timeout when waiting for a signal. func IsWaitedTooLongError(err error) bool { return errors.IsError(err, ErrWaitedTooLong) } // EOF golib-4.24.2/scene/scene.go000066400000000000000000000273701315505703200154320ustar00rootroot00000000000000// Tideland Go Library - Scene // // Copyright (C) 2014-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package scene //-------------------- // IMPORTS //-------------------- import ( "time" "github.com/tideland/golib/errors" "github.com/tideland/golib/identifier" "github.com/tideland/golib/loop" ) //-------------------- // SCENE //-------------------- // Activity states of a scene. const ( Active = loop.Running Finishing = loop.Stopping Over = loop.Stopped ) // CleanupFunc is a function for the cleanup of props after // a scene ended. type CleanupFunc func(key string, prop interface{}) error // box contains a prop and a possible cleanup function. type box struct { key string prop interface{} cleanup CleanupFunc } // signaling contains a topic and a signal channel. type signaling struct { topic string signalChan chan struct{} } const ( storeProp = iota fetchProp disposeProp flag unflag wait ) // envelope contains information transferred between client and scene. type envelope struct { kind int box *box signaling *signaling err error respChan chan *envelope } // Scene is the access point to one scene. It has to be created once // for a continuous flow of operations and then passed between all // functions and goroutine which are actors of the scene. type Scene interface { // ID returns the unique ID of the scene. ID() identifier.UUID // Stop tells the scene to end and waits until it is done. Stop() error // Abort tells the scene to end due to the passed error. // Here only the first error will be stored for later evaluation. Abort(err error) // Wait blocks the caller until the scene ended and returns a // possible error or nil. Wait() error // Status returns information about the current status of the scene. Status() (int, error) // Store stores a prop with a given key. The key must not exist. Store(key string, prop interface{}) error // StoreAndFlag stores a prop with a given key. The key must not exist. // The storing is signaled with the key as topic. StoreAndFlag(key string, prop interface{}) error // StoreClean stores a prop with a given key and a cleanup // function called when a scene ends. The key must not exist. StoreClean(key string, prop interface{}, cleanup CleanupFunc) error // StoreClean stores a prop with a given key and a cleanup // function called when a scene ends. The key must not exist. // The storing is signaled with the key as topic. StoreCleanAndFlag(key string, prop interface{}, cleanup CleanupFunc) error // Fetch retrieves a prop. Fetch(key string) (interface{}, error) // Dispose retrieves a prop and deletes it from the store. Dispose(key string) (interface{}, error) // Flag allows to signal a topic to interested actors. Flag(topic string) error // Unflag drops the signal for a given topic. Unflag(topic string) error // WaitFlag waits until the passed topic has been signaled. WaitFlag(topic string) error // WaitFlagAndFetch waits until the passed topic has been signaled. // A prop stored at the topic as key is fetched. WaitFlagAndFetch(topic string) (interface{}, error) // WaitFlagLimited waits until the passed topic has been signaled // or the timeout happened. WaitFlagLimited(topic string, timeout time.Duration) error // WaitFlagLimitedAndFetch waits until the passed topic has been signaled // or the timeout happened. A prop stored at the topic as key is fetched. WaitFlagLimitedAndFetch(topic string, timeout time.Duration) (interface{}, error) } // scene implements Scene. type scene struct { id identifier.UUID props map[string]*box flags map[string]bool signalings map[string][]chan struct{} inactivity time.Duration absolute time.Duration commandChan chan *envelope backend loop.Loop } // Start creates and runs a new scene. func Start() Scene { return StartLimited(0, 0) } // StartLimited creates and runs a new scene with an inactivity // and an absolute timeout. They may be zero. func StartLimited(inactivity, absolute time.Duration) Scene { s := &scene{ id: identifier.NewUUID(), props: make(map[string]*box), flags: make(map[string]bool), signalings: make(map[string][]chan struct{}), inactivity: inactivity, absolute: absolute, commandChan: make(chan *envelope, 1), } s.backend = loop.Go(s.backendLoop, "scene", s.id.String()) return s } // ID is specified on the Scene interface. func (s *scene) ID() identifier.UUID { return s.id.Copy() } // Stop is specified on the Scene interface. func (s *scene) Stop() error { return s.backend.Stop() } // Abort is specified on the Scene interface. func (s *scene) Abort(err error) { s.backend.Kill(err) } // Wait is specified on the Scene interface. func (s *scene) Wait() error { return s.backend.Wait() } // Status is specified on the Scene interface. func (s *scene) Status() (int, error) { return s.backend.Error() } // Store is specified on the Scene interface. func (s *scene) Store(key string, prop interface{}) error { return s.StoreClean(key, prop, nil) } // StoreAndFlag is specified on the Scene interface. func (s *scene) StoreAndFlag(key string, prop interface{}) error { err := s.StoreClean(key, prop, nil) if err != nil { return err } return s.Flag(key) } // StoreClean is specified on the Scene interface. func (s *scene) StoreClean(key string, prop interface{}, cleanup CleanupFunc) error { command := &envelope{ kind: storeProp, box: &box{ key: key, prop: prop, cleanup: cleanup, }, respChan: make(chan *envelope, 1), } _, err := s.command(command) return err } // StoreCleanAndFlag is specified on the Scene interface. func (s *scene) StoreCleanAndFlag(key string, prop interface{}, cleanup CleanupFunc) error { err := s.StoreClean(key, prop, cleanup) if err != nil { return err } return s.Flag(key) } // Fetch is specified on the Scene interface. func (s *scene) Fetch(key string) (interface{}, error) { command := &envelope{ kind: fetchProp, box: &box{ key: key, }, respChan: make(chan *envelope, 1), } resp, err := s.command(command) if err != nil { return nil, err } return resp.box.prop, nil } // Dispose is specified on the Scene interface. func (s *scene) Dispose(key string) (interface{}, error) { command := &envelope{ kind: disposeProp, box: &box{ key: key, }, respChan: make(chan *envelope, 1), } resp, err := s.command(command) if err != nil { return nil, err } return resp.box.prop, nil } // Flag is specified on the Scene interface. func (s *scene) Flag(topic string) error { command := &envelope{ kind: flag, signaling: &signaling{ topic: topic, }, respChan: make(chan *envelope, 1), } _, err := s.command(command) return err } // Unflag is specified on the Scene interface. func (s *scene) Unflag(topic string) error { command := &envelope{ kind: unflag, signaling: &signaling{ topic: topic, }, respChan: make(chan *envelope, 1), } _, err := s.command(command) return err } // WaitFlag is specified on the Scene interface. func (s *scene) WaitFlag(topic string) error { return s.WaitFlagLimited(topic, 0) } // WaitFlagAndFetch is specified on the Scene interface. func (s *scene) WaitFlagAndFetch(topic string) (interface{}, error) { err := s.WaitFlag(topic) if err != nil { return nil, err } return s.Fetch(topic) } // WaitFlagLimited is specified on the Scene interface. func (s *scene) WaitFlagLimited(topic string, timeout time.Duration) error { // Add signal channel. command := &envelope{ kind: wait, signaling: &signaling{ topic: topic, signalChan: make(chan struct{}, 1), }, respChan: make(chan *envelope, 1), } _, err := s.command(command) if err != nil { return err } // Wait for signal. var timeoutChan <-chan time.Time if timeout > 0 { timeoutChan = time.After(timeout) } select { case <-s.backend.IsStopping(): err = s.Wait() if err == nil { err = errors.New(ErrSceneEnded, errorMessages) } return err case <-command.signaling.signalChan: return nil case <-timeoutChan: return errors.New(ErrWaitedTooLong, errorMessages, topic) } } // WaitFlagLimitedAndFetch is specified on the Scene interface. func (s *scene) WaitFlagLimitedAndFetch(topic string, timeout time.Duration) (interface{}, error) { err := s.WaitFlagLimited(topic, timeout) if err != nil { return nil, err } return s.Fetch(topic) } // command sends a command envelope to the backend and // waits for the response. func (s *scene) command(command *envelope) (*envelope, error) { select { case s.commandChan <- command: case <-s.backend.IsStopping(): err := s.Wait() if err == nil { err = errors.New(ErrSceneEnded, errorMessages) } return nil, err } select { case <-s.backend.IsStopping(): err := s.Wait() if err == nil { err = errors.New(ErrSceneEnded, errorMessages) } return nil, err case resp := <-command.respChan: if resp.err != nil { return nil, resp.err } return resp, nil } } // backendLoop runs the backend loop of the scene. func (s *scene) backendLoop(l loop.Loop) (err error) { // Defer cleanup. defer func() { cerr := s.cleanupAllProps() if err == nil { err = cerr } }() // Init timers. var watchdog <-chan time.Time var clapperboard <-chan time.Time if s.absolute > 0 { clapperboard = time.After(s.absolute) } // Run loop. for { if s.inactivity > 0 { watchdog = time.After(s.inactivity) } select { case <-l.ShallStop(): return nil case timeout := <-watchdog: return errors.New(ErrTimeout, errorMessages, "inactivity", timeout) case timeout := <-clapperboard: return errors.New(ErrTimeout, errorMessages, "absolute", timeout) case command := <-s.commandChan: s.processCommand(command) } } } // processCommand processes the sent commands. func (s *scene) processCommand(command *envelope) { switch command.kind { case storeProp: // Add a new prop. _, ok := s.props[command.box.key] if ok { command.err = errors.New(ErrPropAlreadyExist, errorMessages, command.box.key) } else { s.props[command.box.key] = command.box } case fetchProp: // Retrieve a prop. box, ok := s.props[command.box.key] if !ok { command.err = errors.New(ErrPropNotFound, errorMessages, command.box.key) } else { command.box = box } case disposeProp: // Remove a prop. box, ok := s.props[command.box.key] if !ok { command.err = errors.New(ErrPropNotFound, errorMessages, command.box.key) } else { delete(s.props, command.box.key) command.box = box if box.cleanup != nil { cerr := box.cleanup(box.key, box.prop) if cerr != nil { command.err = errors.Annotate(cerr, ErrCleanupFailed, errorMessages, box.key) } } } case flag: // Signal a topic. s.flags[command.signaling.topic] = true // Notify subscribers. subscribers, ok := s.signalings[command.signaling.topic] if ok { delete(s.signalings, command.signaling.topic) for _, subscriber := range subscribers { subscriber <- struct{}{} } } case unflag: // Drop a topic. delete(s.flags, command.signaling.topic) case wait: // Add a waiter for a topic. active := s.flags[command.signaling.topic] if active { command.signaling.signalChan <- struct{}{} } else { waiters := s.signalings[command.signaling.topic] s.signalings[command.signaling.topic] = append(waiters, command.signaling.signalChan) } default: panic("illegal command") } // Return the changed command as response. command.respChan <- command } // cleanupAllProps cleans all props. func (s *scene) cleanupAllProps() error { for _, box := range s.props { if box.cleanup != nil { err := box.cleanup(box.key, box.prop) if err != nil { return errors.Annotate(err, ErrCleanupFailed, errorMessages, box.key) } } } return nil } // EOF golib-4.24.2/scene/scene_test.go000066400000000000000000000211661315505703200164660ustar00rootroot00000000000000// Tideland Go Library - Scene - Unit Tests // // Copyright (C) 2014-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package scene_test //-------------------- // IMPORTS //-------------------- import ( "errors" "testing" "time" "github.com/tideland/golib/audit" "github.com/tideland/golib/scene" ) //-------------------- // TESTS //-------------------- // TestSimpleNoTimeout tests a simple scene usage without // any timeout. func TestSimpleNoTimeout(t *testing.T) { assert := audit.NewTestingAssertion(t, false) scn := scene.Start() id := scn.ID() assert.Length(id, 16) err := scn.Store("foo", 4711) assert.Nil(err) foo, err := scn.Fetch("foo") assert.Nil(err) assert.Equal(foo, 4711) _, err = scn.Fetch("bar") assert.True(scene.IsPropNotFoundError(err)) err = scn.Store("foo", "bar") assert.True(scene.IsPropAlreadyExistError(err)) _, err = scn.Dispose("bar") assert.True(scene.IsPropNotFoundError(err)) foo, err = scn.Dispose("foo") assert.Nil(err) assert.Equal(foo, 4711) _, err = scn.Fetch("foo") assert.True(scene.IsPropNotFoundError(err)) status, err := scn.Status() assert.Nil(err) assert.Equal(status, scene.Active) err = scn.Stop() assert.Nil(err) status, err = scn.Status() assert.Nil(err) assert.Equal(status, scene.Over) } // TestAccessAfterStopping tests an access after the // scene already has been stopped. func TestAccessAfterStopping(t *testing.T) { assert := audit.NewTestingAssertion(t, false) scn := scene.Start() err := scn.Store("foo", 4711) assert.Nil(err) foo, err := scn.Fetch("foo") assert.Nil(err) assert.Equal(foo, 4711) err = scn.Stop() assert.Nil(err) foo, err = scn.Fetch("foo") assert.True(scene.IsSceneEndedError(err)) assert.Nil(foo) } // TestCleanupNoError tests the cleanup of props with // no errors. func TestCleanupNoError(t *testing.T) { assert := audit.NewTestingAssertion(t, false) cleanups := make(map[string]interface{}) cleanup := func(key string, prop interface{}) error { cleanups[key] = prop return nil } scn := scene.Start() err := scn.StoreClean("foo", 4711, cleanup) assert.Nil(err) err = scn.StoreClean("bar", "yadda", cleanup) assert.Nil(err) foo, err := scn.Dispose("foo") assert.Nil(err) assert.Equal(foo, 4711) err = scn.Stop() assert.Nil(err) assert.Length(cleanups, 2) assert.Equal(cleanups["foo"], 4711) assert.Equal(cleanups["bar"], "yadda") } // TestCleanupWithErrors tests the cleanup of props with errors. func TestCleanupWithErrors(t *testing.T) { assert := audit.NewTestingAssertion(t, false) cleanup := func(key string, prop interface{}) error { return errors.New("ouch") } scn := scene.Start() err := scn.StoreClean("foo", 4711, cleanup) assert.Nil(err) err = scn.StoreClean("bar", true, cleanup) assert.Nil(err) err = scn.StoreClean("yadda", "OK", cleanup) assert.Nil(err) foo, err := scn.Dispose("foo") assert.True(scene.IsCleanupFailedError(err)) assert.Nil(foo) bar, err := scn.Fetch("bar") assert.Nil(err) assert.Equal(bar, true) err = scn.Stop() assert.True(scene.IsCleanupFailedError(err)) } // TestSimpleInactivityTimeout tests a simple scene usage // with inactivity timeout. func TestSimpleInactivityTimeout(t *testing.T) { assert := audit.NewTestingAssertion(t, false) scn := scene.StartLimited(100*time.Millisecond, 0) err := scn.Store("foo", 4711) assert.Nil(err) for i := 0; i < 5; i++ { foo, err := scn.Fetch("foo") assert.Nil(err) assert.Equal(foo, 4711) time.Sleep(50) } time.Sleep(100 * time.Millisecond) foo, err := scn.Fetch("foo") assert.True(scene.IsTimeoutError(err)) assert.Nil(foo) status, err := scn.Status() assert.True(scene.IsTimeoutError(err)) assert.Equal(status, scene.Over) err = scn.Stop() assert.True(scene.IsTimeoutError(err)) } // TestSimpleAbsoluteTimeout tests a simple scene usage // with absolute timeout. func TestSimpleAbsoluteTimeout(t *testing.T) { assert := audit.NewTestingAssertion(t, false) scn := scene.StartLimited(0, 250*time.Millisecond) err := scn.Store("foo", 4711) assert.Nil(err) for { _, err = scn.Fetch("foo") if err != nil { assert.True(scene.IsTimeoutError(err)) break } time.Sleep(50) } err = scn.Stop() assert.True(scene.IsTimeoutError(err)) } // TestCleanupAfterTimeout tests the cleanup of props after // a timeout. func TestCleanupAfterTimeout(t *testing.T) { assert := audit.NewTestingAssertion(t, false) cleanups := make(map[string]interface{}) cleanup := func(key string, prop interface{}) error { cleanups[key] = prop return nil } scn := scene.StartLimited(0, 100*time.Millisecond) err := scn.StoreClean("foo", 4711, cleanup) assert.Nil(err) err = scn.StoreClean("bar", "yadda", cleanup) assert.Nil(err) time.Sleep(250 * time.Millisecond) err = scn.Stop() assert.True(scene.IsTimeoutError(err)) assert.Length(cleanups, 2) assert.Equal(cleanups["foo"], 4711) assert.Equal(cleanups["bar"], "yadda") } // TestAbort tests the aborting of a scene. A cleanup error // will not be reported. func TestAbort(t *testing.T) { assert := audit.NewTestingAssertion(t, false) cleanup := func(key string, prop interface{}) error { return errors.New("ouch") } scn := scene.Start() err := scn.StoreClean("foo", 4711, cleanup) assert.Nil(err) scn.Abort(errors.New("aborted")) foo, err := scn.Fetch("foo") assert.ErrorMatch(err, "aborted") assert.Nil(foo) err = scn.Stop() assert.ErrorMatch(err, "aborted") } // TestFlagNoTimeout tests the waiting for a signal without // a timeout. func TestFlagNoTimeout(t *testing.T) { assert := audit.NewTestingAssertion(t, false) scn := scene.Start() go func() { err := scn.WaitFlag("foo") assert.Nil(err) err = scn.Store("foo-a", true) assert.Nil(err) }() go func() { err := scn.WaitFlag("foo") assert.Nil(err) err = scn.Store("foo-b", true) assert.Nil(err) }() time.Sleep(100 * time.Millisecond) err := scn.Flag("foo") assert.Nil(err) time.Sleep(250 * time.Millisecond) fooA, err := scn.Fetch("foo-a") assert.Nil(err) assert.Equal(fooA, true) fooB, err := scn.Fetch("foo-b") assert.Nil(err) assert.Equal(fooB, true) err = scn.Stop() assert.Nil(err) } // TestNoFlagDueToStop tests the waiting for a signal while // a scene is stopped. func TestNoFlagDueToStop(t *testing.T) { assert := audit.NewTestingAssertion(t, false) scn := scene.Start() go func() { err := scn.WaitFlag("foo") assert.True(scene.IsSceneEndedError(err)) }() go func() { err := scn.WaitFlag("foo") assert.True(scene.IsSceneEndedError(err)) }() time.Sleep(100 * time.Millisecond) err := scn.Stop() assert.Nil(err) } // TestStoreAndFlag tests the signaling after storing // after value. func TestStoreAndFlag(t *testing.T) { assert := audit.NewTestingAssertion(t, false) scn := scene.Start() go func() { time.Sleep(100 * time.Millisecond) err := scn.StoreAndFlag("foo", 4711) assert.Nil(err) }() err := scn.WaitFlag("foo") assert.Nil(err) foo, err := scn.Fetch("foo") assert.Nil(err) assert.Equal(foo, 4711) err = scn.Stop() assert.Nil(err) } // TestEarlyFlag tests the signaling before a waiting. func TestEarlyFlag(t *testing.T) { assert := audit.NewTestingAssertion(t, false) scn := scene.Start() err := scn.Flag("foo") assert.Nil(err) go func() { err := scn.WaitFlag("foo") assert.Nil(err) err = scn.Store("foo-a", true) assert.Nil(err) }() go func() { err := scn.WaitFlag("foo") assert.Nil(err) err = scn.Store("foo-b", true) assert.Nil(err) }() time.Sleep(100 * time.Millisecond) fooA, err := scn.Fetch("foo-a") assert.Nil(err) assert.Equal(fooA, true) fooB, err := scn.Fetch("foo-b") assert.Nil(err) assert.Equal(fooB, true) err = scn.Stop() assert.Nil(err) } // TestFlagTimeout tests the waiting for a signal with // a timeout. func TestFlagTimeout(t *testing.T) { assert := audit.NewTestingAssertion(t, false) doneC := audit.MakeSigChan() scn := scene.Start() go func() { err := scn.WaitFlag("foo") assert.Nil(err) doneC <- true }() go func() { err := scn.WaitFlagLimited("foo", 50*time.Millisecond) assert.True(scene.IsWaitedTooLongError(err)) doneC <- true }() time.Sleep(100 * time.Millisecond) err := scn.Flag("foo") assert.Nil(err) assert.Wait(doneC, true, time.Second) assert.Wait(doneC, true, time.Second) err = scn.Stop() assert.Nil(err) } // TestFlagUnflag tests the removal of a flag. func TestFlagUnflag(t *testing.T) { assert := audit.NewTestingAssertion(t, false) scn := scene.Start() err := scn.Flag("foo") assert.Nil(err) err = scn.Unflag("foo") assert.Nil(err) err = scn.WaitFlagLimited("foo", 50*time.Millisecond) assert.True(scene.IsWaitedTooLongError(err)) err = scn.Stop() assert.Nil(err) } // EOF golib-4.24.2/scroller/000077500000000000000000000000001315505703200145255ustar00rootroot00000000000000golib-4.24.2/scroller/doc.go000066400000000000000000000013531315505703200156230ustar00rootroot00000000000000// Tideland Go Library - Scroller // // Copyright (C) 2014-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. // Package scroller of the Tideland Go Library helps analyzing a continuously // written line by line content, e.g. at the monitoring of log files. // Here the Scroller is working in the background and allows to read out of // any ReadSeeker (which may be a File) from beginning, end or a given number // of lines before the end, filter the output by a filter function and write // it into a Writer. If a number of lines and a filter are passed the Scroller // tries to find that number of lines matching to the filter. package scroller // EOF golib-4.24.2/scroller/errors.go000066400000000000000000000024541315505703200163750ustar00rootroot00000000000000// Tideland Go Library - Scroller // // Copyright (C) 2014-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package scroller //-------------------- // IMPORTS //-------------------- import ( "github.com/tideland/golib/errors" ) //-------------------- // CONSTANTS //-------------------- // Error codes of the scroller package. const ( ErrNoSource = iota + 1 ErrNoTarget ErrNegativeLines ) var errorMessages = errors.Messages{ ErrNoSource: "cannot start scroller: no source", ErrNoTarget: "cannot start scroller: no target", ErrNegativeLines: "negative number of lines not allowed: %d", } //-------------------- // TESTING //-------------------- // IsNoSourceError returns true, if the error signals that // no source has been passed. func IsNoSourceError(err error) bool { return errors.IsError(err, ErrNoSource) } // IsNoTargetError returns true, if the error signals that // no target has been passed. func IsNoTargetError(err error) bool { return errors.IsError(err, ErrNoTarget) } // IsNegativeLinesError returns true, if the error shows the // setting of a negative number of lines to start with. func IsNegativeLinesError(err error) bool { return errors.IsError(err, ErrNegativeLines) } // EOF golib-4.24.2/scroller/scroller.go000066400000000000000000000154341315505703200167100ustar00rootroot00000000000000// Tideland Go Library - Scroller // // Copyright (C) 2014-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package scroller //-------------------- // IMPORTS //-------------------- import ( "bufio" "bytes" "io" "os" "time" "github.com/tideland/golib/errors" "github.com/tideland/golib/loop" ) //-------------------- // CONSTANTS //-------------------- const ( defaultBufferSize = 4096 defaultPollTime = time.Second delimiter = '\n' ) var ( delimiters = []byte{delimiter} ) //-------------------- // FILTER //-------------------- // FilterFunc decides if a line shall be scrolled (func is nil or // returns true) or not (func returns false). type FilterFunc func(line []byte) bool //-------------------- // OPTIONS //-------------------- // Option defines a function setting an option. type Option func(s *Scroller) error // Lines sets the number of lines ro scroll initially. func Lines(l int) Option { return func(s *Scroller) error { if l < 0 { return errors.New(ErrNegativeLines, errorMessages, l) } s.lines = l return nil } } // Filter sets the filter function of the scroller. func Filter(ff FilterFunc) Option { return func(s *Scroller) error { s.filter = ff return nil } } // BufferSize allows to set the initial size of the buffer // used for reading. func BufferSize(bs int) Option { return func(s *Scroller) error { s.bufferSize = bs return nil } } // PollTime defines the frequency the source is polled. func PollTime(pt time.Duration) Option { return func(s *Scroller) error { if pt == 0 { pt = defaultPollTime } s.pollTime = pt return nil } } //-------------------- // SCROLLER //-------------------- // Scroller scrolls and filters a ReadSeeker line by line and // writes the data into a Writer. type Scroller struct { source io.ReadSeeker target io.Writer lines int filter FilterFunc bufferSize int pollTime time.Duration reader *bufio.Reader writer *bufio.Writer loop loop.Loop } // NewScroller starts a Scroller for the given source and target. // The options can control the number of lines, a filter, the buffer // size and the poll time. func NewScroller(source io.ReadSeeker, target io.Writer, options ...Option) (*Scroller, error) { if source == nil { return nil, errors.New(ErrNoSource, errorMessages) } if target == nil { return nil, errors.New(ErrNoTarget, errorMessages) } s := &Scroller{ source: source, target: target, bufferSize: defaultBufferSize, pollTime: defaultPollTime, } for _, option := range options { if err := option(s); err != nil { return nil, err } } s.reader = bufio.NewReaderSize(s.source, s.bufferSize) s.writer = bufio.NewWriter(s.target) s.loop = loop.Go(s.backendLoop, "scroller") return s, nil } // Stop tells the scroller to end working. func (s *Scroller) Stop() error { return s.loop.Stop() } // Wait blocks until the scroller has stopped. func (s *Scroller) Wait() error { return s.loop.Wait() } // Error returns the status and a possible error of the scroller. func (s *Scroller) Error() (int, error) { return s.loop.Error() } // backendLoop is the goroutine for reading, filtering and writing. func (s *Scroller) backendLoop(l loop.Loop) error { // Initial positioning. if err := s.seekInitial(); err != nil { return err } // Polling loop. timer := time.NewTimer(0) for { select { case <-l.ShallStop(): return nil case <-timer.C: for { line, readErr := s.readLine() _, writeErr := s.writer.Write(line) if writeErr != nil { return writeErr } if readErr != nil { if readErr != io.EOF { return readErr } break } } if writeErr := s.writer.Flush(); writeErr != nil { return writeErr } timer.Reset(s.pollTime) } } } // seekInitial sets the initial position to start reading. This // position depends on the number lines and the filter st. func (s *Scroller) seekInitial() error { offset, err := s.source.Seek(0, os.SEEK_END) if err != nil { return err } if s.lines < 1 { // Simple case, no initial lines wanted. return nil } seekPos := int64(0) found := 0 buffer := make([]byte, s.bufferSize) SeekLoop: for offset > 0 { // bufferf partly filled, check if large enough. space := cap(buffer) - len(buffer) if space < s.bufferSize { // Grow buffer. newBuffer := make([]byte, len(buffer), cap(buffer)*2) copy(newBuffer, buffer) buffer = newBuffer space = cap(buffer) - len(buffer) } if int64(space) > offset { // Use exactly the right amount of space if there's // only a small amount remaining. space = int(offset) } // Copy remaining data to the end of the buffer. copy(buffer[space:cap(buffer)], buffer) buffer = buffer[0 : len(buffer)+space] offset -= int64(space) _, err := s.source.Seek(offset, os.SEEK_SET) if err != nil { return err } // Read into the beginning of the buffer. _, err = io.ReadFull(s.source, buffer[0:space]) if err != nil { return err } // Find the end of the last line in the buffer. // This will discard any unterminated line at the end // of the file. end := bytes.LastIndex(buffer, delimiters) if end == -1 { // No end of line found - discard incomplete // line and continue looking. If this happens // at the beginning of the file, we don't care // because we're going to stop anyway. buffer = buffer[:0] continue } end++ for { start := bytes.LastIndex(buffer[0:end-1], delimiters) if start == -1 && offset >= 0 { break } start++ if s.isValid(buffer[start:end]) { found++ if found >= s.lines { seekPos = offset + int64(start) break SeekLoop } } end = start } // Leave the last line in the buffer. It's not // clear if it is complete or not. buffer = buffer[0:end] } // Final positioning. s.source.Seek(seekPos, os.SEEK_SET) return nil } // readLine reads the next valid line from the reader, even if it is // larger than the reader buffer. func (s *Scroller) readLine() ([]byte, error) { for { slice, err := s.reader.ReadSlice(delimiter) if err == nil { if s.isValid(slice) { return slice, nil } continue } line := append([]byte(nil), slice...) for err == bufio.ErrBufferFull { slice, err = s.reader.ReadSlice(delimiter) line = append(line, slice...) } switch err { case nil: if s.isValid(line) { return line, nil } case io.EOF: // Reached EOF without a delimiter, // so step back for next time. s.source.Seek(-int64(len(line)), os.SEEK_CUR) return nil, err default: return nil, err } } } // isValid checks if the passed line is valid by using a // possibly set filter. func (s *Scroller) isValid(line []byte) bool { if s.filter == nil { return true } return s.filter(line) } // EOF golib-4.24.2/scroller/scroller_test.go000066400000000000000000000241431315505703200177440ustar00rootroot00000000000000// Tideland Go Library - Scroller - Unit Tests // // Copyright (C) 2014-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package scroller_test //-------------------- // IMPORTS //-------------------- import ( "bufio" "bytes" "errors" "fmt" "io" "sync" "testing" "time" "github.com/tideland/golib/audit" "github.com/tideland/golib/loop" "github.com/tideland/golib/scroller" ) //-------------------- // TESTS //-------------------- // tests contains the data descibing the tests. var tests = []struct { description string initialLeg int options func() []scroller.Option initialExpected []int appendedExpected []int injector func(*scroller.Scroller, *readSeeker) func([]string) err string }{{ description: "no lines existing; initially no lines scrolled", options: func() []scroller.Option { return []scroller.Option{ scroller.PollTime(2 * time.Millisecond), } }, initialExpected: []int{}, appendedExpected: intRange(0, 99), }, { description: "no lines existing; initially five lines scrolled", options: func() []scroller.Option { return []scroller.Option{ scroller.Lines(5), scroller.PollTime(2 * time.Millisecond), } }, initialExpected: []int{}, appendedExpected: intRange(0, 99), }, { description: "ten lines existing; initially no lines scrolled", initialLeg: 10, options: func() []scroller.Option { return []scroller.Option{ scroller.PollTime(2 * time.Millisecond), } }, initialExpected: []int{}, appendedExpected: intRange(10, 99), }, { description: "ten lines existing; initially five lines scrolled", initialLeg: 10, options: func() []scroller.Option { return []scroller.Option{ scroller.Lines(5), scroller.PollTime(2 * time.Millisecond), } }, initialExpected: intRange(5, 9), appendedExpected: intRange(10, 99), }, { description: "ten lines existing; initially twenty lines scrolled", initialLeg: 10, options: func() []scroller.Option { return []scroller.Option{ scroller.Lines(20), scroller.PollTime(2 * time.Millisecond), } }, initialExpected: intRange(0, 9), appendedExpected: intRange(10, 99), }, { description: "ten lines existing; initially twenty lines scrolled; buffer smaller than lines", initialLeg: 10, options: func() []scroller.Option { return []scroller.Option{ scroller.Lines(20), scroller.PollTime(2 * time.Millisecond), scroller.BufferSize(10), } }, initialExpected: intRange(0, 9), appendedExpected: intRange(10, 99), }, { description: "ten lines existing; initially three lines scrolled; filter lines with special prefix", initialLeg: 10, options: func() []scroller.Option { return []scroller.Option{ scroller.Lines(3), scroller.Filter(func(line []byte) bool { return bytes.HasPrefix(line, specialPrefix) }), scroller.PollTime(2 * time.Millisecond), } }, initialExpected: []int{3, 5, 8}, appendedExpected: []int{13, 21, 44, 65}, }, { description: "ten lines existing; initially five lines scrolled; error after further 25 lines", initialLeg: 10, options: func() []scroller.Option { return []scroller.Option{ scroller.Lines(5), scroller.PollTime(2 * time.Millisecond), } }, initialExpected: intRange(5, 9), appendedExpected: intRange(10, 99), injector: func(s *scroller.Scroller, rs *readSeeker) func([]string) { return func(lines []string) { if len(lines) == 25 { rs.setError("ouch") } } }, err: "ouch", }, { description: "ten lines existing; initially five lines scrolled; simply stop after 25 lines", initialLeg: 10, options: func() []scroller.Option { return []scroller.Option{ scroller.Lines(5), scroller.PollTime(2 * time.Millisecond), } }, initialExpected: intRange(5, 9), appendedExpected: intRange(10, 99), injector: func(s *scroller.Scroller, rs *readSeeker) func([]string) { return func(lines []string) { if len(lines) == 25 { s.Stop() } } }, }, { description: "unterminated last line is not scrolled", initialLeg: 103, options: func() []scroller.Option { return []scroller.Option{ scroller.Lines(5), scroller.PollTime(2 * time.Millisecond), } }, initialExpected: intRange(95, 97), appendedExpected: intRange(98, 99), }} // TestScroller runs the different scroller test. func TestScroller(t *testing.T) { assert := audit.NewTestingAssertion(t, true) input, output := generateTestData() for i, test := range tests { assert.Logf("test #%d/%d: %s", i+1, len(tests), test.description) rs, sigc := newReadSeeker(input, test.initialLeg) receiver := newReceiver(assert, output) // Set options. options := []scroller.Option{} if test.options != nil { options = test.options() } // Start scroller. s, err := scroller.NewScroller(rs, receiver.writer, options...) assert.Nil(err) receiver.autoClose(s) // Prepare injection. var injection func([]string) if test.injector != nil { injection = test.injector(s, rs) } // Make assertions. receiver.assertCollected(test.initialExpected, nil) sigc <- struct{}{} receiver.assertCollected(test.appendedExpected, injection) // Test success or error. if test.err == "" { assert.Nil(s.Stop()) } else { st, err := s.Error() assert.Equal(st, loop.Stopped) assert.ErrorMatch(err, test.err) } } } //-------------------- // TEST HELPERS //-------------------- // intRange creates a set of ints. func intRange(lo, hi int) []int { is := []int{} for i := lo; i <= hi; i++ { is = append(is, i) } return is } var ( regularPrefix = []byte("[REGULAR]") specialPrefix = []byte("[SPECIAL]") ) // generateTestData returns slices with input and output data for tests. func generateTestData() (input, output []string) { tagged := []int{1, 2, 3, 5, 8, 13, 21, 44, 65} rand := audit.FixedRand() gen := audit.NewGenerator(rand) line := "" // Generate 98 standard lines. for i := 0; i < 98; i++ { switch { case i%10 == 0: // Spread some empty lines. line = "\n" case len(tagged) > 0 && i == tagged[0]: // Special prefixed lines. line = fmt.Sprintf("%s #%d ", specialPrefix, i) + gen.Sentence() + "\n" tagged = tagged[1:] default: // Regular prefixed lines. line = fmt.Sprintf("%s #%d ", regularPrefix, i) + gen.Sentence() + "\n" } input = append(input, line) output = append(output, line) } // Add two longer lines, each time the first half not terminated. tmp := "" for i := 0; i < 4; i++ { if i%2 == 0 { line = fmt.Sprintf("%s #%d ", regularPrefix, i) + gen.Sentence() + " " + gen.Sentence() + " " tmp = line } else { line = gen.Sentence() + " " + gen.Sentence() + "\n" tmp += line } input = append(input, line) if i%2 != 0 { output = append(output, tmp) tmp = "" } } // Add an unterminated line. line = fmt.Sprintf("%s #%d ", specialPrefix, 100) + gen.Sentence() input = append(input, line) return input, output } // readSeeker simulates the ReadSeeker in the tests. type readSeeker struct { mux sync.Mutex buffer []byte pos int err error } // newReadSeeker creates the ReadSeeker with the passed input. The data // is written with an initial number of lines and then waits for a signal // to continue. func newReadSeeker(input []string, initialLeg int) (*readSeeker, chan struct{}) { sigc := make(chan struct{}) rs := &readSeeker{} i := 0 for ; i < initialLeg; i++ { rs.write(input[i]) } go func() { <-sigc for ; i < len(input); i++ { time.Sleep(5 * time.Millisecond) rs.write(input[i]) } }() return rs, sigc } func (rs *readSeeker) write(s string) { rs.mux.Lock() defer rs.mux.Unlock() rs.buffer = append(rs.buffer, []byte(s)...) } func (rs *readSeeker) setError(msg string) { rs.mux.Lock() defer rs.mux.Unlock() rs.err = errors.New(msg) } func (rs *readSeeker) Read(p []byte) (n int, err error) { rs.mux.Lock() defer rs.mux.Unlock() if rs.err != nil { return 0, rs.err } if rs.pos >= len(rs.buffer) { return 0, io.EOF } n = copy(p, rs.buffer[rs.pos:]) rs.pos += n return n, nil } func (rs *readSeeker) Seek(offset int64, whence int) (ret int64, err error) { rs.mux.Lock() defer rs.mux.Unlock() var newPos int64 switch whence { case 0: newPos = offset case 1: newPos = int64(rs.pos) + offset case 2: newPos = int64(len(rs.buffer)) + offset default: return 0, fmt.Errorf("invalid whence: %d", whence) } if newPos < 0 { return 0, fmt.Errorf("negative position: %d", newPos) } if newPos >= 1<<31 { return 0, fmt.Errorf("position out of range: %d", newPos) } rs.pos = int(newPos) return newPos, nil } // receiver is responsible for receiving the scrolled lines and // performing the assertions type receiver struct { assert audit.Assertion data []string reader *io.PipeReader writer *io.PipeWriter linec chan string } // newReceiver creates a new receiver. func newReceiver(assert audit.Assertion, data []string) *receiver { r := &receiver{ assert: assert, data: data, linec: make(chan string), } r.reader, r.writer = io.Pipe() go r.loop() return r } func (r *receiver) autoClose(scroller *scroller.Scroller) { go func() { scroller.Wait() r.writer.Close() }() } func (r *receiver) assertCollected(expected []int, injection func([]string)) { expectedLines := []string{} for _, lineNo := range expected { expectedLines = append(expectedLines, r.data[lineNo]) } timeout := time.After(2 * time.Second) lines := []string{} for { select { case line, ok := <-r.linec: if ok { lines = append(lines, line) if injection != nil { injection(lines) } if len(lines) == len(expectedLines) { // All data received. r.assert.Equal(lines, expectedLines) return } } else { // linec closed after stopping or error. r.assert.Equal(lines, expectedLines[:len(lines)]) return } case <-timeout: if len(expected) == 0 || injection != nil { return } r.assert.Fail("timeout during tailer collection") return } } } func (r *receiver) loop() { defer close(r.linec) reader := bufio.NewReader(r.reader) for { line, err := reader.ReadString('\n') switch err { case nil: r.linec <- line case io.EOF: return default: r.assert.Fail() } } } // EOF golib-4.24.2/sml/000077500000000000000000000000001315505703200134735ustar00rootroot00000000000000golib-4.24.2/sml/builder.go000066400000000000000000000121111315505703200154440ustar00rootroot00000000000000// Tideland Go Library - Simple Markup Language - Builder // // Copyright (C) 2009-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package sml //-------------------- // IMPORTS //-------------------- import ( "strings" "github.com/tideland/golib/collections" "github.com/tideland/golib/errors" ) //-------------------- // NODE BUILDER //-------------------- // NodeBuilder creates a node structure. type NodeBuilder struct { stack []*tagNode done bool } // NewNodeBuilder return a new nnode builder. func NewNodeBuilder() *NodeBuilder { return &NodeBuilder{[]*tagNode{}, false} } // Root returns the root node of the read document. func (nb *NodeBuilder) Root() (Node, error) { if !nb.done { return nil, errors.New(ErrBuilder, errorMessages, "building is not yet done") } return nb.stack[0], nil } // BeginTagNode implements the Builder interface. func (nb *NodeBuilder) BeginTagNode(tag string) error { if nb.done { return errors.New(ErrBuilder, errorMessages, "building is already done") } t, err := newTagNode(tag) if err != nil { return err } nb.stack = append(nb.stack, t) return nil } // EndTagNode implements the Builder interface. func (nb *NodeBuilder) EndTagNode() error { if nb.done { return errors.New(ErrBuilder, errorMessages, "building is already done") } switch l := len(nb.stack); l { case 0: return errors.New(ErrBuilder, errorMessages, "no opening tag") case 1: nb.done = true default: nb.stack[l-2].appendChild(nb.stack[l-1]) nb.stack = nb.stack[:l-1] } return nil } // TextNode implements the Builder interface. func (nb *NodeBuilder) TextNode(text string) error { if nb.done { return errors.New(ErrBuilder, errorMessages, "building is already done") } if len(nb.stack) > 0 { nb.stack[len(nb.stack)-1].appendTextNode(text) return nil } return errors.New(ErrBuilder, errorMessages, "no opening tag for text") } // RawNode implements the Builder interface. func (nb *NodeBuilder) RawNode(raw string) error { if nb.done { return errors.New(ErrBuilder, errorMessages, "building is already done") } if len(nb.stack) > 0 { nb.stack[len(nb.stack)-1].appendRawNode(raw) return nil } return errors.New(ErrBuilder, errorMessages, "no opening tag for raw text") } // CommentNode implements the Builder interface. func (nb *NodeBuilder) CommentNode(comment string) error { if nb.done { return errors.New(ErrBuilder, errorMessages, "building is already done") } if len(nb.stack) > 0 { nb.stack[len(nb.stack)-1].appendCommentNode(comment) return nil } return errors.New(ErrBuilder, errorMessages, "no opening tag for comment") } //-------------------- // KEY/STRING VALUE TREE BUILDER //-------------------- // KeyStringValueTreeBuilder implements Builder to parse a // file and create a KeyStringValueTree. type KeyStringValueTreeBuilder struct { stack collections.StringStack tree collections.KeyStringValueTree done bool } // NewKeyStringValueTreeBuilder return a new nnode builder. func NewKeyStringValueTreeBuilder() *KeyStringValueTreeBuilder { return &KeyStringValueTreeBuilder{} } // Tree returns the created tree. func (tb *KeyStringValueTreeBuilder) Tree() (collections.KeyStringValueTree, error) { if !tb.done { return nil, errors.New(ErrBuilder, errorMessages, "building is not yet done") } return tb.tree, nil } // BeginTagNode implements the Builder interface. func (tb *KeyStringValueTreeBuilder) BeginTagNode(tag string) error { if tb.done { return errors.New(ErrBuilder, errorMessages, "building is already done") } switch { case tb.tree == nil: tb.stack = collections.NewStringStack(tag) tb.tree = collections.NewKeyStringValueTree(tag, "", false) default: tb.stack.Push(tag) changer := tb.tree.Create(tb.stack.All()...) if err := changer.Error(); err != nil { return errors.Annotate(err, ErrBuilder, errorMessages) } } return nil } // EndTagNode implements the Builder interface. func (tb *KeyStringValueTreeBuilder) EndTagNode() error { if tb.done { return errors.New(ErrBuilder, errorMessages, "building is already done") } _, err := tb.stack.Pop() if tb.stack.Len() == 0 { tb.done = true } return err } // TextNode implements the Builder interface. func (tb *KeyStringValueTreeBuilder) TextNode(text string) error { if tb.done { return errors.New(ErrBuilder, errorMessages, "building is already done") } value, err := tb.tree.At(tb.stack.All()...).Value() if err != nil { return errors.Annotate(err, ErrBuilder, errorMessages) } if value != "" { return errors.New(ErrBuilder, errorMessages, "node has multiple values") } text = strings.TrimSpace(text) if text != "" { _, err = tb.tree.At(tb.stack.All()...).SetValue(text) } return err } // RawNode implements the Builder interface. func (tb *KeyStringValueTreeBuilder) RawNode(raw string) error { return tb.TextNode(raw) } // CommentNode implements the Builder interface. func (tb *KeyStringValueTreeBuilder) CommentNode(comment string) error { if tb.done { return errors.New(ErrBuilder, errorMessages, "building is already done") } return nil } // EOF golib-4.24.2/sml/doc.go000066400000000000000000000012051315505703200145650ustar00rootroot00000000000000// Tideland Go Library - Simple Markup Language // // Copyright (C) 2009-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. // Package sml of the Tideland Go Library provides a very simple // markup language using a kind of LISP like notation with curly // braces. // // The tag only consists out of the chars 'a' to 'z', '0' to '9' // and '-'. Also several parts of the tag can be separated by colons. // The package contains a kind of DOM as well as a parser and a // processor. The latter is used e.g. for printing SML documents. package sml // EOF golib-4.24.2/sml/errors.go000066400000000000000000000027401315505703200153410ustar00rootroot00000000000000// Tideland Go Library - Simple Markup Language - Errors // // Copyright (C) 2009-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package sml //-------------------- // IMPORTS //-------------------- import ( "github.com/tideland/golib/errors" ) //-------------------- // CONSTANTS //-------------------- // Error codes of the SML package. const ( ErrBuilder = iota + 1 ErrReader ErrNoRootProcessor ErrRegisteredPlugin ) var errorMessages = errors.Messages{ ErrBuilder: "cannot build node structure: %v", ErrReader: "cannot read SML document: %v", ErrNoRootProcessor: "no root processor registered", ErrRegisteredPlugin: "plugin processor with tag %q is already registered", } //-------------------- // ERROR //-------------------- // IsBuilderError checks for an error during node building. func IsBuilderError(err error) bool { return errors.IsError(err, ErrBuilder) } // IsReaderError checks for an error during SML text reading. func IsReaderError(err error) bool { return errors.IsError(err, ErrBuilder) } // IsNoRootProcessorError checks for an unregistered root // processor. func IsNoRootProcessorError(err error) bool { return errors.IsError(err, ErrNoRootProcessor) } // IsRegisteredPluginError checks for the error of an already // registered plugin. func IsRegisteredPluginError(err error) bool { return errors.IsError(err, ErrRegisteredPlugin) } // EOF golib-4.24.2/sml/nodes.go000066400000000000000000000127531315505703200151420ustar00rootroot00000000000000// Tideland Go Library - Simple Markup Language - Nodes // // Copyright (C) 2009-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package sml //-------------------- // IMPORTS //-------------------- import ( "bytes" "fmt" "regexp" "strings" ) //-------------------- // TAG NODE //-------------------- // tagNode represents a node with one multipart tag and zero to many // children nodes. type tagNode struct { tag []string children []Node } // newTagNode creates a node with the given tag. func newTagNode(tag string) (*tagNode, error) { vtag, err := ValidateTag(tag) if err != nil { return nil, err } return &tagNode{ tag: vtag, children: []Node{}, }, nil } // appendTagNode creates a new tag node, appends it as last child // and returns it. func (tn *tagNode) appendTagNode(tag string) (*tagNode, error) { ntn, err := newTagNode(tag) if err != nil { return nil, err } tn.appendChild(ntn) return ntn, nil } // appendTextNode creates a text node, appends it as last child // and returns it. func (tn *tagNode) appendTextNode(text string) *textNode { trimmedText := strings.TrimSpace(text) if trimmedText == "" { return nil } ntn := newTextNode(trimmedText) tn.appendChild(ntn) return ntn } // appendRawNode creates a raw node, appends it as last child // and returns it. func (tn *tagNode) appendRawNode(raw string) *rawNode { nrn := newRawNode(raw) tn.appendChild(nrn) return nrn } // appendCommentNode creates a comment node, appends it as last child // and returns it. func (tn *tagNode) appendCommentNode(comment string) *commentNode { ncn := newCommentNode(comment) tn.appendChild(ncn) return ncn } // appendChild adds a node as last child. func (tn *tagNode) appendChild(n Node) { tn.children = append(tn.children, n) } // Tag returns the tag parts. func (tn *tagNode) Tag() []string { out := make([]string, len(tn.tag)) copy(out, tn.tag) return out } // Len return the number of children of this node. func (tn *tagNode) Len() int { return 1 + len(tn.children) } // ProcessWith processes the node and all chidlren recursively // with the passed processor. func (tn *tagNode) ProcessWith(p Processor) error { if err := p.OpenTag(tn.tag); err != nil { return err } for _, child := range tn.children { if err := child.ProcessWith(p); err != nil { return err } } return p.CloseTag(tn.tag) } // String returns the tag node as string. func (tn *tagNode) String() string { var buf bytes.Buffer context := NewWriterContext(NewStandardSMLWriter(), &buf, true, "\t") WriteSML(tn, context) return buf.String() } //-------------------- // TEXT NODE //-------------------- // textNode is a node containing some text. type textNode struct { text string } // newTextNode creates a new text node. func newTextNode(text string) *textNode { return &textNode{strings.TrimSpace(text)} } // Tag returns nil. func (tn *textNode) Tag() []string { return nil } // Len returns the len of the text in the text node. func (tn *textNode) Len() int { return len(tn.text) } // ProcessWith processes the text node with the given // processor. func (tn *textNode) ProcessWith(p Processor) error { return p.Text(tn.text) } // String returns the text node as string. func (tn *textNode) String() string { return tn.text } //-------------------- // RAW NODE //-------------------- // rawNode is a node containing some raw data. type rawNode struct { raw string } // newRawNode creates a new raw node. func newRawNode(raw string) *rawNode { return &rawNode{raw} } // Tag returns nil. func (rn *rawNode) Tag() []string { return nil } // Len returns the len of the data in the raw node. func (rn *rawNode) Len() int { return len(rn.raw) } // ProcessWith processes the raw node with the given // processor. func (rn *rawNode) ProcessWith(p Processor) error { return p.Raw(rn.raw) } // String returns the raw node as string. func (rn *rawNode) String() string { return rn.raw } //-------------------- // COMMENT NODE //-------------------- // commentNode is a node containing a comment. type commentNode struct { comment string } // newCommentNode creates a new comment node. func newCommentNode(comment string) *commentNode { return &commentNode{strings.TrimSpace(comment)} } // Tag returns nil. func (cn *commentNode) Tag() []string { return nil } // Len returns the len of the data in the comment node. func (cn *commentNode) Len() int { return len(cn.comment) } // ProcessWith processes the comment node with the given // processor. func (cn *commentNode) ProcessWith(p Processor) error { return p.Comment(cn.comment) } // String returns the comment node as string. func (cn *commentNode) String() string { return cn.comment } //-------------------- // PRIVATE FUNCTIONS //-------------------- // validTagRe contains the regular expression for // the validation of tags. var validTagRe *regexp.Regexp // init the regexp for valid tags. func init() { var err error validTagRe, err = regexp.Compile(`^([a-z][a-z0-9]*(\-[a-z0-9]+)*)(:([a-z0-9]+(\-[a-z0-9]+)*))*$`) if err != nil { panic(err) } } // ValidateTag checks if a tag is valid. Only // the chars 'a' to 'z', '0' to '9', '-' and ':' are // accepted. It also transforms it to lowercase // and splits the parts at the colons. func ValidateTag(tag string) ([]string, error) { ltag := strings.ToLower(tag) if !validTagRe.MatchString(ltag) { return nil, fmt.Errorf("invalid tag: %q", tag) } ltags := strings.Split(ltag, ":") return ltags, nil } // EOF golib-4.24.2/sml/reader.go000066400000000000000000000151351315505703200152710ustar00rootroot00000000000000// Tideland Go Library - Simple Markup Language - Reader // // Copyright (C) 2009-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package sml //-------------------- // IMPORTS //-------------------- import ( "bufio" "bytes" "fmt" "io" "unicode" "github.com/tideland/golib/errors" ) //-------------------- // SML READER //-------------------- const ( // Rune classes. rcText int = iota + 1 rcSpace rcOpen rcClose rcEscape rcExclamation rcHash rcTag rcEOF rcInvalid // Chars for the rune classes. chSpace = ' ' chOpen = '{' chClose = '}' chEscape = '^' chExclamation = '!' chHash = '#' ) // ReadSML parses a SML document and uses the passed builder // for the callbacks. func ReadSML(reader io.Reader, builder Builder) error { s := &mlReader{ reader: bufio.NewReader(reader), builder: builder, index: -1, } if err := s.readPreliminary(); err != nil { return err } return s.readTagNode() } // mlReader is used by ReadSML to parse a SML document // and return it as node structure. type mlReader struct { reader *bufio.Reader builder Builder index int } // readPreliminary reads the content before the first node. func (mr *mlReader) readPreliminary() error { for { _, rc, err := mr.readRune() switch { case err != nil: return err case rc == rcEOF: return errors.New(ErrReader, errorMessages, "unexpected end of file while reading preliminary") case rc == rcOpen: return nil } } } // readNode reads the next tag node. func (mr *mlReader) readTagNode() error { tag, rc, err := mr.readTag() if err != nil { return err } if err = mr.builder.BeginTagNode(tag); err != nil { return err } // Read children. if rc != rcClose { if err = mr.readTagChildren(); err != nil { return err } } return mr.builder.EndTagNode() } // readTag reads the tag of a node. It als returns the class of the next rune. func (mr *mlReader) readTag() (string, int, error) { var buf bytes.Buffer for { r, rc, err := mr.readRune() switch { case err != nil: return "", 0, err case rc == rcEOF: return "", 0, errors.New(ErrReader, errorMessages, "unexpected end of file while reading a tag") case rc == rcTag: buf.WriteRune(r) case rc == rcSpace || rc == rcClose: return buf.String(), rc, nil default: msg := fmt.Sprintf("invalid tag character at position %d", mr.index) return "", 0, errors.New(ErrReader, errorMessages, msg) } } } // readTagChildren reads the children of parent tag node. func (mr *mlReader) readTagChildren() error { for { _, rc, err := mr.readRune() switch { case err != nil: return err case rc == rcEOF: return errors.New(ErrReader, errorMessages, "unexpected end of file while reading children") case rc == rcClose: return nil case rc == rcOpen: if err = mr.readBracedContent(); err != nil { return err } default: mr.index-- mr.reader.UnreadRune() if err = mr.readTextNode(); err != nil { return err } } } } // readBracedContent checks if the opening is for a tag node, raw node, // or comment and starts the reading of it. func (mr *mlReader) readBracedContent() error { _, rc, err := mr.readRune() switch { case err != nil: return err case rc == rcEOF: return errors.New(ErrReader, errorMessages, "unexpected end of file while reading a tag or raw node") case rc == rcTag: mr.index-- mr.reader.UnreadRune() return mr.readTagNode() case rc == rcExclamation: return mr.readRawNode() case rc == rcHash: return mr.readCommentNode() } msg := fmt.Sprintf("invalid character after opening at index %d", mr.index) return errors.New(ErrReader, errorMessages, msg) } // readRawNode reads a raw node. func (mr *mlReader) readRawNode() error { var buf bytes.Buffer for { r, rc, err := mr.readRune() switch { case err != nil: return err case rc == rcEOF: return errors.New(ErrReader, errorMessages, "unexpected end of file while reading a raw node") case rc == rcExclamation: r, rc, err = mr.readRune() switch { case err != nil: return err case rc == rcEOF: return errors.New(ErrReader, errorMessages, "unexpected end of file while reading a raw node") case rc == rcClose: return mr.builder.RawNode(buf.String()) } buf.WriteRune(chExclamation) buf.WriteRune(r) default: buf.WriteRune(r) } } } // readCommentNode reads a raw node. func (mr *mlReader) readCommentNode() error { var buf bytes.Buffer for { r, rc, err := mr.readRune() switch { case err != nil: return err case rc == rcEOF: return errors.New(ErrReader, errorMessages, "unexpected end of file while reading a comment node") case rc == rcHash: r, rc, err = mr.readRune() switch { case err != nil: return err case rc == rcEOF: return errors.New(ErrReader, errorMessages, "unexpected end of file while reading a comment node") case rc == rcClose: return mr.builder.CommentNode(buf.String()) } buf.WriteRune(chHash) buf.WriteRune(r) default: buf.WriteRune(r) } } } // readTextNode reads a text node. func (mr *mlReader) readTextNode() error { var buf bytes.Buffer for { r, rc, err := mr.readRune() switch { case err != nil: return err case rc == rcEOF: return errors.New(ErrReader, errorMessages, "unexpected end of file while reading a text node") case rc == rcOpen || rc == rcClose: mr.index-- mr.reader.UnreadRune() return mr.builder.TextNode(buf.String()) case rc == rcEscape: r, rc, err = mr.readRune() switch { case err != nil: return err case rc == rcEOF: return errors.New(ErrReader, errorMessages, "unexpected end of file while reading a text node") case rc == rcOpen || rc == rcClose || rc == rcEscape: buf.WriteRune(r) default: msg := fmt.Sprintf("invalid character after escaping at index %d", mr.index) return errors.New(ErrReader, errorMessages, msg) } default: buf.WriteRune(r) } } } // Reads one rune of the reader. func (mr *mlReader) readRune() (r rune, rc int, err error) { var size int mr.index++ r, size, err = mr.reader.ReadRune() if err != nil { return 0, 0, err } switch { case size == 0: rc = rcEOF case r == chOpen: rc = rcOpen case r == chClose: rc = rcClose case r == chEscape: rc = rcEscape case r == chExclamation: rc = rcExclamation case r == chHash: rc = rcHash case r >= 'a' && r <= 'z': rc = rcTag case r >= 'A' && r <= 'Z': rc = rcTag case r >= '0' && r <= '9': rc = rcTag case r == '-' || r == ':': rc = rcTag case unicode.IsSpace(r): rc = rcSpace default: rc = rcText } return } // EOF golib-4.24.2/sml/sml.go000066400000000000000000000037131315505703200146210ustar00rootroot00000000000000// Tideland Go Library - Simple Markup Language // // Copyright (C) 2009-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package sml //-------------------- // IMPORTS //-------------------- //-------------------- // PROCESSOR //-------------------- // Processor represents any type able to process // a node structure. Four callbacks then will be // called with the according data. type Processor interface { // OpenTag is called if a tag is opened. OpenTag(tag []string) error // CloseTag is called if a tag is closed. CloseTag(tag []string) error // Text is called for each text data inside a node. Text(text string) error // Raw is called for each raw data inside a node. Raw(raw string) error // Comment is called for each comment data inside a node. Comment(comment string) error } //-------------------- // BUILDER //-------------------- // Builder defines the callbacks for the reader // to handle parts of the document. type Builder interface { // BeginTagNode is called when new tag node begins. BeginTagNode(tag string) error // EndTagNode is called when the tag node ends. EndTagNode() error // TextNode is called for each text data. TextNode(text string) error // RawNode is called for each raw data. RawNode(raw string) error // Comment is called for each comment data inside a node. CommentNode(comment string) error } //-------------------- // NODES //-------------------- // Node represents the common interface of all nodes (tags and text). type Node interface { // Tag returns the tag in case of a tag node, otherwise nil. Tag() []string // Len returns the length of a text or the number of subnodes, // depending on the concrete type of the node. Len() int // ProcessWith is called for the processing of this node. ProcessWith(p Processor) error // String returns a simple string representation of the node. String() string } // EOF golib-4.24.2/sml/sml_test.go000066400000000000000000000162731315505703200156650ustar00rootroot00000000000000// Tideland Go Library - Simple Markup Language - Unit Tests // // Copyright (C) 2009-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package sml_test //-------------------- // IMPORTS //-------------------- import ( "bytes" "fmt" "strings" "testing" "github.com/tideland/golib/audit" "github.com/tideland/golib/sml" ) //-------------------- // TESTS //-------------------- // TestTagValidation checks if only correct tags are accepted. func TestTagValidation(t *testing.T) { assert := audit.NewTestingAssertion(t, true) tests := []struct { in string out []string ok bool }{ {"-abc", nil, false}, {"-", nil, false}, {"abc-", nil, false}, {"ab-c", []string{"ab-c"}, true}, {"abc", []string{"abc"}, true}, {"ab:cd", []string{"ab", "cd"}, true}, {"1a", nil, false}, {"a1", []string{"a1"}, true}, {"a:1", []string{"a", "1"}, true}, {"a-b:c-d", []string{"a-b", "c-d"}, true}, {"a-:c-d", nil, false}, {"-a:c-d", nil, false}, {"ab:-c", nil, false}, {"ab:c-", nil, false}, {"a-b-1", []string{"a-b-1"}, true}, {"a-b-1:c-d-2:e-f-3", []string{"a-b-1", "c-d-2", "e-f-3"}, true}, } for i, test := range tests { msg := fmt.Sprintf("%q (test %d) ", test.in, i) tag, err := sml.ValidateTag(test.in) if err == nil { assert.Equal(tag, test.out, msg) assert.True(test.ok, msg) } else { assert.ErrorMatch(err, fmt.Sprintf("invalid tag: %q", test.in), msg) assert.False(test.ok, msg) } } } // TestCreating checks the manual tree creation. func TestCreating(t *testing.T) { assert := audit.NewTestingAssertion(t, true) root := createNodeStructure(assert) assert.Equal(root.Tag(), []string{"root"}, "Root tag has to be 'root'.") assert.NotEmpty(root, "Root tag is not empty.") } // TestWriterProcessing checks the writing of SML. func TestWriterProcessing(t *testing.T) { assert := audit.NewTestingAssertion(t, true) root := createNodeStructure(assert) bufA := bytes.NewBufferString("") bufB := bytes.NewBufferString("") ctxA := sml.NewWriterContext(sml.NewStandardSMLWriter(), bufA, true, " ") ctxB := sml.NewWriterContext(sml.NewStandardSMLWriter(), bufB, false, "") sml.WriteSML(root, ctxA) sml.WriteSML(root, ctxB) assert.Logf("===== WITH INDENT =====") assert.Logf(bufA.String()) assert.Logf("===== WITHOUT INDENT =====") assert.Logf(bufB.String()) assert.Logf("===== DONE =====") assert.NotEmpty(bufA, "Buffer A must not be empty.") assert.NotEmpty(bufB, "Buffer B must not be empty.") } // TestPositiveNodeReading checks the successful reading of nodes. func TestPositiveNodeReading(t *testing.T) { assert := audit.NewTestingAssertion(t, true) text := "Before! {foo:main {bar:1:first Yadda ^{Test^} 1} {! Raw: }} { ! ^^^ !} {between} {bar:2:last Yadda {Test ^^} 2}} After!" builder := sml.NewNodeBuilder() err := sml.ReadSML(strings.NewReader(text), builder) assert.Nil(err) root, err := builder.Root() assert.Nil(err) assert.Equal(root.Tag(), []string{"foo", "main"}) assert.NotEmpty(root) buf := bytes.NewBufferString("") ctx := sml.NewWriterContext(sml.NewStandardSMLWriter(), buf, true, " ") sml.WriteSML(root, ctx) assert.Logf("===== PARSED SML =====") assert.Logf(buf.String()) assert.Logf("===== DONE =====") } // TestNegativeNodeReading checks the failing reading of nodes. func TestNegativeNodeReading(t *testing.T) { assert := audit.NewTestingAssertion(t, true) text := "{Foo {bar:1 Yadda {test} {} 1} {bar:2 Yadda 2}}" builder := sml.NewNodeBuilder() err := sml.ReadSML(strings.NewReader(text), builder) assert.ErrorMatch(err, `.* cannot read SML document: invalid character after opening at index .*`) } // TestPositiveTreeReading checks the successful reading of trees. func TestPositiveTreeReading(t *testing.T) { assert := audit.NewTestingAssertion(t, true) text := "{config {foo 1}{bar 2}{yadda {up down}{down up}}}" builder := sml.NewKeyStringValueTreeBuilder() err := sml.ReadSML(strings.NewReader(text), builder) assert.Nil(err) tree, err := builder.Tree() assert.Nil(err) assert.Logf("%v", tree) } // TestNegativeTreeReading checks the failing reading of trees. func TestNegativeTreeReading(t *testing.T) { assert := audit.NewTestingAssertion(t, true) text := "{foo {bar 1}{bar 2}}" builder := sml.NewKeyStringValueTreeBuilder() err := sml.ReadSML(strings.NewReader(text), builder) assert.ErrorMatch(err, `.* node has multiple values`) } // TestSML2XML checks the conversion from SML to XML. func TestSML2XML(t *testing.T) { assert := audit.NewTestingAssertion(t, true) in := `{html {head {title A test document}} {body {h1:title A test document} {p:intro:preface The is a simple sentence with an {em emphasized} and a {strong strong} text. We'll see how it renders.} {ul {li:1 It should be nice.} {li:2 It should be error free.} {li:3 It should be fast.} } {! for foo := 0; foo < 42; foo++ { println(foo) } !} }}` builder := sml.NewNodeBuilder() err := sml.ReadSML(strings.NewReader(in), builder) assert.Nil(err) root, err := builder.Root() assert.Nil(err) buf := bytes.NewBufferString("") ctx := sml.NewWriterContext(sml.NewXMLWriter("pre"), buf, true, " ") ctx.Register("li", newLIWriter()) sml.WriteSML(root, ctx) assert.Logf("===== XML =====") assert.Logf(buf.String()) assert.Logf("===== DONE =====") } //-------------------- // HELPERS //-------------------- // Create a node structure. func createNodeStructure(assert audit.Assertion) sml.Node { builder := sml.NewNodeBuilder() builder.BeginTagNode("root") builder.TextNode("Text A") builder.TextNode("Text B") builder.CommentNode("A first comment.") builder.BeginTagNode("sub-a:1st:important") builder.TextNode("Text A.A") builder.CommentNode("A second comment.") builder.EndTagNode() builder.BeginTagNode("sub-b:2nd") builder.TextNode("Text B.A") builder.BeginTagNode("text") builder.TextNode("Any text with the special characters {, }, and ^.") builder.EndTagNode() builder.EndTagNode() builder.BeginTagNode("sub-c") builder.TextNode("Before raw.") builder.RawNode("func Test(i int) { println(i) }") builder.TextNode("After raw.") builder.EndTagNode() builder.EndTagNode() root, err := builder.Root() assert.Nil(err) return root } // liWriter handles the li-tag of the document. type liWriter struct { context *sml.WriterContext } // newLIWriter creates a new writer for the li-tag. func newLIWriter() sml.WriterProcessor { return &liWriter{} } // SetContext sets the writer context. func (w *liWriter) SetContext(ctx *sml.WriterContext) { w.context = ctx } // OpenTag writes the opening of a tag. func (w *liWriter) OpenTag(tag []string) error { return w.context.Writef("
  • ") } // CloseTag writes the closing of a tag. func (w *liWriter) CloseTag(tag []string) error { return w.context.Writef("
  • ") } // Text writes a text with an encoding of special runes. func (w *liWriter) Text(text string) error { return w.context.Writef(" %s ", text) } // Raw writes raw data without any encoding. func (w *liWriter) Raw(raw string) error { return w.context.Writef("\n%s\n", raw) } // Comment writes comment data without any encoding. func (w *liWriter) Comment(comment string) error { return w.context.Writef("\n%s\n", comment) } // EOF golib-4.24.2/sml/writer.go000066400000000000000000000164501315505703200153440ustar00rootroot00000000000000// Tideland Go Library - Simple Markup Language - Writer // // Copyright (C) 2009-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package sml //-------------------- // IMPORTS //-------------------- import ( "bytes" "encoding/xml" "fmt" "io" "strings" "github.com/tideland/golib/errors" ) //-------------------- // SML WRITER //-------------------- // WriterProcessor a processor type WriterProcessor interface { // WriterProcessor is a processor itself. Processor // SetContext sets the writer context. SetContext(ctx *WriterContext) } // WriterProcessors is a map of processors that can be plugged // into the used SML writer. The key is the first part of each // tag. type WriterProcessors map[string]WriterProcessor // WriterContext controls the parameters of a writing. type WriterContext struct { plugins WriterProcessors writer io.Writer prettyPrint bool indentStr string } // NewWriterContext creates a new writer context. func NewWriterContext(wp WriterProcessor, w io.Writer, pretty bool, indentStr string) *WriterContext { ctx := &WriterContext{ plugins: WriterProcessors{"": wp}, writer: w, prettyPrint: pretty, indentStr: indentStr, } wp.SetContext(ctx) return ctx } // Register adds a writer processor which will be responsible for // processing if the tag is matching. func (ctx *WriterContext) Register(tag string, wp WriterProcessor) error { if _, ok := ctx.plugins[tag]; ok { return errors.New(ErrRegisteredPlugin, errorMessages, tag) } wp.SetContext(ctx) ctx.plugins[tag] = wp return nil } // Writef writes a formatted string to the writer. func (ctx *WriterContext) Writef(format string, args ...interface{}) error { _, err := fmt.Fprintf(ctx.writer, format, args...) return err } // mlWriter writes it to an io.Writer. type mlWriter struct { context *WriterContext stack []WriterProcessor indent int } // WriteSML uses one WriterProcessor and possible more as plugins to write the // SML node to the passed writer. The prettyPrint flag controls if the writing // is in a more beautiful formatted way. func WriteSML(node Node, ctx *WriterContext) error { wp := ctx.plugins[""] if wp == nil { return errors.New(ErrNoRootProcessor, errorMessages) } w := &mlWriter{ context: ctx, stack: []WriterProcessor{wp}, indent: 0, } // Process the node with the new writer. if err := node.ProcessWith(w); err != nil { return err } return nil } // OpenTag writes the opening of a tag. func (w *mlWriter) OpenTag(tag []string) error { w.activatePlugin(tag[0]) w.writeIndent(true) if err := w.activePlugin().OpenTag(tag); err != nil { return err } w.writeNewline() w.indent++ return nil } // CloseTag writes the closing of a tag. func (w *mlWriter) CloseTag(tag []string) error { w.indent-- w.writeIndent(false) if err := w.activePlugin().CloseTag(tag); err != nil { return err } w.writeNewline() // Check if a plugin is to deactivate. if len(w.stack) > 1 { w.deactivatePlugin() } return nil } // Text writes a text with an encoding of special runes. func (w *mlWriter) Text(text string) error { w.writeIndent(true) if err := w.activePlugin().Text(text); err != nil { return err } w.writeNewline() return nil } // Raw writes raw data without any encoding. func (w *mlWriter) Raw(raw string) error { w.writeIndent(true) if err := w.activePlugin().Raw(raw); err != nil { return err } w.writeNewline() return nil } // Comment writes comment data without any encoding. func (w *mlWriter) Comment(comment string) error { w.writeIndent(true) if err := w.activePlugin().Comment(comment); err != nil { return err } w.writeNewline() return nil } // activatePlugin activates a new one of the // registered plugins. func (w *mlWriter) activatePlugin(tag string) { if p := w.context.plugins[tag]; p != nil { w.stack = append(w.stack, p) } } // deactivatePlugin deactivates the top plugin. func (w *mlWriter) deactivatePlugin() { w.stack = w.stack[:len(w.stack)-1] } // activePlugin returns the current active plugin. func (w *mlWriter) activePlugin() WriterProcessor { return w.stack[len(w.stack)-1] } // writeIndent writes an indentation if wanted. func (w *mlWriter) writeIndent(open bool) { if w.context.prettyPrint { for i := 0; i < w.indent; i++ { w.context.Writef(w.context.indentStr) } } else if open { w.context.Writef(" ") } } // writeNewline writes a newline if wanted. func (w *mlWriter) writeNewline() { if w.context.prettyPrint { w.context.Writef("\n") } } //-------------------- // STANDARD SML WRITER PROCESSOR //-------------------- // standardSMLWriter writes a SML document in its standard // notation to a writer. type standardSMLWriter struct { context *WriterContext } // NewStandardSMLWriter creates a new writer for a ML // document in standard notation. func NewStandardSMLWriter() WriterProcessor { return &standardSMLWriter{} } // SetContext sets the writer context. func (w *standardSMLWriter) SetContext(ctx *WriterContext) { w.context = ctx } // OpenTag writes the opening of a tag. func (w *standardSMLWriter) OpenTag(tag []string) error { return w.context.Writef("{%s", strings.Join(tag, ":")) } // CloseTag writes the closing of a tag. func (w *standardSMLWriter) CloseTag(tag []string) error { return w.context.Writef("}") } // Text writes a text with an encoding of special runes. func (w *standardSMLWriter) Text(text string) error { var buf bytes.Buffer for _, r := range text { switch r { case '^': buf.WriteString("^^") case '{': buf.WriteString("^{") case '}': buf.WriteString("^}") default: buf.WriteRune(r) } } return w.context.Writef(buf.String()) } // Raw writes raw data without any encoding. func (w *standardSMLWriter) Raw(raw string) error { return w.context.Writef("{! %s !}", raw) } // Comment writes comment data without any encoding. func (w *standardSMLWriter) Comment(comment string) error { return w.context.Writef("{# %s #}", comment) } //-------------------- // XML WRITER PROCESSOR //-------------------- // xmlWriter writes a ML document in XML notation. type xmlWriter struct { context *WriterContext rawTag string } // NewXMLWriter creates a new writer for a ML // document in XML notation. func NewXMLWriter(rawTag string) WriterProcessor { return &xmlWriter{ rawTag: rawTag, } } // SetContext sets the writer context. func (w *xmlWriter) SetContext(ctx *WriterContext) { w.context = ctx } // OpenTag writes the opening of a tag. func (w *xmlWriter) OpenTag(tag []string) error { w.context.Writef("<%s", tag[0]) if len(tag) > 1 { w.context.Writef(" id=%q", tag[1]) } if len(tag) > 2 { w.context.Writef(" class=%q", tag[2]) } return w.context.Writef(">") } // CloseTag writes the closing of a tag. func (w *xmlWriter) CloseTag(tag []string) error { return w.context.Writef("", tag[0]) } // Text writes a text with an encoding of special runes. func (w *xmlWriter) Text(text string) error { return xml.EscapeText(w.context.writer, []byte(text)) } // Raw writes raw data without any encoding. func (w *xmlWriter) Raw(raw string) error { return w.context.Writef("<%s>%s", w.rawTag, raw, w.rawTag) } // Comment writes comment data without any encoding. func (w *xmlWriter) Comment(comment string) error { return w.context.Writef("", comment) } // EOF golib-4.24.2/sort/000077500000000000000000000000001315505703200136675ustar00rootroot00000000000000golib-4.24.2/sort/doc.go000066400000000000000000000005411315505703200147630ustar00rootroot00000000000000// Tideland Go Library - Sort // // Copyright (C) 2009-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. // Package sort of the Tideland Go Library provides a parallel quicksort. It // uses the same interface as the standard sort package. package sort // EOF golib-4.24.2/sort/export_test.go000066400000000000000000000012241315505703200165750ustar00rootroot00000000000000// Tideland Go Library - Sort - Export Test // // Copyright (C) 2009-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package sort //-------------------- // IMPORTS //-------------------- import ( "sort" ) //-------------------- // EXPORTED FUNCTIONS //-------------------- func Partition(data sort.Interface, lo, hi int) (int, int) { return partition(data, lo, hi) } func InsertionSort(data sort.Interface, lo, hi int) { insertionSort(data, lo, hi) } func SequentialQuickSort(data sort.Interface, lo, hi int) { sequentialQuickSort(data, lo, hi) } // EOF golib-4.24.2/sort/sort.go000066400000000000000000000056631315505703200152170ustar00rootroot00000000000000// Tideland Go Library - Sort // // Copyright (C) 2009-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package sort //-------------------- // IMPORTS //-------------------- import ( "runtime" "sort" ) //-------------------- // CONTROL VALUES //-------------------- // sequentialThreshold for switching from sequential quick sort to insertion sort. var sequentialThreshold int = runtime.NumCPU()*4 - 1 // parallelThreshold for switching from parallel to sequential quick sort. var parallelThreshold int = runtime.NumCPU()*2048 - 1 //-------------------- // HELPING FUNCS //-------------------- // insertionSort for smaller data collections. func insertionSort(data sort.Interface, lo, hi int) { for i := lo + 1; i < hi+1; i++ { for j := i; j > lo && data.Less(j, j-1); j-- { data.Swap(j, j-1) } } } // median to caclculate the median based on Tukey's ninther. func median(data sort.Interface, lo, hi int) int { m := (lo + hi) / 2 d := (hi - lo) / 8 // Move median into the middle. mot := func(ml, mm, mh int) { if data.Less(mm, ml) { data.Swap(mm, ml) } if data.Less(mh, mm) { data.Swap(mh, mm) } if data.Less(mm, ml) { data.Swap(mm, ml) } } // Get low, middle, and high median. if hi-lo > 40 { mot(lo+d, lo, lo+2*d) mot(m-d, m, m+d) mot(hi-d, hi, hi-2*d) } // Get combined median. mot(lo, m, hi) return m } // partition the data based on the median. func partition(data sort.Interface, lo, hi int) (int, int) { med := median(data, lo, hi) idx := lo data.Swap(med, hi) for i := lo; i < hi; i++ { if data.Less(i, hi) { data.Swap(i, idx) idx++ } } data.Swap(idx, hi) return idx - 1, idx + 1 } // sequentialQuickSort using itself recursively. func sequentialQuickSort(data sort.Interface, lo, hi int) { if hi-lo > sequentialThreshold { // Use sequential quicksort. plo, phi := partition(data, lo, hi) sequentialQuickSort(data, lo, plo) sequentialQuickSort(data, phi, hi) } else { // Use insertion sort. insertionSort(data, lo, hi) } } // parallelQuickSort using itself recursively and concurrent. func parallelQuickSort(data sort.Interface, lo, hi int, done chan bool) { if hi-lo > parallelThreshold { // Parallel QuickSort. plo, phi := partition(data, lo, hi) partDone := make(chan bool) go parallelQuickSort(data, lo, plo, partDone) go parallelQuickSort(data, phi, hi, partDone) // Wait for the end of both sorts. <-partDone <-partDone } else { // Sequential QuickSort. sequentialQuickSort(data, lo, hi) } // Signal that it's done. done <- true } //-------------------- // PARALLEL QUICKSORT //-------------------- // Sort is the single function for sorting data according // to the standard sort interface. Internally it uses the // parallel quicksort. func Sort(data sort.Interface) { done := make(chan bool) go parallelQuickSort(data, 0, data.Len()-1, done) <-done } // EOF golib-4.24.2/sort/sort_test.go000066400000000000000000000044311315505703200162460ustar00rootroot00000000000000// Tideland Go Library - Sort - Unit Tests // // Copyright (C) 2009-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package sort_test //-------------------- // IMPORTS //-------------------- import ( "math/rand" stdsort "sort" "testing" "time" "github.com/tideland/golib/audit" "github.com/tideland/golib/sort" ) //-------------------- // TESTS //-------------------- // Test pivot. func TestPivot(t *testing.T) { assert := audit.NewTestingAssertion(t, true) // Make some test data. td := ByteSlice{17, 20, 13, 15, 51, 6, 21, 11, 23, 47, 59, 88, 78, 67, 94} plh, puh := sort.Partition(td, 0, len(td)-1) // Asserts. assert.Equal(plh, 3, "Pivot lower half.") assert.Equal(puh, 5, "Pivot upper half.") assert.Equal(td[puh-1], byte(17), "Data at median.") assert.Equal(td, ByteSlice{11, 13, 15, 6, 17, 20, 21, 94, 23, 47, 59, 88, 78, 67, 51}, "Prepared data.") } // Benchmark the standart integer sort. func BenchmarkStandardSort(b *testing.B) { is := generateIntSlice(b.N) stdsort.Sort(is) } // Benchmark the insertion sort used insed of sort. func BenchmarkInsertionSort(b *testing.B) { is := generateIntSlice(b.N) sort.InsertionSort(is, 0, len(is)-1) } // Benchmark the sequential quicksort used insed of sort. func BenchmarkSequentialQuickSort(b *testing.B) { is := generateIntSlice(b.N) sort.SequentialQuickSort(is, 0, len(is)-1) } // Benchmark the parallel quicksort provided by the package. func BenchmarkParallelQuickSort(b *testing.B) { is := generateIntSlice(b.N) sort.Sort(is) } //-------------------- // HELPERS //-------------------- // ByteSlice is a number of bytes for sorting implementing the sort.Interface. type ByteSlice []byte func (bs ByteSlice) Len() int { return len(bs) } func (bs ByteSlice) Less(i, j int) bool { return bs[i] < bs[j] } func (bs ByteSlice) Swap(i, j int) { bs[i], bs[j] = bs[j], bs[i] } // generateIntSlice generates a slice of ints. func generateIntSlice(count int) stdsort.IntSlice { is := make([]int, count) for i := 0; i < count; i++ { is[i] = rand.Int() } return is } // duration measures the duration of a function execution. func duration(f func()) time.Duration { start := time.Now() f() return time.Now().Sub(start) } // EOF golib-4.24.2/stringex/000077500000000000000000000000001315505703200145435ustar00rootroot00000000000000golib-4.24.2/stringex/defaulter.go000066400000000000000000000152231315505703200170500ustar00rootroot00000000000000// Tideland Go Library - String Extensions - Defaulter // // Copyright (C) 2015-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package stringex //-------------------- // IMPORTS //-------------------- import ( "errors" "fmt" "strconv" "strings" "time" "github.com/tideland/golib/logger" ) //-------------------- // DEFAULTER //-------------------- // Defaulter provides an access to valuers interpreting the strings // as types. In case of access or conversion errors these errors // will be logged and the passed default values are returned. type Defaulter interface { fmt.Stringer // AsString returns the value itself, only an error will // return the default. AsString(v Valuer, dv string) string // AsStringSlice returns the value as slice of strings // separated by the passed separator. AsStringSlice(v Valuer, sep string, dv []string) []string // AsStringMap returns the value as map of strings to strings. // The rows are separated by the rsep, the key/values per // row with kvsep. AsStringMap(v Valuer, rsep, kvsep string, dv map[string]string) map[string]string // AsBool returns the value interpreted as bool. Here the // strings 1, t, T, TRUE, true, True are interpreted as // true, the strings 0, f, F, FALSE, false, False as false. AsBool(v Valuer, dv bool) bool // AsInt returns the value interpreted as int. AsInt(v Valuer, dv int) int // AsInt64 returns the value interpreted as int64. AsInt64(v Valuer, dv int64) int64 // AsUint returns the value interpreted as uint. AsUint(v Valuer, dv uint) uint // AsUint64 returns the value interpreted as uint64. AsUint64(v Valuer, dv uint64) uint64 // AsFloat64 returns the value interpreted as float64. AsFloat64(v Valuer, dv float64) float64 // AsTime returns the value interpreted as time in // the given layout. AsTime(v Valuer, layout string, dv time.Time) time.Time // AsDuration returns the value interpreted as duration. AsDuration(v Valuer, dv time.Duration) time.Duration } // defaulter implements the Defaulter. type defaulter struct { id string log bool } // NewDefaulter creates a defaulter with the given settings. func NewDefaulter(id string, log bool) Defaulter { return &defaulter{ id: id, log: log, } } // AsString implements Defaulter. func (d *defaulter) AsString(v Valuer, dv string) string { value, err := v.Value() if err != nil { d.logValuerError(err) return dv } return value } // AsStringSlice implements Defaulter. func (d *defaulter) AsStringSlice(v Valuer, sep string, dv []string) []string { value, err := v.Value() if err != nil { d.logValuerError(err) return dv } return strings.Split(value, sep) } // AsStringMap implements Defaulter. func (d *defaulter) AsStringMap(v Valuer, rsep, kvsep string, dv map[string]string) map[string]string { value, err := v.Value() if err != nil { d.logValuerError(err) return dv } rows := strings.Split(value, rsep) mvalue := make(map[string]string, len(rows)) for _, row := range rows { kv := strings.SplitN(row, kvsep, 2) if len(kv) == 2 { mvalue[kv[0]] = kv[1] } else { mvalue[kv[0]] = kv[0] } } return mvalue } // AsBool implements Defaulter. func (d *defaulter) AsBool(v Valuer, dv bool) bool { value, err := v.Value() if err != nil { d.logValuerError(err) return dv } bvalue, err := strconv.ParseBool(value) if err != nil { d.logFormatError("bool", err) return dv } return bvalue } // AsInt implements Defaulter. func (d *defaulter) AsInt(v Valuer, dv int) int { value, err := v.Value() if err != nil { d.logValuerError(err) return dv } ivalue, err := strconv.ParseInt(value, 10, 0) if err != nil { d.logFormatError("int", err) return dv } return int(ivalue) } // AsInt64 implements Defaulter. func (d *defaulter) AsInt64(v Valuer, dv int64) int64 { value, err := v.Value() if err != nil { d.logValuerError(err) return dv } ivalue, err := strconv.ParseInt(value, 10, 64) if err != nil { d.logFormatError("int64", err) return dv } return int64(ivalue) } // AsUint implements Defaulter. func (d *defaulter) AsUint(v Valuer, dv uint) uint { value, err := v.Value() if err != nil { d.logValuerError(err) return dv } uivalue, err := strconv.ParseUint(value, 10, 0) if err != nil { d.logFormatError("uint", err) return dv } return uint(uivalue) } // AsUint64 implements Defaulter. func (d *defaulter) AsUint64(v Valuer, dv uint64) uint64 { value, err := v.Value() if err != nil { d.logValuerError(err) return dv } uivalue, err := strconv.ParseUint(value, 10, 64) if err != nil { d.logFormatError("uint64", err) return dv } return uint64(uivalue) } // AsFloat64 implements Defaulter. func (d *defaulter) AsFloat64(v Valuer, dv float64) float64 { value, err := v.Value() if err != nil { d.logValuerError(err) return dv } fvalue, err := strconv.ParseFloat(value, 64) if err != nil { d.logFormatError("float64", err) return dv } return fvalue } // AsTime implements Defaulter. func (d *defaulter) AsTime(v Valuer, layout string, dv time.Time) time.Time { value, err := v.Value() if err != nil { d.logValuerError(err) return dv } tvalue, err := time.Parse(layout, value) if err != nil { d.logFormatError("time", err) return dv } return tvalue } // AsDuration implements Defaulter. func (d *defaulter) AsDuration(v Valuer, dv time.Duration) time.Duration { value, err := v.Value() if err != nil { d.logValuerError(err) return dv } dvalue, err := time.ParseDuration(value) if err != nil { d.logFormatError("duration", err) return dv } return dvalue } // String implements fmt.Stringer. func (d *defaulter) String() string { return fmt.Sprintf("Defaulter{%s}", d.id) } // logValuerError logs the passed valuer error if configured. func (d *defaulter) logValuerError(err error) { d.logError("value returned with error", err) } // logFormatError logs the passed format error if configured. func (d *defaulter) logFormatError(t string, err error) { d.logError(fmt.Sprintf("value has illegal format for %q", t), err) } // logError finally checks logging and formatting before logging an error. func (d *defaulter) logError(format string, err error) { if !d.log { return } format += ": %v" if len(d.id) > 0 { logger.Errorf("(%s) "+format, d.id, err) } else { logger.Errorf(format, err) } } //-------------------- // STRING VALUER //-------------------- // StringValuer implements the Valuer interface for simple strings. type StringValuer string // Value implements the Valuer interface. func (sv StringValuer) Value() (string, error) { v := string(sv) if len(v) == 0 { return "", errors.New("[-empty-]") } return v, nil } // EOF golib-4.24.2/stringex/defaulter_test.go000066400000000000000000000254531315505703200201150ustar00rootroot00000000000000// Tideland Go Library - String Extensions - Unit Tests // // Copyright (C) 2015-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package stringex_test //-------------------- // IMPORTS //-------------------- import ( "errors" "math" "strconv" "testing" "time" "github.com/tideland/golib/audit" "github.com/tideland/golib/stringex" ) //-------------------- // CONSTANTS //-------------------- const ( maxUint = ^uint(0) minUint = 0 maxInt = int(maxUint >> 1) minInt = -maxInt - 1 ) //-------------------- // TESTS //-------------------- // TestAsString checks the access of string values. func TestAsString(t *testing.T) { assert := audit.NewTestingAssertion(t, true) d := stringex.NewDefaulter("AsString", true) tests := []struct { v valuer dv string expected string }{ {valuer{"foo", false}, "bar", "foo"}, {valuer{"foo", true}, "bar", "bar"}, } for i, test := range tests { assert.Logf("test %v %d: %v and default %v", d, i, test.v, test.dv) sv := d.AsString(test.v, test.dv) assert.Equal(sv, test.expected) } } // TestAsStringSlice checks the access of string slice values. func TestAsStringSlice(t *testing.T) { assert := audit.NewTestingAssertion(t, true) d := stringex.NewDefaulter("AsStringSlice", true) tests := []struct { v valuer sep string dv []string expected []string }{ {valuer{"a/b/c", false}, "/", []string{"a"}, []string{"a", "b", "c"}}, {valuer{"a/b/c", true}, "/", []string{"a"}, []string{"a"}}, {valuer{"a/b/c", false}, "/", nil, []string{"a", "b", "c"}}, {valuer{"a/b/c", true}, "/", nil, nil}, {valuer{"", false}, "/", nil, []string{""}}, {valuer{"", true}, "/", []string{"foo"}, []string{"foo"}}, {valuer{"a/b/c", false}, ":", []string{"a"}, []string{"a/b/c"}}, } for i, test := range tests { assert.Logf("test %v %d: %v and default %v", d, i, test.v, test.dv) sv := d.AsStringSlice(test.v, test.sep, test.dv) assert.Equal(sv, test.expected) } } // TestAsStringMap checks the access of string map values. func TestAsStringMap(t *testing.T) { assert := audit.NewTestingAssertion(t, true) d := stringex.NewDefaulter("AsStringMap", true) tests := []struct { v valuer rsep string kvsep string dv map[string]string expected map[string]string }{ {valuer{"a:1/b:2", false}, "/", ":", map[string]string{"a": "1"}, map[string]string{"a": "1", "b": "2"}}, {valuer{"a:1/b:2", true}, "/", ":", map[string]string{"a": "1"}, map[string]string{"a": "1"}}, {valuer{"a:1/b:2", false}, "/", ":", nil, map[string]string{"a": "1", "b": "2"}}, {valuer{"a:1/b:2", true}, "/", ":", nil, nil}, {valuer{"", false}, "/", ":", nil, map[string]string{"": ""}}, {valuer{"", true}, "/", ":", map[string]string{"a": "1"}, map[string]string{"a": "1"}}, {valuer{"a:1/b:2", false}, "|", "=", nil, map[string]string{"a:1/b:2": "a:1/b:2"}}, } for i, test := range tests { assert.Logf("test %v %d: %v and default %v", d, i, test.v, test.dv) sv := d.AsStringMap(test.v, test.rsep, test.kvsep, test.dv) assert.Equal(sv, test.expected) } } // TestAsBool checks the access of bool values. func TestAsBool(t *testing.T) { assert := audit.NewTestingAssertion(t, true) d := stringex.NewDefaulter("AsBool", true) tests := []struct { v valuer dv bool expected bool }{ {valuer{"1", false}, false, true}, {valuer{"t", false}, false, true}, {valuer{"T", false}, false, true}, {valuer{"TRUE", false}, false, true}, {valuer{"true", false}, false, true}, {valuer{"True", false}, false, true}, {valuer{"wahr", false}, true, true}, {valuer{"", true}, true, true}, {valuer{"0", false}, true, false}, {valuer{"f", false}, true, false}, {valuer{"F", false}, true, false}, {valuer{"FALSE", false}, true, false}, {valuer{"false", false}, true, false}, {valuer{"False", false}, true, false}, {valuer{"falsch", false}, false, false}, {valuer{"", true}, false, false}, } for i, test := range tests { assert.Logf("test %v %d: %v and default %v", d, i, test.v, test.dv) bv := d.AsBool(test.v, test.dv) assert.Equal(bv, test.expected) } } // TestAsInt checks the access of int values. func TestAsInt(t *testing.T) { assert := audit.NewTestingAssertion(t, true) maxIntS := strconv.FormatInt(int64(maxInt), 10) minIntS := strconv.FormatInt(int64(minInt), 10) d := stringex.NewDefaulter("AsInt", true) tests := []struct { v valuer dv int expected int }{ {valuer{"0", false}, 0, 0}, {valuer{"1", false}, 0, 1}, {valuer{"-1", false}, 0, -1}, {valuer{maxIntS, false}, 0, maxInt}, {valuer{minIntS, false}, 0, minInt}, {valuer{"999999999999999999999", false}, 1, 1}, {valuer{"-999999999999999999999", false}, 1, 1}, {valuer{"one two three", false}, 1, 1}, {valuer{"1", true}, 2, 2}, {valuer{"-1", true}, -2, -2}, } for i, test := range tests { assert.Logf("test %v %d: %v and default %v", d, i, test.v, test.dv) bv := d.AsInt(test.v, test.dv) assert.Equal(bv, test.expected) } } // TestAsInt64 checks the access of int64 values. func TestAsInt64(t *testing.T) { assert := audit.NewTestingAssertion(t, true) maxInt64S := strconv.FormatInt(math.MaxInt64, 10) minInt64S := strconv.FormatInt(math.MinInt64, 10) d := stringex.NewDefaulter("AsInt64", true) tests := []struct { v valuer dv int64 expected int64 }{ {valuer{"0", false}, 0, 0}, {valuer{"1", false}, 0, 1}, {valuer{"-1", false}, 0, -1}, {valuer{maxInt64S, false}, 0, math.MaxInt64}, {valuer{minInt64S, false}, 0, math.MinInt64}, {valuer{"999999999999999999999", false}, 1, 1}, {valuer{"-999999999999999999999", false}, 1, 1}, {valuer{"one two three", false}, 1, 1}, {valuer{"1", true}, 2, 2}, {valuer{"-1", true}, -2, -2}, } for i, test := range tests { assert.Logf("test %v %d: %v and default %v", d, i, test.v, test.dv) bv := d.AsInt64(test.v, test.dv) assert.Equal(bv, test.expected) } } // TestAsUint checks the access of uint values. func TestAsUint(t *testing.T) { assert := audit.NewTestingAssertion(t, true) maxUintS := strconv.FormatUint(uint64(maxUint), 10) d := stringex.NewDefaulter("AsUint", true) tests := []struct { v valuer dv uint expected uint }{ {valuer{"0", false}, 0, 0}, {valuer{"1", false}, 0, 1}, {valuer{maxUintS, false}, 0, maxUint}, {valuer{"999999999999999999999", false}, 1, 1}, {valuer{"one two three", false}, 1, 1}, {valuer{"-1", true}, 1, 1}, } for i, test := range tests { assert.Logf("test %v %d: %v and default %v", d, i, test.v, test.dv) bv := d.AsUint(test.v, test.dv) assert.Equal(bv, test.expected) } } // TestAsUInt64 checks the access of uint64 values. func TestAsUInt64(t *testing.T) { assert := audit.NewTestingAssertion(t, true) maxUInt64S := strconv.FormatUint(math.MaxUint64, 10) d := stringex.NewDefaulter("AsUInt64", true) tests := []struct { v valuer dv uint64 expected uint64 }{ {valuer{"0", false}, 0, 0}, {valuer{"1", false}, 0, 1}, {valuer{maxUInt64S, false}, 0, math.MaxUint64}, {valuer{"999999999999999999999", false}, 1, 1}, {valuer{"one two three", false}, 1, 1}, {valuer{"-1", true}, 1, 1}, } for i, test := range tests { assert.Logf("test %v %d: %v and default %v", d, i, test.v, test.dv) bv := d.AsUint64(test.v, test.dv) assert.Equal(bv, test.expected) } } // TestAsFloat64 checks the access of float64 values. func TestAsFloat64(t *testing.T) { assert := audit.NewTestingAssertion(t, true) maxFloat64S := strconv.FormatFloat(math.MaxFloat64, 'e', -1, 64) minFloat64S := strconv.FormatFloat(-1*math.MaxFloat64, 'e', -1, 64) d := stringex.NewDefaulter("AsFloat4", true) tests := []struct { v valuer dv float64 expected float64 }{ {valuer{"0.0", false}, 0.0, 0.0}, {valuer{"1.0", false}, 0.0, 1.0}, {valuer{"-1.0", false}, 0.0, -1.0}, {valuer{maxFloat64S, false}, 0.0, math.MaxFloat64}, {valuer{minFloat64S, false}, 0.0, math.MaxFloat64 * -1.0}, {valuer{"9e+999", false}, 1.0, 1.0}, {valuer{"-9e+999", false}, 1.0, 1.0}, {valuer{"one.two", false}, 1.0, 1.0}, {valuer{"1.0", true}, 2.0, 2.0}, {valuer{"-1.0", true}, -2.0, -2.0}, } for i, test := range tests { assert.Logf("test %v %d: %v and default %v", d, i, test.v, test.dv) bv := d.AsFloat64(test.v, test.dv) assert.Equal(bv, test.expected) } } // TestAsTime checks the access of time values. func TestAsTime(t *testing.T) { assert := audit.NewTestingAssertion(t, true) y2k := time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC) nowStr, now := audit.BuildTime(time.RFC3339Nano, 0) d := stringex.NewDefaulter("AsTime", true) tests := []struct { v valuer layout string dv time.Time expected time.Time }{ {valuer{nowStr, false}, time.RFC3339Nano, y2k, now}, {valuer{nowStr, true}, time.RFC3339Nano, y2k, y2k}, {valuer{nowStr, false}, "any false layout", y2k, y2k}, {valuer{"", false}, time.RFC3339Nano, y2k, y2k}, } for i, test := range tests { assert.Logf("test %v %d: %v and default %v", d, i, test.v, test.dv) bv := d.AsTime(test.v, test.layout, test.dv) assert.Equal(bv, test.expected) } } // TestAsDuration checks the access of duration values. func TestAsDuration(t *testing.T) { assert := audit.NewTestingAssertion(t, true) d := stringex.NewDefaulter("AsDuration", true) tests := []struct { v valuer dv time.Duration expected time.Duration }{ {valuer{"1s", false}, time.Second, time.Second}, {valuer{"1s", true}, time.Minute, time.Minute}, {valuer{"2", false}, time.Minute, time.Minute}, {valuer{"1 hour", false}, time.Minute, time.Minute}, {valuer{"4711h", false}, time.Minute, 4711 * time.Hour}, } for i, test := range tests { assert.Logf("test %v %d: %v and default %v", d, i, test.v, test.dv) bv := d.AsDuration(test.v, test.dv) assert.Equal(bv, test.expected) } } // TestDefaulterString checks the output of the defaulter as string. func TestDefaulterString(t *testing.T) { assert := audit.NewTestingAssertion(t, true) d := stringex.NewDefaulter("my-id", true) s := d.String() assert.Equal(s, "Defaulter{my-id}") } // TestStringValuer checks the simple valuer for plain strings. func StringValuer(t *testing.T) { assert := audit.NewTestingAssertion(t, true) d := stringex.NewDefaulter("StringValuer", false) sv := stringex.StringValuer("4711") assert.Equal(d.AsString(sv, "12345"), "4711") assert.Equal(d.AsInt(sv, 12345), 4711) sv = stringex.StringValuer("") assert.Equal(d.AsString(sv, "12345"), "12345") assert.Equal(d.AsInt(sv, 12345), 12345) } //-------------------- // HELPER //-------------------- type valuer struct { value string err bool } func (v valuer) Value() (string, error) { if v.err { return "", errors.New(v.value) } return v.value, nil } func (v valuer) String() string { if v.err { return "value '" + v.value + "' with error" } return "value '" + v.value + "' without error" } // EOF golib-4.24.2/stringex/doc.go000066400000000000000000000022511315505703200156370ustar00rootroot00000000000000// Tideland Go Library - String Extensions // // Copyright (C) 2015-2017 Frank Mueller / Tideland / Oldenburg / Germay // // All rights reserved. Use of this source code is governed // by the new BSD license. // Package stringex of the Tideland Go Library helps when working with // strings. So SplitFilter() and SplitMap() split given strings by a // separator and user defined functions are called for each part to // filter or map those. // // Matches() provides a more simple string matching than regular // expressions. Patterns are ? for one char, * for multiple chars, // and [aeiou] or [0-9] for group or ranges of chars. Both latter // can be negotiated with [^abc] while the pattern chars also can // be escaped with \. // // While the Valuer defines the interface to anything that may // return a value as string the Default helps to interpret these // strings as other data types. In case they don't match a default // value will be returned. // // Processor defines an interface for the processing of strings. // Those easily can be chained or used for stream splitting again // working with processor chains. Some processors are already // pre-defined. package stringex // EOF golib-4.24.2/stringex/processor.go000066400000000000000000000124571315505703200171220ustar00rootroot00000000000000// Tideland Go Library - String Extensions // // Copyright (C) 2015-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package stringex //-------------------- // IMPORTS //-------------------- import ( "fmt" "regexp" "strings" ) //-------------------- // PROCESSOR //-------------------- // Processor defines a type able to process strings. type Processor interface { // Process takes a string and processes it. If the result is to // be ignored the bool has to be false. Process(in string) (string, bool) } //-------------------- // PROCESSOR FUNCTIONS //-------------------- // ProcessorFunc describes functions processing a string and returning // the new one. A returned false means to ignore the result. type ProcessorFunc func(in string) (string, bool) // Process implements Processor. func (pf ProcessorFunc) Process(in string) (string, bool) { return pf(in) } // WrapProcessorFunc takes a standard string processing function and // returns it as a ProcessorFunc. func WrapProcessorFunc(f func(fin string) string) ProcessorFunc { return func(in string) (string, bool) { return f(in), true } } // errorProcessorFunc returns a processor func used in case of failing // preparation steps. func errorProcessorFunc(err error) ProcessorFunc { return func(in string) (string, bool) { return fmt.Sprintf("error processing '%s': %v", in, err), false } } //-------------------- // FACTORIES //-------------------- // NewChainProcessor creates a processor chaning the passed processors. func NewChainProcessor(processors ...Processor) ProcessorFunc { return func(in string) (string, bool) { out := in ok := true for _, processor := range processors { out, ok = processor.Process(out) if !ok { return "", false } } return out, ok } } // NewConditionProcessor creates a processor taking the first processor // for creating a temporary result and a decision. Based on the decision // the temporary result is passed to an affirmer or a negater. func NewConditionProcessor(decider, affirmer, negater Processor) ProcessorFunc { return func(in string) (string, bool) { temp, ok := decider.Process(in) if ok { return affirmer.Process(temp) } return negater.Process(temp) } } // NewLoopProcessor creates a processor letting the processor function // work on the input until it returns false (aka while true). Itself then // will return the processed sting and always true. func NewLoopProcessor(processor Processor) ProcessorFunc { return func(in string) (string, bool) { temp, ok := processor.Process(in) for ok { temp, ok = processor.Process(temp) } return temp, true } } // NewSplitMapProcessor creates a processor splitting the input and // mapping the parts. It will only contain those where the mapper // returns true. So it can be used as a filter too. Afterwards the // collected mapped parts are joined again. func NewSplitMapProcessor(sep string, mapper Processor) ProcessorFunc { return func(in string) (string, bool) { parts := strings.Split(in, sep) out := []string{} for _, part := range parts { if mp, ok := mapper.Process(part); ok { out = append(out, mp) } } return strings.Join(out, sep), true } } // NewSubstringProcessor returns a processor slicing the input // based on the index and length. func NewSubstringProcessor(index, length int) ProcessorFunc { return func(in string) (string, bool) { if length < 1 { return "", false } if index < 0 { index = 0 } if index >= len(in) { return "", true } out := in[index:] if length > len(out) { length = len(out) } return out[:length], true } } // NewMatchProcessor returns a processor evaluating the input // against a given pattern and returns the input and true // when it is matching. func NewMatchProcessor(pattern string) ProcessorFunc { r, err := regexp.Compile(pattern) if err != nil { return errorProcessorFunc(err) } return func(in string) (string, bool) { return in, r.MatchString(in) } } // NewTrimFuncProcessor returns a processor trimming prefix and // suffix of the input based on the return value of the passed // function checking each rune. func NewTrimFuncProcessor(f func(r rune) bool) ProcessorFunc { return func(in string) (string, bool) { return strings.TrimFunc(in, f), true } } // NewTrimPrefixProcessor returns a processor trimming a prefix of // the input as long as it can find it. func NewTrimPrefixProcessor(prefix string) ProcessorFunc { prefixTrimmer := func(in string) (string, bool) { out := strings.TrimPrefix(in, prefix) return out, out != in } return NewLoopProcessor(ProcessorFunc(prefixTrimmer)) } // NewTrimSuffixProcessor returns a processor trimming a prefix of // the input as long as it can find it. func NewTrimSuffixProcessor(prefix string) ProcessorFunc { suffixTrimmer := func(in string) (string, bool) { out := strings.TrimSuffix(in, prefix) return out, out != in } return NewLoopProcessor(ProcessorFunc(suffixTrimmer)) } // NewUpperProcessor returns a processor converting the // input to upper-case. func NewUpperProcessor() ProcessorFunc { return WrapProcessorFunc(strings.ToUpper) } // NewLowerProcessor returns a processor converting the // input to lower-case. func NewLowerProcessor() ProcessorFunc { return WrapProcessorFunc(strings.ToLower) } // EOF golib-4.24.2/stringex/processor_test.go000066400000000000000000000121371315505703200201540ustar00rootroot00000000000000// Tideland Go Library - String Extensions - Unit Tests // // Copyright (C) 2015-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package stringex_test //-------------------- // IMPORTS //-------------------- import ( "strings" "testing" "github.com/tideland/golib/audit" "github.com/tideland/golib/stringex" ) //-------------------- // TESTS //-------------------- // TestWrapping tests wrapping a standard function to a processor. func TestWrapping(t *testing.T) { assert := audit.NewTestingAssertion(t, true) upperCaser := stringex.WrapProcessorFunc(strings.ToUpper) value, ok := upperCaser("test") assert.True(ok) assert.Equal(value, "TEST") } // TestSplitMapProcessor tests the splitting and mapping. func TestSplitMapProcessor(t *testing.T) { assert := audit.NewTestingAssertion(t, true) sep := "the" upperCaser := stringex.WrapProcessorFunc(strings.ToUpper) splitMapper := stringex.NewSplitMapProcessor(sep, upperCaser) value, ok := splitMapper("the quick brown fox jumps over the lazy dog") assert.True(ok) assert.Equal(value, "the QUICK BROWN FOX JUMPS OVER the LAZY DOG") } // TestSubstringProcessor tests retrieving substrings. func TestSubstringProcessor(t *testing.T) { assert := audit.NewTestingAssertion(t, true) tests := []struct { index int length int in string out string ok bool }{ {0, 5, "yadda", "yadda", true}, {0, 3, "yadda", "yad", true}, {2, 3, "yadda", "dda", true}, {2, 5, "yadda", "dda", true}, {-1, 5, "yadda", "yadda", true}, {-1, 10, "yadda", "yadda", true}, {0, 0, "yadda", "", false}, } for _, test := range tests { substringer := stringex.NewSubstringProcessor(test.index, test.length) out, ok := substringer(test.in) assert.Equal(ok, test.ok) assert.Equal(out, test.out) } } // TestMatchProcessor tests the matching of patterns. func TestMatchProcessor(t *testing.T) { assert := audit.NewTestingAssertion(t, true) tests := []struct { pattern string in string out string ok bool }{ {"[0-9]+", "+++12345+++", "+++12345+++", true}, {"^[0-9]+", "+++12345+++", "+++12345+++", false}, {"[", "+++12345+++", "error processing '+++12345+++': error parsing regexp: missing closing ]: `[`", false}, } for _, test := range tests { matcher := stringex.NewMatchProcessor(test.pattern) out, ok := matcher(test.in) assert.Equal(ok, test.ok) assert.Equal(out, test.out) } } // TestTrimmingProcessors tests the trimming. func TestTrimmingProcessors(t *testing.T) { assert := audit.NewTestingAssertion(t, true) in := "+++++foo+++" // Prefix. plusPreTrimmer := stringex.NewTrimPrefixProcessor("+") plusPlusPreTrimmer := stringex.NewTrimPrefixProcessor("++") value, ok := plusPreTrimmer(in) assert.True(ok) assert.Equal(value, "foo+++") value, ok = plusPlusPreTrimmer(in) assert.True(ok) assert.Equal(value, "+foo+++") // Suffix. plusSufTrimmer := stringex.NewTrimSuffixProcessor("+") plusPlusSufTrimmer := stringex.NewTrimSuffixProcessor("++") value, ok = plusSufTrimmer(in) assert.True(ok) assert.Equal(value, "+++++foo") value, ok = plusPlusSufTrimmer(in) assert.True(ok) assert.Equal(value, "+++++foo+") // Chaining. plusTrimmer := stringex.NewChainProcessor(plusPreTrimmer, plusSufTrimmer) plusPlusTrimmer := stringex.NewChainProcessor(plusPlusPreTrimmer, plusPlusSufTrimmer) value, ok = plusTrimmer(in) assert.True(ok) assert.Equal(value, "foo") value, ok = plusPlusTrimmer(in) assert.True(ok) assert.Equal(value, "+foo+") } // TestUpperLowerProcessor tests converting strings to upper- or lowe-case. func TestUpperLowerProcessor(t *testing.T) { assert := audit.NewTestingAssertion(t, true) in := "this IS in UPPER & lower cAsE" uppercaser := stringex.NewUpperProcessor() lowercaser := stringex.NewLowerProcessor() value, ok := uppercaser(in) assert.True(ok) assert.Equal(value, "THIS IS IN UPPER & LOWER CASE") value, ok = lowercaser(in) assert.True(ok) assert.Equal(value, "this is in upper & lower case") } // TestProcessorScenario tests the combination of multiple processors. func TestProcessorScenario(t *testing.T) { assert := audit.NewTestingAssertion(t, true) in := "+++++Yadda+++/-----Foobar--/+-+-Testing-+-+/Out" trimmer := stringex.NewTrimFuncProcessor(func(r rune) bool { return r == '+' || r == '-' }) substringer := stringex.NewSubstringProcessor(0, 4) omatcher := stringex.NewMatchProcessor("[Oo]+") uppercaser := stringex.NewUpperProcessor() lowercaser := stringex.NewLowerProcessor() matchcaser := stringex.NewConditionProcessor(omatcher, uppercaser, lowercaser) bracer := stringex.ProcessorFunc(func(in string) (string, bool) { return "(" + in + ")", true }) updater := stringex.NewChainProcessor(trimmer, substringer, matchcaser, bracer) allUpdater := stringex.NewSplitMapProcessor("/", updater) replacer := stringex.ProcessorFunc(func(in string) (string, bool) { return strings.Replace(in, "/", "::", -1), true }) fullUpdater := stringex.NewChainProcessor(allUpdater, replacer) value, ok := fullUpdater(in) assert.True(ok) assert.Equal(value, "(yadd)::(FOOB)::(test)::(OUT)") } // EOF golib-4.24.2/stringex/stringex.go000066400000000000000000000113151315505703200167360ustar00rootroot00000000000000// Tideland Go Library - String Extensions // // Copyright (C) 2015-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code valuePos governed // by the new BSD license. package stringex //-------------------- // IMPORTS //-------------------- import ( "strings" "unicode" ) //-------------------- // VALUER //-------------------- // Valuer describes returning a string value or an error // if it does not exist are another access error happened. type Valuer interface { // Value returns a string or a potential error during access. Value() (string, error) } //-------------------- // SPLITTER //-------------------- // SplitFilter splits the string s by the separator // sep and then filters the parts. Only those where f // returns true will be part of the result. So it even // cout be empty. func SplitFilter(s, sep string, f func(p string) bool) []string { parts := strings.Split(s, sep) out := []string{} for _, part := range parts { if f(part) { out = append(out, part) } } return out } // SplitMap splits the string s by the separator // sep and then maps the parts by the function m. // Only those where m also returns true will be part // of the result. So it even could be empty. func SplitMap(s, sep string, m func(p string) (string, bool)) []string { parts := strings.Split(s, sep) out := []string{} for _, part := range parts { if mp, ok := m(part); ok { out = append(out, mp) } } return out } //-------------------- // MATCHER //-------------------- // Matches checks if the pattern matches a given value. Here // ? matches one char, * a group of chars, [any] any of these // chars, and [0-9] any of this char range. The latter can // be negotiated with [^abc] and \ escapes the pattern chars. func Matches(pattern, value string, ignoreCase bool) bool { patternRunes := append([]rune(pattern), '\u0000') patternLen := len(patternRunes) - 1 patternPos := 0 valueRunes := append([]rune(value), '\u0000') valueLen := len(valueRunes) - 1 valuePos := 0 for patternLen > 0 { switch patternRunes[patternPos] { case '*': // Asterisk for group of characters. for patternRunes[patternPos+1] == '*' { patternPos++ patternLen-- } if patternLen == 1 { return true } for valueLen > 0 { patternCopy := make([]rune, len(patternRunes[patternPos+1:])) valueCopy := make([]rune, len(valueRunes[valuePos:])) copy(patternCopy, patternRunes[patternPos+1:]) copy(valueCopy, valueRunes[valuePos:]) if Matches(string(patternCopy), string(valueCopy), ignoreCase) { return true } valuePos++ valueLen-- } return false case '?': // Question mark for one character. if valueLen == 0 { return false } valuePos++ valueLen-- case '[': // Square brackets for groups of valid characters. patternPos++ patternLen-- not := (patternRunes[patternPos] == '^') match := false if not { patternPos++ patternLen-- } group: for { switch { case patternRunes[patternPos] == '\\': patternPos++ patternLen-- if patternRunes[patternPos] == valueRunes[valuePos] { match = true } case patternRunes[patternPos] == ']': break group case patternLen == 0: patternPos-- patternLen++ break group case patternRunes[patternPos+1] == '-' && patternLen >= 3: start := patternRunes[patternPos] end := patternRunes[patternPos+2] vr := valueRunes[valuePos] if start > end { start, end = end, start } if ignoreCase { start = unicode.ToLower(start) end = unicode.ToLower(end) vr = unicode.ToLower(vr) } patternPos += 2 patternLen -= 2 if vr >= start && vr <= end { match = true } default: if !ignoreCase { if patternRunes[patternPos] == valueRunes[valuePos] { match = true } } else { if unicode.ToLower(patternRunes[patternPos]) == unicode.ToLower(valueRunes[valuePos]) { match = true } } } patternPos++ patternLen-- } if not { match = !match } if !match { return false } valuePos++ valueLen-- case '\\': if patternLen >= 2 { patternPos++ patternLen-- } fallthrough default: if !ignoreCase { if patternRunes[patternPos] != valueRunes[valuePos] { return false } } else { if unicode.ToLower(patternRunes[patternPos]) != unicode.ToLower(valueRunes[valuePos]) { return false } } valuePos++ valueLen-- } patternPos++ patternLen-- if valueLen == 0 { for patternRunes[patternPos] == '*' { patternPos++ patternLen-- } break } } if patternLen == 0 && valueLen == 0 { return true } return false } // EOF golib-4.24.2/stringex/stringex_test.go000066400000000000000000000110731315505703200177760ustar00rootroot00000000000000// Tideland Go Library - String Extensions - Unit Tests // // Copyright (C) 2015-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package stringex_test //-------------------- // IMPORTS //-------------------- import ( "strings" "testing" "github.com/tideland/golib/audit" "github.com/tideland/golib/stringex" ) //-------------------- // TESTS //-------------------- // TestSplitFilter tests splitting with a filter. func TestSplitFilter(t *testing.T) { assert := audit.NewTestingAssertion(t, true) tests := []struct { name string in string sep string filter func(string) bool out []string }{ { "all fine", "a/b/c", "/", func(p string) bool { return p != "" }, []string{"a", "b", "c"}, }, { "filter empty parts", "/a/b//c/", "/", func(p string) bool { return p != "" }, []string{"a", "b", "c"}, }, { "filter all parts", "a/b/c", "/", func(p string) bool { return p == "x" }, []string{}, }, { "filter empty input", "", "/", func(p string) bool { return true }, []string{""}, }, { "filter not splitted", "/a/b/c/", "+", func(p string) bool { return p != "" }, []string{"/a/b/c/"}, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { defer assert.SetFailable(t)() out := stringex.SplitFilter(test.in, test.sep, test.filter) assert.Equal(out, test.out) }) } } // TestSplitMap tests spliting with a mapper. func TestSplitMap(t *testing.T) { assert := audit.NewTestingAssertion(t, true) tests := []struct { name string in string sep string mapper func(string) (string, bool) out []string }{ { "uppercase all", "a/b/c", "/", func(p string) (string, bool) { return strings.ToUpper(p), true }, []string{"A", "B", "C"}, }, { "filter empty parts, uppercase the rest", "/a/b//c/", "/", func(p string) (string, bool) { if p != "" { return strings.ToUpper(p), true } return "", false }, []string{"A", "B", "C"}, }, { "filter all parts", "a/b/c", "/", func(p string) (string, bool) { return p, p == "x" }, []string{}, }, { "encapsulate even empty input", "", "/", func(p string) (string, bool) { return "(" + p + ")", true }, []string{"()"}, }, { "uppercase not splitted", "/a/b/c/", "+", func(p string) (string, bool) { return strings.ToUpper(p), true }, []string{"/A/B/C/"}, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { defer assert.SetFailable(t)() out := stringex.SplitMap(test.in, test.sep, test.mapper) assert.Equal(out, test.out) }) } } // TestMatches tests matching string. func TestMatches(t *testing.T) { assert := audit.NewTestingAssertion(t, true) tests := []struct { name string pattern string value string ignoreCase bool out bool }{ { "equal pattern and string without wildcards", "quick brown fox", "quick brown fox", true, true, }, { "unequal pattern and string without wildcards", "quick brown fox", "lazy dog", true, false, }, { "matching pattern with one question mark", "quick brown f?x", "quick brown fox", true, true, }, { "matching pattern with one asterisk", "quick*fox", "quick brown fox", true, true, }, { "matching pattern with char group", "quick brown f[ao]x", "quick brown fox", true, true, }, { "not-matching pattern with char group", "quick brown f[eiu]x", "quick brown fox", true, false, }, { "matching pattern with char range", "quick brown f[a-u]x", "quick brown fox", true, true, }, { "not-matching pattern with char range", "quick brown f[^a-u]x", "quick brown fox", true, false, }, { "matching pattern with char group not ignoring care", "quick * F[aeiou]x", "quick * Fox", false, true, }, { "not-matching pattern with char group not ignoring care", "quick * F[aeiou]x", "quick * fox", false, false, }, { "matching pattern with escape", "quick \\* f\\[o\\]x", "quick * f[o]x", true, true, }, { "not-matching pattern with escape", "quick \\* f\\[o\\]x", "quick brown fox", true, false, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { defer assert.SetFailable(t)() out := stringex.Matches(test.pattern, test.value, test.ignoreCase) assert.Equal(out, test.out) }) } } // EOF golib-4.24.2/timex/000077500000000000000000000000001315505703200140265ustar00rootroot00000000000000golib-4.24.2/timex/crontab.go000066400000000000000000000054251315505703200160130ustar00rootroot00000000000000// Tideland Go Library - Time Extensions // // Copyright (C) 2009-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package timex //-------------------- // IMPORTS //-------------------- import ( "time" "github.com/tideland/golib/errors" "github.com/tideland/golib/logger" "github.com/tideland/golib/loop" ) //-------------------- // CRONTAB //-------------------- // Job is executed by the crontab. type Job interface { // ShallExecute decides when called if the job // shal be executed. ShallExecute(t time.Time) bool // Execute executes the job. If the method returns // false or an error it will be removed. Execute() (bool, error) } // cronCommand operates on a crontab. type command struct { add bool id string job Job } // Crontab is one cron server. A system can run multiple in // parallel. type Crontab struct { frequency time.Duration jobs map[string]Job commandChan chan *command loop loop.Loop } // NewCrontab creates a cron server. func NewCrontab(freq time.Duration) *Crontab { c := &Crontab{ frequency: freq, jobs: make(map[string]Job), commandChan: make(chan *command), } c.loop = loop.GoRecoverable(c.backendLoop, c.checkRecovering, "crontab", freq.String()) return c } // Stop terminates the cron server. func (c *Crontab) Stop() error { return c.loop.Stop() } // Add adds a new job to the server. func (c *Crontab) Add(id string, job Job) { c.commandChan <- &command{true, id, job} } // Remove removes a job from the server. func (c *Crontab) Remove(id string) { c.commandChan <- &command{false, id, nil} } // backendLoop runs the server backend. func (c *Crontab) backendLoop(l loop.Loop) error { ticker := time.NewTicker(c.frequency) for { select { case <-l.ShallStop(): return nil case cmd := <-c.commandChan: if cmd.add { c.jobs[cmd.id] = cmd.job } else { delete(c.jobs, cmd.id) } case now := <-ticker.C: for id, job := range c.jobs { c.do(id, job, now) } } } } // checkRecovering checks if the backend can be recovered. func (c *Crontab) checkRecovering(rs loop.Recoverings) (loop.Recoverings, error) { if rs.Frequency(12, time.Minute) { logger.Errorf("crontab cannot be recovered: %v", rs.Last().Reason) return nil, errors.New(ErrCrontabCannotBeRecovered, errorMessages, rs.Last().Reason) } logger.Warningf("crontab recovered: %v", rs.Last().Reason) return rs.Trim(12), nil } // do checks and performs a job. func (c *Crontab) do(id string, job Job, now time.Time) { if job.ShallExecute(now) { go func() { cont, err := job.Execute() if err != nil { logger.Errorf("job %q removed after error: %v", id, err) cont = false } if !cont { c.Remove(id) } }() } } // EOF golib-4.24.2/timex/doc.go000066400000000000000000000007171315505703200151270ustar00rootroot00000000000000// Tideland Go Library - Time Extensions // // Copyright (C) 2009-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. // Package timex helps when working with times and dates. Beside // tests it contains a crontab for chronological jobs and a retry // function to let code blocks be retried under well defined conditions // regarding time and count. package timex // EOF golib-4.24.2/timex/errors.go000066400000000000000000000013511315505703200156710ustar00rootroot00000000000000// Tideland Go Library - Time Extensions // // Copyright (C) 2015-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package timex //-------------------- // IMPORTS //-------------------- import ( "github.com/tideland/golib/errors" ) //-------------------- // CONSTANTS //-------------------- // Error codes of the timex package. const ( ErrCrontabCannotBeRecovered = iota + 1 ErrRetriedTooLong ErrRetriedTooOften ) var errorMessages = errors.Messages{ ErrCrontabCannotBeRecovered: "crontab cannot be recovered: %v", ErrRetriedTooLong: "retried longer than %v", ErrRetriedTooOften: "retried more than %d times", } // EOF golib-4.24.2/timex/retry.go000066400000000000000000000040601315505703200155220ustar00rootroot00000000000000// Tideland Go Library - Time Extensions // // Copyright (C) 2009-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package timex //-------------------- // IMPORTS //-------------------- import ( "time" "github.com/tideland/golib/errors" ) //-------------------- // RETRY //-------------------- // RetryStrategy describes how often the function in Retry is executed, the // initial break between those retries, how much this time is incremented // for each retry, and the maximum timeout. type RetryStrategy struct { Count int Break time.Duration BreakIncrement time.Duration Timeout time.Duration } // ShortAttempt returns a predefined short retry strategy. func ShortAttempt() RetryStrategy { return RetryStrategy{ Count: 10, Break: 50 * time.Millisecond, BreakIncrement: 0, Timeout: 5 * time.Second, } } // MediumAttempt returns a predefined medium retry strategy. func MediumAttempt() RetryStrategy { return RetryStrategy{ Count: 50, Break: 10 * time.Millisecond, BreakIncrement: 10 * time.Millisecond, Timeout: 30 * time.Second, } } // LongAttempt returns a predefined long retry strategy. func LongAttempt() RetryStrategy { return RetryStrategy{ Count: 100, Break: 10 * time.Millisecond, BreakIncrement: 25 * time.Millisecond, Timeout: 5 * time.Minute, } } // Retry executes the passed function until it returns true or an error. // These retries are restricted by the retry strategy. func Retry(f func() (bool, error), rs RetryStrategy) error { timeout := time.Now().Add(rs.Timeout) sleep := rs.Break for i := 0; i < rs.Count; i++ { done, err := f() if err != nil { return err } if done { return nil } if time.Now().After(timeout) { return errors.New(ErrRetriedTooLong, errorMessages, rs.Timeout) } time.Sleep(sleep) sleep += rs.BreakIncrement } return errors.New(ErrRetriedTooOften, errorMessages, rs.Count) } // EOF golib-4.24.2/timex/timex.go000066400000000000000000000121471315505703200155100ustar00rootroot00000000000000// Tideland Go Library - Time Extensions // // Copyright (C) 2009-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package timex //-------------------- // IMPORTS //-------------------- import ( "time" ) //-------------------- // RANGES //-------------------- // YearInList test if the year of a time is in a given list. func YearInList(t time.Time, years []int) bool { for _, year := range years { if t.Year() == year { return true } } return false } // YearInRange tests if a year of a time is in a given range. func YearInRange(t time.Time, minYear, maxYear int) bool { return (minYear <= t.Year()) && (t.Year() <= maxYear) } // MonthInList tests if the month of a time is in a given list. func MonthInList(t time.Time, months []time.Month) bool { for _, month := range months { if t.Month() == month { return true } } return false } // MonthInRange tests if a month of a time is in a given range. func MonthInRange(t time.Time, minMonth, maxMonth time.Month) bool { return (minMonth <= t.Month()) && (t.Month() <= maxMonth) } // DayInList tests if the day of a time is in a given list. func DayInList(t time.Time, days []int) bool { for _, day := range days { if t.Day() == day { return true } } return false } // DayInRange tests if a day of a time is in a given range. func DayInRange(t time.Time, minDay, maxDay int) bool { return (minDay <= t.Day()) && (t.Day() <= maxDay) } // HourInList tests if the hour of a time is in a given list. func HourInList(t time.Time, hours []int) bool { for _, hour := range hours { if t.Hour() == hour { return true } } return false } // HourInRange tests if a hour of a time is in a given range. func HourInRange(t time.Time, minHour, maxHour int) bool { return (minHour <= t.Hour()) && (t.Hour() <= maxHour) } // MinuteInList tests if the minute of a time is in a given list. func MinuteInList(t time.Time, minutes []int) bool { for _, minute := range minutes { if t.Minute() == minute { return true } } return false } // MinuteInRange tests if a minute of a time is in a given range. func MinuteInRange(t time.Time, minMinute, maxMinute int) bool { return (minMinute <= t.Minute()) && (t.Minute() <= maxMinute) } // SecondInList tests if the second of a time is in a given list. func SecondInList(t time.Time, seconds []int) bool { for _, second := range seconds { if t.Second() == second { return true } } return false } // SecondInRange tests if a second of a time is in a given range. func SecondInRange(t time.Time, minSecond, maxSecond int) bool { return (minSecond <= t.Second()) && (t.Second() <= maxSecond) } // WeekdayInList tests if the weekday of a time is in a given list. func WeekdayInList(t time.Time, weekdays []time.Weekday) bool { for _, weekday := range weekdays { if t.Weekday() == weekday { return true } } return false } // WeekdayInRange tests if a weekday of a time is in a given range. func WeekdayInRange(t time.Time, minWeekday, maxWeekday time.Weekday) bool { return (minWeekday <= t.Weekday()) && (t.Weekday() <= maxWeekday) } //-------------------- // BEGIN / END //-------------------- // UnitOfTime describes whose begin/end is wanted. type UnitOfTime int // Different units of time. const ( Second UnitOfTime = iota + 1 Minute Hour Day Month Year ) // BeginOf returns the begin of the passed unit for the given time. func BeginOf(t time.Time, unit UnitOfTime) time.Time { // Retrieve the individual parts of the given time. year := t.Year() month := t.Month() day := t.Day() hour := t.Hour() minute := t.Minute() second := t.Second() loc := t.Location() // Build new time. switch unit { case Second: return time.Date(year, month, day, hour, minute, second, 0, loc) case Minute: return time.Date(year, month, day, hour, minute, 0, 0, loc) case Hour: return time.Date(year, month, day, hour, 0, 0, 0, loc) case Day: return time.Date(year, month, day, 0, 0, 0, 0, loc) case Month: return time.Date(year, month, 1, 0, 0, 0, 0, loc) case Year: return time.Date(year, time.January, 1, 0, 0, 0, 0, loc) default: return t } } // EndOf returns the end of the passed unit for the given time. func EndOf(t time.Time, unit UnitOfTime) time.Time { // Retrieve the individual parts of the given time. year := t.Year() month := t.Month() day := t.Day() hour := t.Hour() minute := t.Minute() second := t.Second() loc := t.Location() // Build new time. switch unit { case Second: return time.Date(year, month, day, hour, minute, second, 999999999, loc) case Minute: return time.Date(year, month, day, hour, minute, 59, 999999999, loc) case Hour: return time.Date(year, month, day, hour, 59, 59, 999999999, loc) case Day: return time.Date(year, month, day, 23, 59, 59, 999999999, loc) case Month: // Catching leap years makes the month a bit more complex. _, nextMonth, _ := t.AddDate(0, 1, 0).Date() return time.Date(year, nextMonth, 1, 23, 59, 59, 999999999, loc).AddDate(0, 0, -1) case Year: return time.Date(year, time.December, 31, 23, 59, 59, 999999999, loc) default: return t } } // EOF golib-4.24.2/timex/timex_test.go000066400000000000000000000152621315505703200165500ustar00rootroot00000000000000// Tideland Go Library - Time Extensions - Unit Tests // // Copyright (C) 2009-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package timex_test //-------------------- // IMPORTS //-------------------- import ( "errors" "testing" "time" "github.com/tideland/golib/audit" "github.com/tideland/golib/timex" ) //-------------------- // TESTS //-------------------- // Test time containments. func TestTimeContainments(t *testing.T) { assert := audit.NewTestingAssertion(t, true) // Create some test data. ts := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC) years := []int{2008, 2009, 2010} months := []time.Month{10, 11, 12} days := []int{10, 11, 12, 13, 14} hours := []int{20, 21, 22, 23} minutes := []int{0, 5, 10, 15, 20, 25} seconds := []int{0, 15, 30, 45} weekdays := []time.Weekday{time.Monday, time.Tuesday, time.Wednesday} assert.True(timex.YearInList(ts, years), "Go time in year list.") assert.True(timex.YearInRange(ts, 2005, 2015), "Go time in year range.") assert.True(timex.MonthInList(ts, months), "Go time in month list.") assert.True(timex.MonthInRange(ts, 7, 12), "Go time in month range.") assert.True(timex.DayInList(ts, days), "Go time in day list.") assert.True(timex.DayInRange(ts, 5, 15), "Go time in day range .") assert.True(timex.HourInList(ts, hours), "Go time in hour list.") assert.True(timex.HourInRange(ts, 20, 31), "Go time in hour range .") assert.True(timex.MinuteInList(ts, minutes), "Go time in minute list.") assert.True(timex.MinuteInRange(ts, 0, 5), "Go time in minute range .") assert.True(timex.SecondInList(ts, seconds), "Go time in second list.") assert.True(timex.SecondInRange(ts, 0, 5), "Go time in second range .") assert.True(timex.WeekdayInList(ts, weekdays), "Go time in weekday list.") assert.True(timex.WeekdayInRange(ts, time.Monday, time.Friday), "Go time in weekday range .") } // TestBeginOf tests the calculation of a beginning of a unit of time. func TestBeginOf(t *testing.T) { assert := audit.NewTestingAssertion(t, true) ts := time.Date(2015, time.August, 2, 15, 10, 45, 12345, time.UTC) assert.Equal(timex.BeginOf(ts, timex.Second), time.Date(2015, time.August, 2, 15, 10, 45, 0, time.UTC)) assert.Equal(timex.BeginOf(ts, timex.Minute), time.Date(2015, time.August, 2, 15, 10, 0, 0, time.UTC)) assert.Equal(timex.BeginOf(ts, timex.Hour), time.Date(2015, time.August, 2, 15, 0, 0, 0, time.UTC)) assert.Equal(timex.BeginOf(ts, timex.Day), time.Date(2015, time.August, 2, 0, 0, 0, 0, time.UTC)) assert.Equal(timex.BeginOf(ts, timex.Month), time.Date(2015, time.August, 1, 0, 0, 0, 0, time.UTC)) assert.Equal(timex.BeginOf(ts, timex.Year), time.Date(2015, time.January, 1, 0, 0, 0, 0, time.UTC)) } // TestEndOf tests the calculation of a ending of a unit of time. func TestEndOf(t *testing.T) { assert := audit.NewTestingAssertion(t, true) ts := time.Date(2012, time.February, 2, 15, 10, 45, 12345, time.UTC) assert.Equal(timex.EndOf(ts, timex.Second), time.Date(2012, time.February, 2, 15, 10, 45, 999999999, time.UTC)) assert.Equal(timex.EndOf(ts, timex.Minute), time.Date(2012, time.February, 2, 15, 10, 59, 999999999, time.UTC)) assert.Equal(timex.EndOf(ts, timex.Hour), time.Date(2012, time.February, 2, 15, 59, 59, 999999999, time.UTC)) assert.Equal(timex.EndOf(ts, timex.Day), time.Date(2012, time.February, 2, 23, 59, 59, 999999999, time.UTC)) assert.Equal(timex.EndOf(ts, timex.Month), time.Date(2012, time.February, 29, 23, 59, 59, 999999999, time.UTC)) assert.Equal(timex.EndOf(ts, timex.Year), time.Date(2012, time.December, 31, 23, 59, 59, 999999999, time.UTC)) } // Test crontab keeping the job. func TestCrontabKeep(t *testing.T) { assert := audit.NewTestingAssertion(t, true) // Create test crontab with job. c := timex.NewCrontab(50 * time.Millisecond) j := &cronjob{[]time.Time{}, false, false} c.Add("keep", j) time.Sleep(time.Second) c.Remove("keep") c.Stop() for i := range j.times { if i > 0 { d := j.times[i].Sub(j.times[i-1]) assert.True(d.Seconds() >= 0.05) } } } // Test crontab removing the job. func TestCrontabRemove(t *testing.T) { assert := audit.NewTestingAssertion(t, true) // Create test crontab with job. c := timex.NewCrontab(10 * time.Millisecond) j := &cronjob{[]time.Time{}, false, false} c.Add("remove", j) time.Sleep(500 * time.Millisecond) c.Remove("remove") c.Stop() assert.Length(j.times, 10, "job counter increased max ten times") } // Test crontab removing the job after an error. func TestCrontabError(t *testing.T) { assert := audit.NewTestingAssertion(t, true) // Create test crontab with job. c := timex.NewCrontab(10 * time.Millisecond) j := &cronjob{[]time.Time{}, false, true} c.Add("remove", j) time.Sleep(500 * time.Millisecond) c.Remove("remove") c.Stop() assert.Length(j.times, 5, "job counter increased max five times") } // TestRetrySuccess tests a successful retry. func TestRetrySuccess(t *testing.T) { assert := audit.NewTestingAssertion(t, true) count := 0 err := timex.Retry(func() (bool, error) { count++ return count == 5, nil }, timex.ShortAttempt()) assert.Nil(err) assert.Equal(count, 5) } // TestRetryFuncError tests an error inside the retried func. func TestRetryFuncError(t *testing.T) { assert := audit.NewTestingAssertion(t, true) err := timex.Retry(func() (bool, error) { return false, errors.New("ouch") }, timex.ShortAttempt()) assert.ErrorMatch(err, "ouch") } // TestRetryTooLong tests a retry timout. func TestRetryTooLong(t *testing.T) { assert := audit.NewTestingAssertion(t, true) rs := timex.RetryStrategy{ Count: 10, Break: 5 * time.Millisecond, BreakIncrement: 5 * time.Millisecond, Timeout: 50 * time.Millisecond, } err := timex.Retry(func() (bool, error) { return false, nil }, rs) assert.ErrorMatch(err, ".* retried longer than .*") } // TestRetryTooOften tests a retry count error. func TestRetryTooOften(t *testing.T) { assert := audit.NewTestingAssertion(t, true) rs := timex.RetryStrategy{ Count: 5, Break: 5 * time.Millisecond, BreakIncrement: 5 * time.Millisecond, Timeout: time.Second, } err := timex.Retry(func() (bool, error) { return false, nil }, rs) assert.ErrorMatch(err, ".* retried more than .* times") } //-------------------- // HELPERS //-------------------- type cronjob struct { times []time.Time flip bool fail bool } func (j *cronjob) ShallExecute(t time.Time) bool { j.flip = !j.flip return j.flip } func (j *cronjob) Execute() (bool, error) { j.times = append(j.times, time.Now()) if j.fail && len(j.times) == 5 { return false, errors.New("failed") } if len(j.times) == 10 { return false, nil } return true, nil } // EOF golib-4.24.2/version/000077500000000000000000000000001315505703200143655ustar00rootroot00000000000000golib-4.24.2/version/doc.go000066400000000000000000000012351315505703200154620ustar00rootroot00000000000000// Tideland Go Library - Version // // Copyright (C) 2014-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. // Package version of the Tideland Go Library helps other packages to // provide information about their current version and compare it // to others. It follows the idea of semantic versioning (see // http://semver.org/). // // Version instances can be created via New() with explicit passed // field values or via Parse() and a passed sting. Beside accessing // the individual fields two versions can be compared with Compare() // and Less(). package version // EOF golib-4.24.2/version/errors.go000066400000000000000000000011061315505703200162260ustar00rootroot00000000000000// Tideland Go Library - Version - Errors // // Copyright (C) 2016-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package version //-------------------- // IMPORTS //-------------------- import ( "github.com/tideland/golib/errors" ) //-------------------- // CONSTANTS //-------------------- // Error codes of the version package. const ( ErrIllegalVersionFormat = iota + 1 ) var errorMessages = errors.Messages{ ErrIllegalVersionFormat: "illegal version format: %s", } // EOF golib-4.24.2/version/version.go000066400000000000000000000165761315505703200164200ustar00rootroot00000000000000// Tideland Go Library - Version // // Copyright (C) 2014-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package version //-------------------- // IMPORTS //-------------------- import ( "fmt" "strconv" "strings" "github.com/tideland/golib/errors" ) //-------------------- // CONST //-------------------- // Precedence describes if a version is newer, equal, or older. type Precedence int // Level describes the level, on which a version differentiates // from an other. type Level string // Separator, precedences, and part identifiers. const ( Metadata = "+" Newer Precedence = 1 Equal = 0 Older = -1 Major Level = "major" Minor = "minor" Patch = "patch" PreRelease = "pre-release" All = "all" ) //-------------------- // VERSION //-------------------- // Version defines the interface of a version. type Version interface { fmt.Stringer // Major returns the major version. Major() int // Minor returns the minor version. Minor() int // Patch return the path version. Patch() int // PreRelease returns a possible pre-release of the version. PreRelease() string // Metadata returns a possible build metadata of the version. Metadata() string // Compare compares this version to the passed one. The result // is from the perspective of this one. Compare(cv Version) (Precedence, Level) // Less returns true if this version is less than the passed one. // This means this version is older. Less(cv Version) bool } // vsn implements the version interface. type vsn struct { major int minor int patch int preRelease []string metadata []string } // New returns a simple version instance. Parts of pre-release // and metadata are passed as optional strings separated by // version.Metadata ("+"). func New(major, minor, patch int, prmds ...string) Version { if major < 0 { major = 0 } if minor < 0 { minor = 0 } if patch < 0 { patch = 0 } v := &vsn{ major: major, minor: minor, patch: patch, } isPR := true for _, prmd := range prmds { if isPR { if prmd == Metadata { isPR = false continue } v.preRelease = append(v.preRelease, validID(prmd, true)) } else { v.metadata = append(v.metadata, validID(prmd, false)) } } return v } // Parse retrieves a version out of a string. func Parse(vsnstr string) (Version, error) { // Split version, pre-release, and metadata. npmstrs, err := splitVersionString(vsnstr) if err != nil { return nil, err } // Parse these parts. nums, err := parseNumberString(npmstrs[0]) if err != nil { return nil, err } prmds := []string{} if npmstrs[1] != "" { prmds = strings.Split(npmstrs[1], ".") } if npmstrs[2] != "" { prmds = append(prmds, Metadata) prmds = append(prmds, strings.Split(npmstrs[2], ".")...) } // Done. return New(nums[0], nums[1], nums[2], prmds...), nil } // Major implements the Version interface. func (v *vsn) Major() int { return v.major } // Minor implements the Version interface. func (v *vsn) Minor() int { return v.minor } // Patch implements the Version interface. func (v *vsn) Patch() int { return v.patch } // PreRelease implements the Version interface. func (v *vsn) PreRelease() string { return strings.Join(v.preRelease, ".") } // Metadata implements the Version interface. func (v *vsn) Metadata() string { return strings.Join(v.metadata, ".") } // Compare implements the Version interface. func (v *vsn) Compare(cv Version) (Precedence, Level) { // Standard version parts. switch { case v.major < cv.Major(): return Older, Major case v.major > cv.Major(): return Newer, Major case v.minor < cv.Minor(): return Older, Minor case v.minor > cv.Minor(): return Newer, Minor case v.patch < cv.Patch(): return Older, Patch case v.patch > cv.Patch(): return Newer, Patch } // Now the parts of the pre-release. cvpr := []string{} for _, cvprPart := range strings.Split(cv.PreRelease(), ".") { if cvprPart != "" { cvpr = append(cvpr, cvprPart) } } vlen := len(v.preRelease) cvlen := len(cvpr) count := vlen if cvlen < vlen { count = cvlen } for i := 0; i < count; i++ { vn, verr := strconv.Atoi(v.preRelease[i]) cvn, cverr := strconv.Atoi(cvpr[i]) if verr == nil && cverr == nil { // Numerical comparison. switch { case vn < cvn: return Older, PreRelease case vn > cvn: return Newer, PreRelease } continue } // Alphanumerical comparison. switch { case v.preRelease[i] < cvpr[i]: return Older, PreRelease case v.preRelease[i] > cvpr[i]: return Newer, PreRelease } } // Still no clean result, so the shorter // pre-relese is older. switch { case vlen < cvlen: return Newer, PreRelease case vlen > cvlen: return Older, PreRelease } // Last but not least: we are equal. return Equal, All } // Less implements the Version interface. func (v *vsn) Less(cv Version) bool { precedence, _ := v.Compare(cv) return precedence == Older } // String implements the fmt.Stringer interface. func (v *vsn) String() string { vs := fmt.Sprintf("%d.%d.%d", v.major, v.minor, v.patch) if len(v.preRelease) > 0 { vs += "-" + v.PreRelease() } if len(v.metadata) > 0 { vs += Metadata + v.Metadata() } return vs } //-------------------- // TOOLS //-------------------- // validID reduces the passed identifier to a valid one. If we care // for numeric identifiers leading zeros will be removed. func validID(id string, numeric bool) string { out := []rune{} letter := false digit := false hyphen := false for _, r := range id { switch { case r >= 'a' && r <= 'z': letter = true out = append(out, r) case r >= 'A' && r <= 'Z': letter = true out = append(out, r) case r >= '0' && r <= '9': digit = true out = append(out, r) case r == '-': hyphen = true out = append(out, r) } } if numeric && digit && !letter && !hyphen { // Digits only, and we care for it. // Remove leading zeros. for len(out) > 0 && out[0] == '0' { out = out[1:] } if len(out) == 0 { out = []rune{'0'} } } return string(out) } // splitVersionString separates the version string into numbers, // pre-release, and metadata strings. func splitVersionString(vsnstr string) ([]string, error) { npXm := strings.SplitN(vsnstr, Metadata, 2) switch len(npXm) { case 1: nXp := strings.SplitN(npXm[0], "-", 2) switch len(nXp) { case 1: return []string{nXp[0], "", ""}, nil case 2: return []string{nXp[0], nXp[1], ""}, nil } case 2: nXp := strings.SplitN(npXm[0], "-", 2) switch len(nXp) { case 1: return []string{nXp[0], "", npXm[1]}, nil case 2: return []string{nXp[0], nXp[1], npXm[1]}, nil } } return nil, errors.New(ErrIllegalVersionFormat, errorMessages, "wrong parts") } // parseNumberString retrieves major, minor, and patch number // of the passed string. func parseNumberString(nstr string) ([]int, error) { nstrs := strings.Split(nstr, ".") if len(nstrs) < 1 || len(nstrs) > 3 { return nil, errors.New(ErrIllegalVersionFormat, errorMessages, "wrong number parts") } vsn := []int{1, 0, 0} for i, nstr := range nstrs { num, err := strconv.Atoi(nstr) if err != nil { return nil, errors.New(ErrIllegalVersionFormat, errorMessages, err.Error()) } if num < 0 { return nil, errors.New(ErrIllegalVersionFormat, errorMessages, "negative version number") } vsn[i] = num } return vsn, nil } // EOF golib-4.24.2/version/version_test.go000066400000000000000000000245041315505703200174450ustar00rootroot00000000000000// Tideland Go Library - Version - Unit Tests // // Copyright (C) 2014-2017 Frank Mueller / Tideland / Oldenburg / Germany // // All rights reserved. Use of this source code is governed // by the new BSD license. package version_test //-------------------- // IMPORTS //-------------------- import ( "testing" "github.com/tideland/golib/audit" "github.com/tideland/golib/version" ) //-------------------- // TESTS //-------------------- // TestNew tests the creation of new versions and their // accessor methods. func TestNew(t *testing.T) { assert := audit.NewTestingAssertion(t, true) tests := []struct { id string vsn version.Version major int minor int patch int preRelease string metadata string }{ { id: "1.2.3", vsn: version.New(1, 2, 3), major: 1, minor: 2, patch: 3, preRelease: "", metadata: "", }, { id: "1.0.3", vsn: version.New(1, -2, 3), major: 1, minor: 0, patch: 3, preRelease: "", metadata: "", }, { id: "1.2.3-alpha.2014-08-03", vsn: version.New(1, 2, 3, "alpha", "2014-08-03"), major: 1, minor: 2, patch: 3, preRelease: "alpha.2014-08-03", metadata: "", }, { id: "1.2.3-alphabeta.7.11", vsn: version.New(1, 2, 3, "alpha beta", "007", "1+1"), major: 1, minor: 2, patch: 3, preRelease: "alphabeta.7.11", metadata: "", }, { id: "1.2.3+007.a", vsn: version.New(1, 2, 3, version.Metadata, "007", "a"), major: 1, minor: 2, patch: 3, preRelease: "", metadata: "007.a", }, { id: "1.2.3-alpha+007.a", vsn: version.New(1, 2, 3, "alpha", version.Metadata, "007", "a"), major: 1, minor: 2, patch: 3, preRelease: "alpha", metadata: "007.a", }, { id: "1.2.3-ALPHA+007.a", vsn: version.New(1, 2, 3, "ALPHA", version.Metadata, "007", "a"), major: 1, minor: 2, patch: 3, preRelease: "ALPHA", metadata: "007.a", }, } // Perform tests. for i, test := range tests { assert.Logf("new test #%d: %q", i, test.id) assert.Equal(test.vsn.Major(), test.major) assert.Equal(test.vsn.Minor(), test.minor) assert.Equal(test.vsn.Patch(), test.patch) assert.Equal(test.vsn.PreRelease(), test.preRelease) assert.Equal(test.vsn.Metadata(), test.metadata) assert.Equal(test.vsn.String(), test.id) } } // TestParse tests the creation of new versions and their // accessor methods by parsing strings. func TestParse(t *testing.T) { assert := audit.NewTestingAssertion(t, true) tests := []struct { id string vsn string err string major int minor int patch int preRelease string metadata string }{ { id: "1", vsn: "1.0.0", major: 1, minor: 0, patch: 0, preRelease: "", metadata: "", }, { id: "1.1", vsn: "1.1.0", major: 1, minor: 1, patch: 0, preRelease: "", metadata: "", }, { id: "1.2.3", major: 1, minor: 2, patch: 3, preRelease: "", metadata: "", }, { id: "1.0.3", major: 1, minor: 0, patch: 3, preRelease: "", metadata: "", }, { id: "1.2.3-alpha.2016-11-14", major: 1, minor: 2, patch: 3, preRelease: "alpha.2016-11-14", metadata: "", }, { id: "1.2.3-alphabeta.7.11", major: 1, minor: 2, patch: 3, preRelease: "alphabeta.7.11", metadata: "", }, { id: "1.2.3+007.a", major: 1, minor: 2, patch: 3, preRelease: "", metadata: "007.a", }, { id: "1.2.3-alpha+007.a", major: 1, minor: 2, patch: 3, preRelease: "alpha", metadata: "007.a", }, { id: "1.2.3-ALPHA+007.a", major: 1, minor: 2, patch: 3, preRelease: "ALPHA", metadata: "007.a", }, { id: "", err: `.* illegal version format: .*ParseInt.*`, }, { id: "a", err: `.* illegal version format: .*ParseInt.*`, }, { id: "1.a", err: `.* illegal version format: .*ParseInt.*`, }, { id: "1,1", err: `.* illegal version format: .*ParseInt.*`, }, { id: "-1", err: `.* illegal version format: .*ParseInt.*`, }, { id: "1.-1", err: `.* illegal version format: .*ParseInt.*`, }, { id: "+", err: `.* illegal version format: .*ParseInt.*`, }, } // Perform tests. for i, test := range tests { assert.Logf("parse test #%d: %q", i, test.id) vsn, err := version.Parse(test.id) if test.err != "" { assert.ErrorMatch(err, test.err) continue } assert.Nil(err) assert.Equal(vsn.Major(), test.major) assert.Equal(vsn.Minor(), test.minor) assert.Equal(vsn.Patch(), test.patch) assert.Equal(vsn.PreRelease(), test.preRelease) assert.Equal(vsn.Metadata(), test.metadata) if test.vsn != "" { assert.Equal(vsn.String(), test.vsn) } else { assert.Equal(vsn.String(), test.id) } } } // TestCompare tests the comparing of two versions. func TestCompare(t *testing.T) { assert := audit.NewTestingAssertion(t, true) tests := []struct { vsnA version.Version vsnB version.Version precedence version.Precedence level version.Level }{ { vsnA: version.New(1, 2, 3), vsnB: version.New(1, 2, 3), precedence: version.Equal, level: version.All, }, { vsnA: version.New(1, 2, 3), vsnB: version.New(1, 2, 4), precedence: version.Older, level: version.Patch, }, { vsnA: version.New(1, 2, 3), vsnB: version.New(1, 3, 3), precedence: version.Older, level: version.Minor, }, { vsnA: version.New(1, 2, 3), vsnB: version.New(2, 2, 3), precedence: version.Older, level: version.Major, }, { vsnA: version.New(3, 2, 1), vsnB: version.New(1, 2, 3), precedence: version.Newer, level: version.Major, }, { vsnA: version.New(1, 2, 3, "alpha"), vsnB: version.New(1, 2, 3), precedence: version.Older, level: version.PreRelease, }, { vsnA: version.New(1, 2, 3, "alpha", "1"), vsnB: version.New(1, 2, 3, "alpha"), precedence: version.Older, level: version.PreRelease, }, { vsnA: version.New(1, 2, 3, "alpha", "1"), vsnB: version.New(1, 2, 3, "alpha", "2"), precedence: version.Older, level: version.PreRelease, }, { vsnA: version.New(1, 2, 3, "alpha", "4711"), vsnB: version.New(1, 2, 3, "alpha", "471"), precedence: version.Newer, level: version.PreRelease, }, { vsnA: version.New(1, 2, 3, "alpha", "48"), vsnB: version.New(1, 2, 3, "alpha", "4711"), precedence: version.Older, level: version.PreRelease, }, { vsnA: version.New(1, 2, 3, version.Metadata, "alpha", "1"), vsnB: version.New(1, 2, 3, version.Metadata, "alpha", "2"), precedence: version.Equal, level: version.All, }, { vsnA: version.New(1, 2, 3, version.Metadata, "alpha", "2"), vsnB: version.New(1, 2, 3, version.Metadata, "alpha", "1"), precedence: version.Equal, level: version.All, }, { vsnA: version.New(1, 2, 3, "alpha", version.Metadata, "alpha", "2"), vsnB: version.New(1, 2, 3, "alpha", version.Metadata, "alpha", "1"), precedence: version.Equal, level: version.All, }, { vsnA: version.New(1, 2, 3, "alpha", "48", version.Metadata, "alpha", "2"), vsnB: version.New(1, 2, 3, "alpha", "4711", version.Metadata, "alpha", "1"), precedence: version.Older, level: version.PreRelease, }, { vsnA: version.New(1, 2, 3, "alpha", "2"), vsnB: version.New(1, 2, 3, "alpha", "1b"), precedence: version.Newer, level: version.PreRelease, }, } // Perform tests. for i, test := range tests { assert.Logf("compare test #%d: %q <> %q -> %d / %s", i, test.vsnA, test.vsnB, test.precedence, test.level) precedence, level := test.vsnA.Compare(test.vsnB) assert.Equal(precedence, test.precedence) assert.Equal(level, test.level) } } // TestLess tests if a version is less (older) than another. func TestLess(t *testing.T) { assert := audit.NewTestingAssertion(t, true) tests := []struct { vsnA version.Version vsnB version.Version less bool }{ { vsnA: version.New(1, 2, 3), vsnB: version.New(1, 2, 3), less: false, }, { vsnA: version.New(1, 2, 3), vsnB: version.New(1, 2, 4), less: true, }, { vsnA: version.New(1, 2, 3), vsnB: version.New(1, 3, 3), less: true, }, { vsnA: version.New(1, 2, 3), vsnB: version.New(2, 2, 3), less: true, }, { vsnA: version.New(3, 2, 1), vsnB: version.New(1, 2, 3), less: false, }, { vsnA: version.New(1, 2, 3, "alpha"), vsnB: version.New(1, 2, 3), less: true, }, { vsnA: version.New(1, 2, 3, "alpha", "1"), vsnB: version.New(1, 2, 3, "alpha"), less: true, }, { vsnA: version.New(1, 2, 3, "alpha", "1"), vsnB: version.New(1, 2, 3, "alpha", "2"), less: true, }, { vsnA: version.New(1, 2, 3, "alpha", "4711"), vsnB: version.New(1, 2, 3, "alpha", "471"), less: false, }, { vsnA: version.New(1, 2, 3, "alpha", "48"), vsnB: version.New(1, 2, 3, "alpha", "4711"), less: true, }, { vsnA: version.New(1, 2, 3, version.Metadata, "alpha", "1"), vsnB: version.New(1, 2, 3, version.Metadata, "alpha", "2"), less: false, }, { vsnA: version.New(1, 2, 3, version.Metadata, "alpha", "2"), vsnB: version.New(1, 2, 3, version.Metadata, "alpha", "1"), less: false, }, { vsnA: version.New(1, 2, 3, "alpha", version.Metadata, "alpha", "2"), vsnB: version.New(1, 2, 3, "alpha", version.Metadata, "alpha", "1"), less: false, }, { vsnA: version.New(1, 2, 3, "alpha", "48", version.Metadata, "alpha", "2"), vsnB: version.New(1, 2, 3, "alpha", "4711", version.Metadata, "alpha", "1"), less: true, }, { vsnA: version.New(1, 2, 3, "alpha", "2"), vsnB: version.New(1, 2, 3, "alpha", "1b"), less: false, }, } // Perform tests. for i, test := range tests { assert.Logf("less test #%d: %q <> %q -> %v", i, test.vsnA, test.vsnB, test.less) assert.Equal(test.vsnA.Less(test.vsnB), test.less) } } // EOF