vendor github.com/openark/golib
This commit is contained in:
parent
f29e63bc71
commit
f2c203382b
201
vendor/github.com/openark/golib/LICENSE
generated
vendored
Normal file
201
vendor/github.com/openark/golib/LICENSE
generated
vendored
Normal file
@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2014 Outbrain Inc
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
9
vendor/github.com/openark/golib/README.md
generated
vendored
Normal file
9
vendor/github.com/openark/golib/README.md
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
Common Go libraries
|
||||
|
||||
To import & use:
|
||||
```
|
||||
go get "github.com/openark/golib/math"
|
||||
go get "github.com/openark/golib/sqlutils"
|
||||
go get "github.com/openark/golib/tests"
|
||||
...
|
||||
```
|
3
vendor/github.com/openark/golib/go.mod
generated
vendored
Normal file
3
vendor/github.com/openark/golib/go.mod
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
module github.com/openark/golib
|
||||
|
||||
go 1.16
|
0
vendor/github.com/openark/golib/go.sum
generated
vendored
Normal file
0
vendor/github.com/openark/golib/go.sum
generated
vendored
Normal file
268
vendor/github.com/openark/golib/log/log.go
generated
vendored
Normal file
268
vendor/github.com/openark/golib/log/log.go
generated
vendored
Normal file
@ -0,0 +1,268 @@
|
||||
/*
|
||||
Copyright 2014 Outbrain Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package log
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/syslog"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
"time"
|
||||
)
|
||||
|
||||
// LogLevel indicates the severity of a log entry
|
||||
type LogLevel int
|
||||
|
||||
func (this LogLevel) String() string {
|
||||
switch this {
|
||||
case FATAL:
|
||||
return "FATAL"
|
||||
case CRITICAL:
|
||||
return "CRITICAL"
|
||||
case ERROR:
|
||||
return "ERROR"
|
||||
case WARNING:
|
||||
return "WARNING"
|
||||
case NOTICE:
|
||||
return "NOTICE"
|
||||
case INFO:
|
||||
return "INFO"
|
||||
case DEBUG:
|
||||
return "DEBUG"
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
func LogLevelFromString(logLevelName string) (LogLevel, error) {
|
||||
switch logLevelName {
|
||||
case "FATAL":
|
||||
return FATAL, nil
|
||||
case "CRITICAL":
|
||||
return CRITICAL, nil
|
||||
case "ERROR":
|
||||
return ERROR, nil
|
||||
case "WARNING":
|
||||
return WARNING, nil
|
||||
case "NOTICE":
|
||||
return NOTICE, nil
|
||||
case "INFO":
|
||||
return INFO, nil
|
||||
case "DEBUG":
|
||||
return DEBUG, nil
|
||||
}
|
||||
return 0, fmt.Errorf("Unknown LogLevel name: %+v", logLevelName)
|
||||
}
|
||||
|
||||
const (
|
||||
FATAL LogLevel = iota
|
||||
CRITICAL
|
||||
ERROR
|
||||
WARNING
|
||||
NOTICE
|
||||
INFO
|
||||
DEBUG
|
||||
)
|
||||
|
||||
const TimeFormat = "2006-01-02 15:04:05"
|
||||
|
||||
// globalLogLevel indicates the global level filter for all logs (only entries with level equals or higher
|
||||
// than this value will be logged)
|
||||
var globalLogLevel LogLevel = DEBUG
|
||||
var printStackTrace bool = false
|
||||
|
||||
// syslogWriter is optional, and defaults to nil (disabled)
|
||||
var syslogLevel LogLevel = ERROR
|
||||
var syslogWriter *syslog.Writer
|
||||
|
||||
// SetPrintStackTrace enables/disables dumping the stack upon error logging
|
||||
func SetPrintStackTrace(shouldPrintStackTrace bool) {
|
||||
printStackTrace = shouldPrintStackTrace
|
||||
}
|
||||
|
||||
// SetLevel sets the global log level. Only entries with level equals or higher than
|
||||
// this value will be logged
|
||||
func SetLevel(logLevel LogLevel) {
|
||||
globalLogLevel = logLevel
|
||||
}
|
||||
|
||||
// GetLevel returns current global log level
|
||||
func GetLevel() LogLevel {
|
||||
return globalLogLevel
|
||||
}
|
||||
|
||||
// EnableSyslogWriter enables, if possible, writes to syslog. These will execute _in addition_ to normal logging
|
||||
func EnableSyslogWriter(tag string) (err error) {
|
||||
syslogWriter, err = syslog.New(syslog.LOG_ERR, tag)
|
||||
if err != nil {
|
||||
syslogWriter = nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// SetSyslogLevel sets the minimal syslog level. Only entries with level equals or higher than
|
||||
// this value will be logged. However, this is also capped by the global log level. That is,
|
||||
// messages with lower level than global-log-level will be discarded at any case.
|
||||
func SetSyslogLevel(logLevel LogLevel) {
|
||||
syslogLevel = logLevel
|
||||
}
|
||||
|
||||
// logFormattedEntry nicely formats and emits a log entry
|
||||
func logFormattedEntry(logLevel LogLevel, message string, args ...interface{}) string {
|
||||
if logLevel > globalLogLevel {
|
||||
return ""
|
||||
}
|
||||
// if TZ env variable is set, update the timestamp timezone
|
||||
localizedTime := time.Now()
|
||||
tzLocation := os.Getenv("TZ")
|
||||
if tzLocation != "" {
|
||||
location, err := time.LoadLocation(tzLocation)
|
||||
if err == nil { // if invalid tz location was provided, just leave it as the default
|
||||
localizedTime = time.Now().In(location)
|
||||
}
|
||||
}
|
||||
|
||||
msgArgs := fmt.Sprintf(message, args...)
|
||||
entryString := fmt.Sprintf("%s %s %s", localizedTime.Format(TimeFormat), logLevel, msgArgs)
|
||||
fmt.Fprintln(os.Stderr, entryString)
|
||||
|
||||
if syslogWriter != nil {
|
||||
go func() error {
|
||||
if logLevel > syslogLevel {
|
||||
return nil
|
||||
}
|
||||
switch logLevel {
|
||||
case FATAL:
|
||||
return syslogWriter.Emerg(msgArgs)
|
||||
case CRITICAL:
|
||||
return syslogWriter.Crit(msgArgs)
|
||||
case ERROR:
|
||||
return syslogWriter.Err(msgArgs)
|
||||
case WARNING:
|
||||
return syslogWriter.Warning(msgArgs)
|
||||
case NOTICE:
|
||||
return syslogWriter.Notice(msgArgs)
|
||||
case INFO:
|
||||
return syslogWriter.Info(msgArgs)
|
||||
case DEBUG:
|
||||
return syslogWriter.Debug(msgArgs)
|
||||
}
|
||||
return nil
|
||||
}()
|
||||
}
|
||||
return entryString
|
||||
}
|
||||
|
||||
// logEntry emits a formatted log entry
|
||||
func logEntry(logLevel LogLevel, message string, args ...interface{}) string {
|
||||
entryString := message
|
||||
for _, s := range args {
|
||||
entryString += fmt.Sprintf(" %s", s)
|
||||
}
|
||||
return logFormattedEntry(logLevel, entryString)
|
||||
}
|
||||
|
||||
// logErrorEntry emits a log entry based on given error object
|
||||
func logErrorEntry(logLevel LogLevel, err error) error {
|
||||
if err == nil {
|
||||
// No error
|
||||
return nil
|
||||
}
|
||||
entryString := fmt.Sprintf("%+v", err)
|
||||
logEntry(logLevel, entryString)
|
||||
if printStackTrace {
|
||||
debug.PrintStack()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func Debug(message string, args ...interface{}) string {
|
||||
return logEntry(DEBUG, message, args...)
|
||||
}
|
||||
|
||||
func Debugf(message string, args ...interface{}) string {
|
||||
return logFormattedEntry(DEBUG, message, args...)
|
||||
}
|
||||
|
||||
func Info(message string, args ...interface{}) string {
|
||||
return logEntry(INFO, message, args...)
|
||||
}
|
||||
|
||||
func Infof(message string, args ...interface{}) string {
|
||||
return logFormattedEntry(INFO, message, args...)
|
||||
}
|
||||
|
||||
func Notice(message string, args ...interface{}) string {
|
||||
return logEntry(NOTICE, message, args...)
|
||||
}
|
||||
|
||||
func Noticef(message string, args ...interface{}) string {
|
||||
return logFormattedEntry(NOTICE, message, args...)
|
||||
}
|
||||
|
||||
func Warning(message string, args ...interface{}) error {
|
||||
return errors.New(logEntry(WARNING, message, args...))
|
||||
}
|
||||
|
||||
func Warningf(message string, args ...interface{}) error {
|
||||
return errors.New(logFormattedEntry(WARNING, message, args...))
|
||||
}
|
||||
|
||||
func Error(message string, args ...interface{}) error {
|
||||
return errors.New(logEntry(ERROR, message, args...))
|
||||
}
|
||||
|
||||
func Errorf(message string, args ...interface{}) error {
|
||||
return errors.New(logFormattedEntry(ERROR, message, args...))
|
||||
}
|
||||
|
||||
func Errore(err error) error {
|
||||
return logErrorEntry(ERROR, err)
|
||||
}
|
||||
|
||||
func Critical(message string, args ...interface{}) error {
|
||||
return errors.New(logEntry(CRITICAL, message, args...))
|
||||
}
|
||||
|
||||
func Criticalf(message string, args ...interface{}) error {
|
||||
return errors.New(logFormattedEntry(CRITICAL, message, args...))
|
||||
}
|
||||
|
||||
func Criticale(err error) error {
|
||||
return logErrorEntry(CRITICAL, err)
|
||||
}
|
||||
|
||||
// Fatal emits a FATAL level entry and exists the program
|
||||
func Fatal(message string, args ...interface{}) error {
|
||||
logEntry(FATAL, message, args...)
|
||||
os.Exit(1)
|
||||
return errors.New(logEntry(CRITICAL, message, args...))
|
||||
}
|
||||
|
||||
// Fatalf emits a FATAL level entry and exists the program
|
||||
func Fatalf(message string, args ...interface{}) error {
|
||||
logFormattedEntry(FATAL, message, args...)
|
||||
os.Exit(1)
|
||||
return errors.New(logFormattedEntry(CRITICAL, message, args...))
|
||||
}
|
||||
|
||||
// Fatale emits a FATAL level entry and exists the program
|
||||
func Fatale(err error) error {
|
||||
logErrorEntry(FATAL, err)
|
||||
os.Exit(1)
|
||||
return err
|
||||
}
|
119
vendor/github.com/openark/golib/math/math.go
generated
vendored
Normal file
119
vendor/github.com/openark/golib/math/math.go
generated
vendored
Normal file
@ -0,0 +1,119 @@
|
||||
/*
|
||||
Copyright 2014 Shlomi Noach.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package math
|
||||
|
||||
func MinInt(i1, i2 int) int {
|
||||
if i1 < i2 {
|
||||
return i1
|
||||
}
|
||||
return i2
|
||||
}
|
||||
|
||||
func MaxInt(i1, i2 int) int {
|
||||
if i1 > i2 {
|
||||
return i1
|
||||
}
|
||||
return i2
|
||||
}
|
||||
|
||||
func MinInt64(i1, i2 int64) int64 {
|
||||
if i1 < i2 {
|
||||
return i1
|
||||
}
|
||||
return i2
|
||||
}
|
||||
|
||||
func MaxInt64(i1, i2 int64) int64 {
|
||||
if i1 > i2 {
|
||||
return i1
|
||||
}
|
||||
return i2
|
||||
}
|
||||
|
||||
func MinUInt(i1, i2 uint) uint {
|
||||
if i1 < i2 {
|
||||
return i1
|
||||
}
|
||||
return i2
|
||||
}
|
||||
|
||||
func MaxUInt(i1, i2 uint) uint {
|
||||
if i1 > i2 {
|
||||
return i1
|
||||
}
|
||||
return i2
|
||||
}
|
||||
|
||||
func MinUInt64(i1, i2 uint64) uint64 {
|
||||
if i1 < i2 {
|
||||
return i1
|
||||
}
|
||||
return i2
|
||||
}
|
||||
|
||||
func MaxUInt64(i1, i2 uint64) uint64 {
|
||||
if i1 > i2 {
|
||||
return i1
|
||||
}
|
||||
return i2
|
||||
}
|
||||
|
||||
func MinString(i1, i2 string) string {
|
||||
if i1 < i2 {
|
||||
return i1
|
||||
}
|
||||
return i2
|
||||
}
|
||||
|
||||
func MaxString(i1, i2 string) string {
|
||||
if i1 > i2 {
|
||||
return i1
|
||||
}
|
||||
return i2
|
||||
}
|
||||
|
||||
// TernaryString acts like a "? :" C-style ternary operator for strings
|
||||
func TernaryString(condition bool, resTrue string, resFalse string) string {
|
||||
if condition {
|
||||
return resTrue
|
||||
}
|
||||
return resFalse
|
||||
}
|
||||
|
||||
// TernaryString acts like a "? :" C-style ternary operator for ints
|
||||
func TernaryInt(condition bool, resTrue int, resFalse int) int {
|
||||
if condition {
|
||||
return resTrue
|
||||
}
|
||||
return resFalse
|
||||
}
|
||||
|
||||
// AbsInt is an ABS function for int type
|
||||
func AbsInt(i int) int {
|
||||
if i >= 0 {
|
||||
return i
|
||||
}
|
||||
return -i
|
||||
}
|
||||
|
||||
// AbsInt64 is an ABS function for int64 type
|
||||
func AbsInt64(i int64) int64 {
|
||||
if i >= 0 {
|
||||
return i
|
||||
}
|
||||
return -i
|
||||
}
|
49
vendor/github.com/openark/golib/sqlutils/dialect.go
generated
vendored
Normal file
49
vendor/github.com/openark/golib/sqlutils/dialect.go
generated
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
/*
|
||||
Copyright 2017 GitHub Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package sqlutils
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type regexpMap struct {
|
||||
r *regexp.Regexp
|
||||
replacement string
|
||||
}
|
||||
|
||||
func (this *regexpMap) process(text string) (result string) {
|
||||
return this.r.ReplaceAllString(text, this.replacement)
|
||||
}
|
||||
|
||||
func rmap(regexpExpression string, replacement string) regexpMap {
|
||||
return regexpMap{
|
||||
r: regexp.MustCompile(regexpSpaces(regexpExpression)),
|
||||
replacement: replacement,
|
||||
}
|
||||
}
|
||||
|
||||
func regexpSpaces(statement string) string {
|
||||
return strings.Replace(statement, " ", `[\s]+`, -1)
|
||||
}
|
||||
|
||||
func applyConversions(statement string, conversions []regexpMap) string {
|
||||
for _, rmap := range conversions {
|
||||
statement = rmap.process(statement)
|
||||
}
|
||||
return statement
|
||||
}
|
130
vendor/github.com/openark/golib/sqlutils/sqlite_dialect.go
generated
vendored
Normal file
130
vendor/github.com/openark/golib/sqlutils/sqlite_dialect.go
generated
vendored
Normal file
@ -0,0 +1,130 @@
|
||||
/*
|
||||
Copyright 2017 GitHub Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// What's this about?
|
||||
// This is a brute-force regular-expression based conversion from MySQL syntax to sqlite3 syntax.
|
||||
// It is NOT meant to be a general purpose solution and is only expected & confirmed to run on
|
||||
// queries issued by orchestrator. There are known limitations to this design.
|
||||
// It's not even pretty.
|
||||
// In fact...
|
||||
// Well, it gets the job done at this time. Call it debt.
|
||||
|
||||
package sqlutils
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
)
|
||||
|
||||
var sqlite3CreateTableConversions = []regexpMap{
|
||||
rmap(`(?i) (character set|charset) [\S]+`, ``),
|
||||
rmap(`(?i)int unsigned`, `int`),
|
||||
rmap(`(?i)int[\s]*[(][\s]*([0-9]+)[\s]*[)] unsigned`, `int`),
|
||||
rmap(`(?i)engine[\s]*=[\s]*(innodb|myisam|ndb|memory|tokudb)`, ``),
|
||||
rmap(`(?i)DEFAULT CHARSET[\s]*=[\s]*[\S]+`, ``),
|
||||
rmap(`(?i)[\S]*int( not null|) auto_increment`, `integer`),
|
||||
rmap(`(?i)comment '[^']*'`, ``),
|
||||
rmap(`(?i)after [\S]+`, ``),
|
||||
rmap(`(?i)alter table ([\S]+) add (index|key) ([\S]+) (.+)`, `create index ${3}_${1} on $1 $4`),
|
||||
rmap(`(?i)alter table ([\S]+) add unique (index|key) ([\S]+) (.+)`, `create unique index ${3}_${1} on $1 $4`),
|
||||
rmap(`(?i)([\S]+) enum[\s]*([(].*?[)])`, `$1 text check($1 in $2)`),
|
||||
rmap(`(?i)([\s\S]+[/][*] sqlite3-skip [*][/][\s\S]+)`, ``),
|
||||
rmap(`(?i)timestamp default current_timestamp`, `timestamp default ('')`),
|
||||
rmap(`(?i)timestamp not null default current_timestamp`, `timestamp not null default ('')`),
|
||||
|
||||
rmap(`(?i)add column (.*int) not null[\s]*$`, `add column $1 not null default 0`),
|
||||
rmap(`(?i)add column (.* text) not null[\s]*$`, `add column $1 not null default ''`),
|
||||
rmap(`(?i)add column (.* varchar.*) not null[\s]*$`, `add column $1 not null default ''`),
|
||||
}
|
||||
|
||||
var sqlite3InsertConversions = []regexpMap{
|
||||
rmap(`(?i)insert ignore ([\s\S]+) on duplicate key update [\s\S]+`, `insert or ignore $1`),
|
||||
rmap(`(?i)insert ignore`, `insert or ignore`),
|
||||
rmap(`(?i)now[(][)]`, `datetime('now')`),
|
||||
rmap(`(?i)insert into ([\s\S]+) on duplicate key update [\s\S]+`, `replace into $1`),
|
||||
}
|
||||
|
||||
var sqlite3GeneralConversions = []regexpMap{
|
||||
rmap(`(?i)now[(][)][\s]*[-][\s]*interval [?] ([\w]+)`, `datetime('now', printf('-%d $1', ?))`),
|
||||
rmap(`(?i)now[(][)][\s]*[+][\s]*interval [?] ([\w]+)`, `datetime('now', printf('+%d $1', ?))`),
|
||||
rmap(`(?i)now[(][)][\s]*[-][\s]*interval ([0-9.]+) ([\w]+)`, `datetime('now', '-${1} $2')`),
|
||||
rmap(`(?i)now[(][)][\s]*[+][\s]*interval ([0-9.]+) ([\w]+)`, `datetime('now', '+${1} $2')`),
|
||||
|
||||
rmap(`(?i)[=<>\s]([\S]+[.][\S]+)[\s]*[-][\s]*interval [?] ([\w]+)`, ` datetime($1, printf('-%d $2', ?))`),
|
||||
rmap(`(?i)[=<>\s]([\S]+[.][\S]+)[\s]*[+][\s]*interval [?] ([\w]+)`, ` datetime($1, printf('+%d $2', ?))`),
|
||||
|
||||
rmap(`(?i)unix_timestamp[(][)]`, `strftime('%s', 'now')`),
|
||||
rmap(`(?i)unix_timestamp[(]([^)]+)[)]`, `strftime('%s', $1)`),
|
||||
rmap(`(?i)now[(][)]`, `datetime('now')`),
|
||||
rmap(`(?i)cast[(][\s]*([\S]+) as signed[\s]*[)]`, `cast($1 as integer)`),
|
||||
|
||||
rmap(`(?i)\bconcat[(][\s]*([^,)]+)[\s]*,[\s]*([^,)]+)[\s]*[)]`, `($1 || $2)`),
|
||||
rmap(`(?i)\bconcat[(][\s]*([^,)]+)[\s]*,[\s]*([^,)]+)[\s]*,[\s]*([^,)]+)[\s]*[)]`, `($1 || $2 || $3)`),
|
||||
|
||||
rmap(`(?i) rlike `, ` like `),
|
||||
|
||||
rmap(`(?i)create index([\s\S]+)[(][\s]*[0-9]+[\s]*[)]([\s\S]+)`, `create index ${1}${2}`),
|
||||
rmap(`(?i)drop index ([\S]+) on ([\S]+)`, `drop index if exists $1`),
|
||||
}
|
||||
|
||||
var (
|
||||
sqlite3IdentifyCreateTableStatement = regexp.MustCompile(regexpSpaces(`(?i)^[\s]*create table`))
|
||||
sqlite3IdentifyCreateIndexStatement = regexp.MustCompile(regexpSpaces(`(?i)^[\s]*create( unique|) index`))
|
||||
sqlite3IdentifyDropIndexStatement = regexp.MustCompile(regexpSpaces(`(?i)^[\s]*drop index`))
|
||||
sqlite3IdentifyAlterTableStatement = regexp.MustCompile(regexpSpaces(`(?i)^[\s]*alter table`))
|
||||
sqlite3IdentifyInsertStatement = regexp.MustCompile(regexpSpaces(`(?i)^[\s]*(insert|replace)`))
|
||||
)
|
||||
|
||||
func IsInsert(statement string) bool {
|
||||
return sqlite3IdentifyInsertStatement.MatchString(statement)
|
||||
}
|
||||
|
||||
func IsCreateTable(statement string) bool {
|
||||
return sqlite3IdentifyCreateTableStatement.MatchString(statement)
|
||||
}
|
||||
|
||||
func IsCreateIndex(statement string) bool {
|
||||
return sqlite3IdentifyCreateIndexStatement.MatchString(statement)
|
||||
}
|
||||
|
||||
func IsDropIndex(statement string) bool {
|
||||
return sqlite3IdentifyDropIndexStatement.MatchString(statement)
|
||||
}
|
||||
|
||||
func IsAlterTable(statement string) bool {
|
||||
return sqlite3IdentifyAlterTableStatement.MatchString(statement)
|
||||
}
|
||||
|
||||
func ToSqlite3CreateTable(statement string) string {
|
||||
return applyConversions(statement, sqlite3CreateTableConversions)
|
||||
}
|
||||
|
||||
func ToSqlite3Insert(statement string) string {
|
||||
return applyConversions(statement, sqlite3InsertConversions)
|
||||
}
|
||||
|
||||
func ToSqlite3Dialect(statement string) (translated string) {
|
||||
if IsCreateTable(statement) {
|
||||
return ToSqlite3CreateTable(statement)
|
||||
}
|
||||
if IsAlterTable(statement) {
|
||||
return ToSqlite3CreateTable(statement)
|
||||
}
|
||||
statement = applyConversions(statement, sqlite3GeneralConversions)
|
||||
if IsInsert(statement) {
|
||||
return ToSqlite3Insert(statement)
|
||||
}
|
||||
return statement
|
||||
}
|
242
vendor/github.com/openark/golib/sqlutils/sqlite_dialect_test.go
generated
vendored
Normal file
242
vendor/github.com/openark/golib/sqlutils/sqlite_dialect_test.go
generated
vendored
Normal file
@ -0,0 +1,242 @@
|
||||
/*
|
||||
Copyright 2017 GitHub Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package sqlutils
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
test "github.com/openark/golib/tests"
|
||||
)
|
||||
|
||||
var spacesRegexp = regexp.MustCompile(`[\s]+`)
|
||||
|
||||
func init() {
|
||||
}
|
||||
|
||||
func stripSpaces(statement string) string {
|
||||
statement = strings.TrimSpace(statement)
|
||||
statement = spacesRegexp.ReplaceAllString(statement, " ")
|
||||
return statement
|
||||
}
|
||||
|
||||
func TestIsCreateTable(t *testing.T) {
|
||||
test.S(t).ExpectTrue(IsCreateTable("create table t(id int)"))
|
||||
test.S(t).ExpectTrue(IsCreateTable(" create table t(id int)"))
|
||||
test.S(t).ExpectTrue(IsCreateTable("CREATE TABLE t(id int)"))
|
||||
test.S(t).ExpectTrue(IsCreateTable(`
|
||||
create table t(id int)
|
||||
`))
|
||||
test.S(t).ExpectFalse(IsCreateTable("where create table t(id int)"))
|
||||
test.S(t).ExpectFalse(IsCreateTable("insert"))
|
||||
}
|
||||
|
||||
func TestToSqlite3CreateTable(t *testing.T) {
|
||||
{
|
||||
statement := "create table t(id int)"
|
||||
result := ToSqlite3CreateTable(statement)
|
||||
test.S(t).ExpectEquals(result, statement)
|
||||
}
|
||||
{
|
||||
statement := "create table t(id int, v varchar(123) CHARACTER SET ascii NOT NULL default '')"
|
||||
result := ToSqlite3CreateTable(statement)
|
||||
test.S(t).ExpectEquals(result, "create table t(id int, v varchar(123) NOT NULL default '')")
|
||||
}
|
||||
{
|
||||
statement := "create table t(id int, v varchar ( 123 ) CHARACTER SET ascii NOT NULL default '')"
|
||||
result := ToSqlite3CreateTable(statement)
|
||||
test.S(t).ExpectEquals(result, "create table t(id int, v varchar ( 123 ) NOT NULL default '')")
|
||||
}
|
||||
{
|
||||
statement := "create table t(i smallint unsigned)"
|
||||
result := ToSqlite3CreateTable(statement)
|
||||
test.S(t).ExpectEquals(result, "create table t(i smallint)")
|
||||
}
|
||||
{
|
||||
statement := "create table t(i smallint(5) unsigned)"
|
||||
result := ToSqlite3CreateTable(statement)
|
||||
test.S(t).ExpectEquals(result, "create table t(i smallint)")
|
||||
}
|
||||
{
|
||||
statement := "create table t(i smallint ( 5 ) unsigned)"
|
||||
result := ToSqlite3CreateTable(statement)
|
||||
test.S(t).ExpectEquals(result, "create table t(i smallint)")
|
||||
}
|
||||
}
|
||||
|
||||
func TestToSqlite3AlterTable(t *testing.T) {
|
||||
{
|
||||
statement := `
|
||||
ALTER TABLE
|
||||
database_instance
|
||||
ADD COLUMN sql_delay INT UNSIGNED NOT NULL AFTER slave_lag_seconds
|
||||
`
|
||||
result := stripSpaces(ToSqlite3Dialect(statement))
|
||||
test.S(t).ExpectEquals(result, stripSpaces(`
|
||||
ALTER TABLE
|
||||
database_instance
|
||||
add column sql_delay int not null default 0
|
||||
`))
|
||||
}
|
||||
{
|
||||
statement := `
|
||||
ALTER TABLE
|
||||
database_instance
|
||||
ADD INDEX master_host_port_idx (master_host, master_port)
|
||||
`
|
||||
result := stripSpaces(ToSqlite3Dialect(statement))
|
||||
test.S(t).ExpectEquals(result, stripSpaces(`
|
||||
create index
|
||||
master_host_port_idx_database_instance
|
||||
on database_instance (master_host, master_port)
|
||||
`))
|
||||
}
|
||||
{
|
||||
statement := `
|
||||
ALTER TABLE
|
||||
topology_recovery
|
||||
ADD KEY last_detection_idx (last_detection_id)
|
||||
`
|
||||
result := stripSpaces(ToSqlite3Dialect(statement))
|
||||
test.S(t).ExpectEquals(result, stripSpaces(`
|
||||
create index
|
||||
last_detection_idx_topology_recovery
|
||||
on topology_recovery (last_detection_id)
|
||||
`))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestCreateIndex(t *testing.T) {
|
||||
{
|
||||
statement := `
|
||||
create index
|
||||
master_host_port_idx_database_instance
|
||||
on database_instance (master_host(128), master_port)
|
||||
`
|
||||
result := stripSpaces(ToSqlite3Dialect(statement))
|
||||
test.S(t).ExpectEquals(result, stripSpaces(`
|
||||
create index
|
||||
master_host_port_idx_database_instance
|
||||
on database_instance (master_host, master_port)
|
||||
`))
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsInsert(t *testing.T) {
|
||||
test.S(t).ExpectTrue(IsInsert("insert into t"))
|
||||
test.S(t).ExpectTrue(IsInsert("insert ignore into t"))
|
||||
test.S(t).ExpectTrue(IsInsert(`
|
||||
insert ignore into t
|
||||
`))
|
||||
test.S(t).ExpectFalse(IsInsert("where create table t(id int)"))
|
||||
test.S(t).ExpectFalse(IsInsert("create table t(id int)"))
|
||||
test.S(t).ExpectTrue(IsInsert(`
|
||||
insert into
|
||||
cluster_domain_name (cluster_name, domain_name, last_registered)
|
||||
values
|
||||
(?, ?, datetime('now'))
|
||||
on duplicate key update
|
||||
domain_name=values(domain_name),
|
||||
last_registered=values(last_registered)
|
||||
`))
|
||||
}
|
||||
|
||||
func TestToSqlite3Insert(t *testing.T) {
|
||||
{
|
||||
statement := `
|
||||
insert into
|
||||
cluster_domain_name (cluster_name, domain_name, last_registered)
|
||||
values
|
||||
(?, ?, datetime('now'))
|
||||
on duplicate key update
|
||||
domain_name=values(domain_name),
|
||||
last_registered=values(last_registered)
|
||||
`
|
||||
result := stripSpaces(ToSqlite3Dialect(statement))
|
||||
test.S(t).ExpectEquals(result, stripSpaces(`
|
||||
replace into
|
||||
cluster_domain_name (cluster_name, domain_name, last_registered)
|
||||
values
|
||||
(?, ?, datetime('now'))
|
||||
`))
|
||||
}
|
||||
}
|
||||
|
||||
func TestToSqlite3GeneralConversions(t *testing.T) {
|
||||
{
|
||||
statement := "select now()"
|
||||
result := ToSqlite3Dialect(statement)
|
||||
test.S(t).ExpectEquals(result, "select datetime('now')")
|
||||
}
|
||||
{
|
||||
statement := "select now() - interval ? second"
|
||||
result := ToSqlite3Dialect(statement)
|
||||
test.S(t).ExpectEquals(result, "select datetime('now', printf('-%d second', ?))")
|
||||
}
|
||||
{
|
||||
statement := "select now() + interval ? minute"
|
||||
result := ToSqlite3Dialect(statement)
|
||||
test.S(t).ExpectEquals(result, "select datetime('now', printf('+%d minute', ?))")
|
||||
}
|
||||
{
|
||||
statement := "select now() + interval 5 minute"
|
||||
result := ToSqlite3Dialect(statement)
|
||||
test.S(t).ExpectEquals(result, "select datetime('now', '+5 minute')")
|
||||
}
|
||||
{
|
||||
statement := "select some_table.some_column + interval ? minute"
|
||||
result := ToSqlite3Dialect(statement)
|
||||
test.S(t).ExpectEquals(result, "select datetime(some_table.some_column, printf('+%d minute', ?))")
|
||||
}
|
||||
{
|
||||
statement := "AND master_instance.last_attempted_check <= master_instance.last_seen + interval ? minute"
|
||||
result := ToSqlite3Dialect(statement)
|
||||
test.S(t).ExpectEquals(result, "AND master_instance.last_attempted_check <= datetime(master_instance.last_seen, printf('+%d minute', ?))")
|
||||
}
|
||||
{
|
||||
statement := "select concat(master_instance.port, '') as port"
|
||||
result := ToSqlite3Dialect(statement)
|
||||
test.S(t).ExpectEquals(result, "select (master_instance.port || '') as port")
|
||||
}
|
||||
{
|
||||
statement := "select concat( 'abc' , 'def') as s"
|
||||
result := ToSqlite3Dialect(statement)
|
||||
test.S(t).ExpectEquals(result, "select ('abc' || 'def') as s")
|
||||
}
|
||||
{
|
||||
statement := "select concat( 'abc' , 'def', last.col) as s"
|
||||
result := ToSqlite3Dialect(statement)
|
||||
test.S(t).ExpectEquals(result, "select ('abc' || 'def' || last.col) as s")
|
||||
}
|
||||
{
|
||||
statement := "select concat(myself.only) as s"
|
||||
result := ToSqlite3Dialect(statement)
|
||||
test.S(t).ExpectEquals(result, "select concat(myself.only) as s")
|
||||
}
|
||||
{
|
||||
statement := "select concat(1, '2', 3, '4') as s"
|
||||
result := ToSqlite3Dialect(statement)
|
||||
test.S(t).ExpectEquals(result, "select concat(1, '2', 3, '4') as s")
|
||||
}
|
||||
{
|
||||
statement := "select group_concat( 'abc' , 'def') as s"
|
||||
result := ToSqlite3Dialect(statement)
|
||||
test.S(t).ExpectEquals(result, "select group_concat( 'abc' , 'def') as s")
|
||||
}
|
||||
}
|
427
vendor/github.com/openark/golib/sqlutils/sqlutils.go
generated
vendored
Normal file
427
vendor/github.com/openark/golib/sqlutils/sqlutils.go
generated
vendored
Normal file
@ -0,0 +1,427 @@
|
||||
/*
|
||||
Copyright 2014 Outbrain Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package sqlutils
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/openark/golib/log"
|
||||
)
|
||||
|
||||
const DateTimeFormat = "2006-01-02 15:04:05.999999"
|
||||
|
||||
// RowMap represents one row in a result set. Its objective is to allow
|
||||
// for easy, typed getters by column name.
|
||||
type RowMap map[string]CellData
|
||||
|
||||
// Cell data is the result of a single (atomic) column in a single row
|
||||
type CellData sql.NullString
|
||||
|
||||
func (this *CellData) MarshalJSON() ([]byte, error) {
|
||||
if this.Valid {
|
||||
return json.Marshal(this.String)
|
||||
} else {
|
||||
return json.Marshal(nil)
|
||||
}
|
||||
}
|
||||
|
||||
// UnmarshalJSON reds this object from JSON
|
||||
func (this *CellData) UnmarshalJSON(b []byte) error {
|
||||
var s string
|
||||
if err := json.Unmarshal(b, &s); err != nil {
|
||||
return err
|
||||
}
|
||||
(*this).String = s
|
||||
(*this).Valid = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *CellData) NullString() *sql.NullString {
|
||||
return (*sql.NullString)(this)
|
||||
}
|
||||
|
||||
// RowData is the result of a single row, in positioned array format
|
||||
type RowData []CellData
|
||||
|
||||
// MarshalJSON will marshal this map as JSON
|
||||
func (this *RowData) MarshalJSON() ([]byte, error) {
|
||||
cells := make([](*CellData), len(*this), len(*this))
|
||||
for i, val := range *this {
|
||||
d := CellData(val)
|
||||
cells[i] = &d
|
||||
}
|
||||
return json.Marshal(cells)
|
||||
}
|
||||
|
||||
func (this *RowData) Args() []interface{} {
|
||||
result := make([]interface{}, len(*this))
|
||||
for i := range *this {
|
||||
result[i] = (*(*this)[i].NullString())
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// ResultData is an ordered row set of RowData
|
||||
type ResultData []RowData
|
||||
type NamedResultData struct {
|
||||
Columns []string
|
||||
Data ResultData
|
||||
}
|
||||
|
||||
var EmptyResultData = ResultData{}
|
||||
|
||||
func (this *RowMap) GetString(key string) string {
|
||||
return (*this)[key].String
|
||||
}
|
||||
|
||||
// GetStringD returns a string from the map, or a default value if the key does not exist
|
||||
func (this *RowMap) GetStringD(key string, def string) string {
|
||||
if cell, ok := (*this)[key]; ok {
|
||||
return cell.String
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
func (this *RowMap) GetInt64(key string) int64 {
|
||||
res, _ := strconv.ParseInt(this.GetString(key), 10, 0)
|
||||
return res
|
||||
}
|
||||
|
||||
func (this *RowMap) GetNullInt64(key string) sql.NullInt64 {
|
||||
i, err := strconv.ParseInt(this.GetString(key), 10, 0)
|
||||
if err == nil {
|
||||
return sql.NullInt64{Int64: i, Valid: true}
|
||||
} else {
|
||||
return sql.NullInt64{Valid: false}
|
||||
}
|
||||
}
|
||||
|
||||
func (this *RowMap) GetInt(key string) int {
|
||||
res, _ := strconv.Atoi(this.GetString(key))
|
||||
return res
|
||||
}
|
||||
|
||||
func (this *RowMap) GetIntD(key string, def int) int {
|
||||
res, err := strconv.Atoi(this.GetString(key))
|
||||
if err != nil {
|
||||
return def
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (this *RowMap) GetUint(key string) uint {
|
||||
res, _ := strconv.ParseUint(this.GetString(key), 10, 0)
|
||||
return uint(res)
|
||||
}
|
||||
|
||||
func (this *RowMap) GetUintD(key string, def uint) uint {
|
||||
res, err := strconv.Atoi(this.GetString(key))
|
||||
if err != nil {
|
||||
return def
|
||||
}
|
||||
return uint(res)
|
||||
}
|
||||
|
||||
func (this *RowMap) GetUint64(key string) uint64 {
|
||||
res, _ := strconv.ParseUint(this.GetString(key), 10, 0)
|
||||
return res
|
||||
}
|
||||
|
||||
func (this *RowMap) GetUint64D(key string, def uint64) uint64 {
|
||||
res, err := strconv.ParseUint(this.GetString(key), 10, 0)
|
||||
if err != nil {
|
||||
return def
|
||||
}
|
||||
return uint64(res)
|
||||
}
|
||||
|
||||
func (this *RowMap) GetBool(key string) bool {
|
||||
return this.GetInt(key) != 0
|
||||
}
|
||||
|
||||
func (this *RowMap) GetTime(key string) time.Time {
|
||||
if t, err := time.Parse(DateTimeFormat, this.GetString(key)); err == nil {
|
||||
return t
|
||||
}
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
// knownDBs is a DB cache by uri
|
||||
var knownDBs map[string]*sql.DB = make(map[string]*sql.DB)
|
||||
var knownDBsMutex = &sync.Mutex{}
|
||||
|
||||
// GetDB returns a DB instance based on uri.
|
||||
// bool result indicates whether the DB was returned from cache; err
|
||||
func GetGenericDB(driverName, dataSourceName string) (*sql.DB, bool, error) {
|
||||
knownDBsMutex.Lock()
|
||||
defer func() {
|
||||
knownDBsMutex.Unlock()
|
||||
}()
|
||||
|
||||
var exists bool
|
||||
if _, exists = knownDBs[dataSourceName]; !exists {
|
||||
if db, err := sql.Open(driverName, dataSourceName); err == nil {
|
||||
knownDBs[dataSourceName] = db
|
||||
} else {
|
||||
return db, exists, err
|
||||
}
|
||||
}
|
||||
return knownDBs[dataSourceName], exists, nil
|
||||
}
|
||||
|
||||
// GetDB returns a MySQL DB instance based on uri.
|
||||
// bool result indicates whether the DB was returned from cache; err
|
||||
func GetDB(mysql_uri string) (*sql.DB, bool, error) {
|
||||
return GetGenericDB("mysql", mysql_uri)
|
||||
}
|
||||
|
||||
// GetDB returns a SQLite DB instance based on DB file name.
|
||||
// bool result indicates whether the DB was returned from cache; err
|
||||
func GetSQLiteDB(dbFile string) (*sql.DB, bool, error) {
|
||||
return GetGenericDB("sqlite3", dbFile)
|
||||
}
|
||||
|
||||
// RowToArray is a convenience function, typically not called directly, which maps a
|
||||
// single read database row into a NullString
|
||||
func RowToArray(rows *sql.Rows, columns []string) []CellData {
|
||||
buff := make([]interface{}, len(columns))
|
||||
data := make([]CellData, len(columns))
|
||||
for i, _ := range buff {
|
||||
buff[i] = data[i].NullString()
|
||||
}
|
||||
rows.Scan(buff...)
|
||||
return data
|
||||
}
|
||||
|
||||
// ScanRowsToArrays is a convenience function, typically not called directly, which maps rows
|
||||
// already read from the databse into arrays of NullString
|
||||
func ScanRowsToArrays(rows *sql.Rows, on_row func([]CellData) error) error {
|
||||
columns, _ := rows.Columns()
|
||||
for rows.Next() {
|
||||
arr := RowToArray(rows, columns)
|
||||
err := on_row(arr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func rowToMap(row []CellData, columns []string) map[string]CellData {
|
||||
m := make(map[string]CellData)
|
||||
for k, data_col := range row {
|
||||
m[columns[k]] = data_col
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// ScanRowsToMaps is a convenience function, typically not called directly, which maps rows
|
||||
// already read from the databse into RowMap entries.
|
||||
func ScanRowsToMaps(rows *sql.Rows, on_row func(RowMap) error) error {
|
||||
columns, _ := rows.Columns()
|
||||
err := ScanRowsToArrays(rows, func(arr []CellData) error {
|
||||
m := rowToMap(arr, columns)
|
||||
err := on_row(m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// QueryRowsMap is a convenience function allowing querying a result set while poviding a callback
|
||||
// function activated per read row.
|
||||
func QueryRowsMap(db *sql.DB, query string, on_row func(RowMap) error, args ...interface{}) (err error) {
|
||||
defer func() {
|
||||
if derr := recover(); derr != nil {
|
||||
err = fmt.Errorf("QueryRowsMap unexpected error: %+v", derr)
|
||||
}
|
||||
}()
|
||||
|
||||
var rows *sql.Rows
|
||||
rows, err = db.Query(query, args...)
|
||||
if rows != nil {
|
||||
defer rows.Close()
|
||||
}
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return log.Errore(err)
|
||||
}
|
||||
err = ScanRowsToMaps(rows, on_row)
|
||||
return
|
||||
}
|
||||
|
||||
// queryResultData returns a raw array of rows for a given query, optionally reading and returning column names
|
||||
func queryResultData(db *sql.DB, query string, retrieveColumns bool, args ...interface{}) (resultData ResultData, columns []string, err error) {
|
||||
defer func() {
|
||||
if derr := recover(); derr != nil {
|
||||
err = errors.New(fmt.Sprintf("QueryRowsMap unexpected error: %+v", derr))
|
||||
}
|
||||
}()
|
||||
|
||||
var rows *sql.Rows
|
||||
rows, err = db.Query(query, args...)
|
||||
defer rows.Close()
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return EmptyResultData, columns, err
|
||||
}
|
||||
if retrieveColumns {
|
||||
// Don't pay if you don't want to
|
||||
columns, _ = rows.Columns()
|
||||
}
|
||||
resultData = ResultData{}
|
||||
err = ScanRowsToArrays(rows, func(rowData []CellData) error {
|
||||
resultData = append(resultData, rowData)
|
||||
return nil
|
||||
})
|
||||
return resultData, columns, err
|
||||
}
|
||||
|
||||
// QueryResultData returns a raw array of rows
|
||||
func QueryResultData(db *sql.DB, query string, args ...interface{}) (ResultData, error) {
|
||||
resultData, _, err := queryResultData(db, query, false, args...)
|
||||
return resultData, err
|
||||
}
|
||||
|
||||
// QueryResultDataNamed returns a raw array of rows, with column names
|
||||
func QueryNamedResultData(db *sql.DB, query string, args ...interface{}) (NamedResultData, error) {
|
||||
resultData, columns, err := queryResultData(db, query, true, args...)
|
||||
return NamedResultData{Columns: columns, Data: resultData}, err
|
||||
}
|
||||
|
||||
// QueryRowsMapBuffered reads data from the database into a buffer, and only then applies the given function per row.
|
||||
// This allows the application to take its time with processing the data, albeit consuming as much memory as required by
|
||||
// the result set.
|
||||
func QueryRowsMapBuffered(db *sql.DB, query string, on_row func(RowMap) error, args ...interface{}) error {
|
||||
resultData, columns, err := queryResultData(db, query, true, args...)
|
||||
if err != nil {
|
||||
// Already logged
|
||||
return err
|
||||
}
|
||||
for _, row := range resultData {
|
||||
err = on_row(rowToMap(row, columns))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExecNoPrepare executes given query using given args on given DB, without using prepared statements.
|
||||
func ExecNoPrepare(db *sql.DB, query string, args ...interface{}) (res sql.Result, err error) {
|
||||
defer func() {
|
||||
if derr := recover(); derr != nil {
|
||||
err = errors.New(fmt.Sprintf("ExecNoPrepare unexpected error: %+v", derr))
|
||||
}
|
||||
}()
|
||||
|
||||
res, err = db.Exec(query, args...)
|
||||
if err != nil {
|
||||
log.Errore(err)
|
||||
}
|
||||
return res, err
|
||||
}
|
||||
|
||||
// ExecQuery executes given query using given args on given DB. It will safele prepare, execute and close
|
||||
// the statement.
|
||||
func execInternal(silent bool, db *sql.DB, query string, args ...interface{}) (res sql.Result, err error) {
|
||||
defer func() {
|
||||
if derr := recover(); derr != nil {
|
||||
err = errors.New(fmt.Sprintf("execInternal unexpected error: %+v", derr))
|
||||
}
|
||||
}()
|
||||
var stmt *sql.Stmt
|
||||
stmt, err = db.Prepare(query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer stmt.Close()
|
||||
res, err = stmt.Exec(args...)
|
||||
if err != nil && !silent {
|
||||
log.Errore(err)
|
||||
}
|
||||
return res, err
|
||||
}
|
||||
|
||||
// Exec executes given query using given args on given DB. It will safele prepare, execute and close
|
||||
// the statement.
|
||||
func Exec(db *sql.DB, query string, args ...interface{}) (sql.Result, error) {
|
||||
return execInternal(false, db, query, args...)
|
||||
}
|
||||
|
||||
// ExecSilently acts like Exec but does not report any error
|
||||
func ExecSilently(db *sql.DB, query string, args ...interface{}) (sql.Result, error) {
|
||||
return execInternal(true, db, query, args...)
|
||||
}
|
||||
|
||||
func InClauseStringValues(terms []string) string {
|
||||
quoted := []string{}
|
||||
for _, s := range terms {
|
||||
quoted = append(quoted, fmt.Sprintf("'%s'", strings.Replace(s, ",", "''", -1)))
|
||||
}
|
||||
return strings.Join(quoted, ", ")
|
||||
}
|
||||
|
||||
// Convert variable length arguments into arguments array
|
||||
func Args(args ...interface{}) []interface{} {
|
||||
return args
|
||||
}
|
||||
|
||||
func NilIfZero(i int64) interface{} {
|
||||
if i == 0 {
|
||||
return nil
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func ScanTable(db *sql.DB, tableName string) (NamedResultData, error) {
|
||||
query := fmt.Sprintf("select * from %s", tableName)
|
||||
return QueryNamedResultData(db, query)
|
||||
}
|
||||
|
||||
func WriteTable(db *sql.DB, tableName string, data NamedResultData) (err error) {
|
||||
if len(data.Data) == 0 {
|
||||
return nil
|
||||
}
|
||||
if len(data.Columns) == 0 {
|
||||
return nil
|
||||
}
|
||||
placeholders := make([]string, len(data.Columns))
|
||||
for i := range placeholders {
|
||||
placeholders[i] = "?"
|
||||
}
|
||||
query := fmt.Sprintf(
|
||||
`replace into %s (%s) values (%s)`,
|
||||
tableName,
|
||||
strings.Join(data.Columns, ","),
|
||||
strings.Join(placeholders, ","),
|
||||
)
|
||||
for _, rowData := range data.Data {
|
||||
if _, execErr := db.Exec(query, rowData.Args()...); execErr != nil {
|
||||
err = execErr
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
76
vendor/github.com/openark/golib/tests/spec.go
generated
vendored
Normal file
76
vendor/github.com/openark/golib/tests/spec.go
generated
vendored
Normal file
@ -0,0 +1,76 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Spec is an access point to test Expections
|
||||
type Spec struct {
|
||||
t *testing.T
|
||||
}
|
||||
|
||||
// S generates a spec. You will want to use it once in a test file, once in a test or once per each check
|
||||
func S(t *testing.T) *Spec {
|
||||
return &Spec{t: t}
|
||||
}
|
||||
|
||||
// ExpectNil expects given value to be nil, or errors
|
||||
func (spec *Spec) ExpectNil(actual interface{}) {
|
||||
if actual == nil {
|
||||
return
|
||||
}
|
||||
spec.t.Errorf("Expected %+v to be nil", actual)
|
||||
}
|
||||
|
||||
// ExpectNotNil expects given value to be not nil, or errors
|
||||
func (spec *Spec) ExpectNotNil(actual interface{}) {
|
||||
if actual != nil {
|
||||
return
|
||||
}
|
||||
spec.t.Errorf("Expected %+v to be not nil", actual)
|
||||
}
|
||||
|
||||
// ExpectEquals expects given values to be equal (comparison via `==`), or errors
|
||||
func (spec *Spec) ExpectEquals(actual, value interface{}) {
|
||||
if actual == value {
|
||||
return
|
||||
}
|
||||
spec.t.Errorf("Expected:\n[[[%+v]]]\n- got:\n[[[%+v]]]", value, actual)
|
||||
}
|
||||
|
||||
// ExpectNotEquals expects given values to be nonequal (comparison via `==`), or errors
|
||||
func (spec *Spec) ExpectNotEquals(actual, value interface{}) {
|
||||
if !(actual == value) {
|
||||
return
|
||||
}
|
||||
spec.t.Errorf("Expected not %+v", value)
|
||||
}
|
||||
|
||||
// ExpectEqualsAny expects given actual to equal (comparison via `==`) at least one of given values, or errors
|
||||
func (spec *Spec) ExpectEqualsAny(actual interface{}, values ...interface{}) {
|
||||
for _, value := range values {
|
||||
if actual == value {
|
||||
return
|
||||
}
|
||||
}
|
||||
spec.t.Errorf("Expected %+v to equal any of given values", actual)
|
||||
}
|
||||
|
||||
// ExpectNotEqualsAny expects given actual to be nonequal (comparison via `==`)tp any of given values, or errors
|
||||
func (spec *Spec) ExpectNotEqualsAny(actual interface{}, values ...interface{}) {
|
||||
for _, value := range values {
|
||||
if actual == value {
|
||||
spec.t.Errorf("Expected not %+v", value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ExpectFalse expects given values to be false, or errors
|
||||
func (spec *Spec) ExpectFalse(actual interface{}) {
|
||||
spec.ExpectEquals(actual, false)
|
||||
}
|
||||
|
||||
// ExpectTrue expects given values to be true, or errors
|
||||
func (spec *Spec) ExpectTrue(actual interface{}) {
|
||||
spec.ExpectEquals(actual, true)
|
||||
}
|
103
vendor/github.com/openark/golib/util/text.go
generated
vendored
Normal file
103
vendor/github.com/openark/golib/util/text.go
generated
vendored
Normal file
@ -0,0 +1,103 @@
|
||||
/*
|
||||
Copyright 2015 Shlomi Noach.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
TabulateLeft = 0
|
||||
TabulateRight = 1
|
||||
)
|
||||
|
||||
// ParseSimpleTime parses input in the format 7s, 55m, 3h, 31d, 4w (second, minute, hour, day, week)
|
||||
// The time.ParseDuration() function should have done this, but it does not support "d" and "w" extensions.
|
||||
func SimpleTimeToSeconds(simpleTime string) (int, error) {
|
||||
if matched, _ := regexp.MatchString("^[0-9]+s$", simpleTime); matched {
|
||||
i, _ := strconv.Atoi(simpleTime[0 : len(simpleTime)-1])
|
||||
return i, nil
|
||||
}
|
||||
if matched, _ := regexp.MatchString("^[0-9]+m$", simpleTime); matched {
|
||||
i, _ := strconv.Atoi(simpleTime[0 : len(simpleTime)-1])
|
||||
return i * 60, nil
|
||||
}
|
||||
if matched, _ := regexp.MatchString("^[0-9]+h$", simpleTime); matched {
|
||||
i, _ := strconv.Atoi(simpleTime[0 : len(simpleTime)-1])
|
||||
return i * 60 * 60, nil
|
||||
}
|
||||
if matched, _ := regexp.MatchString("^[0-9]+d$", simpleTime); matched {
|
||||
i, _ := strconv.Atoi(simpleTime[0 : len(simpleTime)-1])
|
||||
return i * 60 * 60 * 24, nil
|
||||
}
|
||||
if matched, _ := regexp.MatchString("^[0-9]+w$", simpleTime); matched {
|
||||
i, _ := strconv.Atoi(simpleTime[0 : len(simpleTime)-1])
|
||||
return i * 60 * 60 * 24 * 7, nil
|
||||
}
|
||||
return 0, errors.New(fmt.Sprintf("Cannot parse simple time: %s", simpleTime))
|
||||
}
|
||||
|
||||
func Tabulate(lines []string, separator string, outputSeparator string, directionFlags ...int) (result []string) {
|
||||
tokens := make([][]string, 0)
|
||||
widths := make([][]int, 0)
|
||||
countColumns := 0
|
||||
for _, line := range lines {
|
||||
lineTokens := strings.Split(line, separator)
|
||||
lineWidths := make([]int, len(lineTokens))
|
||||
for i := range lineTokens {
|
||||
lineWidths[i] = len(lineTokens[i])
|
||||
}
|
||||
tokens = append(tokens, lineTokens)
|
||||
widths = append(widths, lineWidths)
|
||||
if len(lineTokens) > countColumns {
|
||||
countColumns = len(lineTokens)
|
||||
}
|
||||
}
|
||||
columnWidths := make([]int, countColumns)
|
||||
for _, lineTokens := range tokens {
|
||||
for col, token := range lineTokens {
|
||||
if len(token) > columnWidths[col] {
|
||||
columnWidths[col] = len(token)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, lineTokens := range tokens {
|
||||
resultRow := ""
|
||||
for col := 0; col < countColumns; col++ {
|
||||
token := ""
|
||||
if col < len(lineTokens) {
|
||||
token = lineTokens[col]
|
||||
}
|
||||
format := fmt.Sprintf("%%-%ds", columnWidths[col]) // format left
|
||||
if col < len(directionFlags) && directionFlags[col] == TabulateRight {
|
||||
format = fmt.Sprintf("%%%ds", columnWidths[col])
|
||||
}
|
||||
formattedToken := fmt.Sprintf(format, token)
|
||||
if col == 0 {
|
||||
resultRow = formattedToken
|
||||
} else {
|
||||
resultRow = fmt.Sprintf("%s%s%s", resultRow, outputSeparator, formattedToken)
|
||||
}
|
||||
}
|
||||
result = append(result, resultRow)
|
||||
}
|
||||
return result
|
||||
}
|
88
vendor/github.com/openark/golib/util/text_test.go
generated
vendored
Normal file
88
vendor/github.com/openark/golib/util/text_test.go
generated
vendored
Normal file
@ -0,0 +1,88 @@
|
||||
/*
|
||||
Copyright 2014 Outbrain Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
test "github.com/openark/golib/tests"
|
||||
)
|
||||
|
||||
func init() {
|
||||
}
|
||||
|
||||
func TestTabulate(t *testing.T) {
|
||||
{
|
||||
text := strings.TrimSpace(`
|
||||
a,b,c
|
||||
d,e,f
|
||||
g,h,i
|
||||
`)
|
||||
|
||||
tabulated := Tabulate(strings.Split(text, "\n"), ",", ",")
|
||||
expected := strings.Split(text, "\n")
|
||||
test.S(t).ExpectTrue(reflect.DeepEqual(tabulated, expected))
|
||||
}
|
||||
{
|
||||
text := strings.TrimSpace(`
|
||||
a,b,c
|
||||
d,e,f
|
||||
g,h,i
|
||||
`)
|
||||
|
||||
tabulated := Tabulate(strings.Split(text, "\n"), ",", "|")
|
||||
expected := []string{
|
||||
"a|b|c",
|
||||
"d|e|f",
|
||||
"g|h|i",
|
||||
}
|
||||
test.S(t).ExpectTrue(reflect.DeepEqual(tabulated, expected))
|
||||
}
|
||||
{
|
||||
text := strings.TrimSpace(`
|
||||
a,20,c
|
||||
d,e,100
|
||||
0000,h,i
|
||||
`)
|
||||
|
||||
tabulated := Tabulate(strings.Split(text, "\n"), ",", "|")
|
||||
expected := []string{
|
||||
"a |20|c ",
|
||||
"d |e |100",
|
||||
"0000|h |i ",
|
||||
}
|
||||
test.S(t).ExpectTrue(reflect.DeepEqual(tabulated, expected))
|
||||
}
|
||||
{
|
||||
text := strings.TrimSpace(`
|
||||
a,20,c
|
||||
d,1,100
|
||||
0000,3,i
|
||||
`)
|
||||
|
||||
tabulated := Tabulate(strings.Split(text, "\n"), ",", "|", TabulateLeft, TabulateRight, TabulateRight)
|
||||
expected := []string{
|
||||
"a |20| c",
|
||||
"d | 1|100",
|
||||
"0000| 3| i",
|
||||
}
|
||||
|
||||
test.S(t).ExpectTrue(reflect.DeepEqual(tabulated, expected))
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user