提交 29388b21 编写于 作者: S Shlomi Noach

readding openark/golib without submodule

上级 e247ae04
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"
...
```
/*
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 ""
}
msgArgs := fmt.Sprintf(message, args...)
entryString := fmt.Sprintf("%s %s %s", time.Now().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
}
/*
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
}
/*
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)
}
var (
createTableCharset = rmap(`(?i)varchar[\s]*[(][\s]*([0-9]+)[\s]*[)] (character set|charset) [\S]+`, `varchar(${1})`)
)
var (
identifyCreateStatement = regexp.MustCompile(regexpSpaces(`(?i)^[\s]*create table`))
)
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, " ", `[ ]+`, -1)
}
func isCreateTable(statement string) bool {
return identifyCreateStatement.MatchString(statement)
}
func ToSqlite3CreateTable(statement string) (string, error) {
statement = createTableCharset.process(statement)
return statement, nil
}
func ToSqlite3Dialect(statement string) (translated string, err error) {
return statement, err
}
/*
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 (
"testing"
test "github.com/openark/golib/tests"
)
func init() {
}
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, err := ToSqlite3CreateTable(statement)
test.S(t).ExpectNil(err)
test.S(t).ExpectEquals(result, statement)
}
{
statement := "create table t(id int, v varchar(123) CHARACTER SET ascii NOT NULL default '')"
result, err := ToSqlite3CreateTable(statement)
test.S(t).ExpectNil(err)
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, err := ToSqlite3CreateTable(statement)
test.S(t).ExpectNil(err)
test.S(t).ExpectEquals(result, "create table t(id int, v varchar(123) NOT NULL default '')")
}
}
/*
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"
_ "github.com/go-sql-driver/mysql"
"github.com/openark/golib/log"
"strconv"
"strings"
"sync"
)
// 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)
}
}
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)
}
// ResultData is an ordered row set of RowData
type ResultData []RowData
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.Atoi(this.GetString(key))
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) GetBool(key string) bool {
return this.GetInt(key) != 0
}
// 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 GetDB(mysql_uri string) (*sql.DB, bool, error) {
knownDBsMutex.Lock()
defer func() {
knownDBsMutex.Unlock()
}()
var exists bool
if _, exists = knownDBs[mysql_uri]; !exists {
if db, err := sql.Open("mysql", mysql_uri); err == nil {
knownDBs[mysql_uri] = db
} else {
return db, exists, err
}
}
return knownDBs[mysql_uri], exists, nil
}
// 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{}) error {
var err error
defer func() {
if derr := recover(); derr != nil {
err = errors.New(fmt.Sprintf("QueryRowsMap unexpected error: %+v", derr))
}
}()
rows, err := db.Query(query, args...)
defer rows.Close()
if err != nil && err != sql.ErrNoRows {
return log.Errore(err)
}
err = ScanRowsToMaps(rows, on_row)
return err
}
// 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, []string, error) {
var err error
defer func() {
if derr := recover(); derr != nil {
err = errors.New(fmt.Sprintf("QueryRowsMap unexpected error: %+v", derr))
}
}()
columns := []string{}
rows, err := db.Query(query, args...)
defer rows.Close()
if err != nil && err != sql.ErrNoRows {
return EmptyResultData, columns, log.Errore(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 QueryResultDataNamed(db *sql.DB, query string, args ...interface{}) (ResultData, []string, error) {
return queryResultData(db, query, true, args...)
}
// 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{}) (sql.Result, error) {
var err error
defer func() {
if derr := recover(); derr != nil {
err = errors.New(fmt.Sprintf("ExecNoPrepare unexpected error: %+v", derr))
}
}()
var res sql.Result
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{}) (sql.Result, error) {
var err error
defer func() {
if derr := recover(); derr != nil {
err = errors.New(fmt.Sprintf("execInternal unexpected error: %+v", derr))
}
}()
stmt, err := db.Prepare(query)
if err != nil {
return nil, err
}
defer stmt.Close()
var res sql.Result
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
}
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 %+v, got %+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)
}
/*
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"
)
// 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))
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册