177
isx/is.go
Normal file
177
isx/is.go
Normal file
@@ -0,0 +1,177 @@
|
||||
package isx
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"git.cloudyne.io/go/hiscaler-gox/stringx"
|
||||
)
|
||||
|
||||
var (
|
||||
rxSafeCharacters = regexp.MustCompile("^[a-zA-Z0-9\\.\\-_][a-zA-Z0-9\\.\\-_]*$")
|
||||
rxNumber = regexp.MustCompile("^[+-]?\\d+$|^\\d+[.]\\d+$")
|
||||
rxColorHex = regexp.MustCompile("^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$")
|
||||
)
|
||||
|
||||
// OS type
|
||||
const (
|
||||
IsAix = "aix"
|
||||
IsAndroid = "android"
|
||||
IsDarwin = "darwin"
|
||||
IsDragonfly = "dragonfly"
|
||||
IsFreebsd = "freebsd"
|
||||
IsHurd = "hurd"
|
||||
IsIllumos = "illumos"
|
||||
IsIos = "ios"
|
||||
IsJs = "js"
|
||||
IsLinux = "linux"
|
||||
IsNacl = "nacl"
|
||||
IsNetbsd = "netbsd"
|
||||
IsOpenbsd = "openbsd"
|
||||
IsPlan9 = "plan9"
|
||||
IsSolaris = "solaris"
|
||||
IsWindows = "windows"
|
||||
IsZos = "zos"
|
||||
)
|
||||
|
||||
// Number Check any value is a number
|
||||
func Number(i interface{}) bool {
|
||||
switch i.(type) {
|
||||
case string:
|
||||
s := stringx.TrimAny(strings.TrimSpace(i.(string)), "+", "-")
|
||||
n := len(s)
|
||||
if n == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if strings.IndexFunc(s[n-1:], func(c rune) bool {
|
||||
return c < '0' || c > '9'
|
||||
}) != -1 {
|
||||
return false
|
||||
}
|
||||
return rxNumber.MatchString(strings.ReplaceAll(s, ",", ""))
|
||||
case int, int8, int16, int32, int64,
|
||||
uint, uint8, uint16, uint32, uint64, uintptr,
|
||||
float32, float64,
|
||||
complex64, complex128:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Empty 判断是否为空
|
||||
func Empty(value interface{}) bool {
|
||||
if value == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
v := reflect.ValueOf(value)
|
||||
switch v.Kind() {
|
||||
case reflect.String, reflect.Array, reflect.Map, reflect.Slice:
|
||||
return v.Len() == 0
|
||||
case reflect.Bool:
|
||||
return !v.Bool()
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return v.Int() == 0
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return v.Uint() == 0
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return v.Float() == 0
|
||||
case reflect.Invalid:
|
||||
return true
|
||||
case reflect.Interface, reflect.Ptr:
|
||||
if v.IsNil() {
|
||||
return true
|
||||
}
|
||||
return Empty(v.Elem().Interface())
|
||||
case reflect.Struct:
|
||||
v, ok := value.(time.Time)
|
||||
if ok && v.IsZero() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func Equal(expected interface{}, actual interface{}) bool {
|
||||
if expected == nil || actual == nil {
|
||||
return expected == actual
|
||||
}
|
||||
|
||||
if exp, ok := expected.([]byte); ok {
|
||||
act, ok := actual.([]byte)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
if exp == nil || act == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
return bytes.Equal(exp, act)
|
||||
}
|
||||
return reflect.DeepEqual(expected, actual)
|
||||
}
|
||||
|
||||
// SafeCharacters Only include a-zA-Z0-9.-_
|
||||
// Reference https://www.quora.com/What-are-valid-file-names
|
||||
func SafeCharacters(str string) bool {
|
||||
if str == "" {
|
||||
return false
|
||||
}
|
||||
return rxSafeCharacters.MatchString(str)
|
||||
}
|
||||
|
||||
// HttpURL checks if the string is a HTTP URL.
|
||||
// govalidator/IsURL
|
||||
func HttpURL(str string) bool {
|
||||
const (
|
||||
URLSchema string = `((https?):\/\/)`
|
||||
URLPath string = `((\/|\?|#)[^\s]*)`
|
||||
URLPort string = `(:(\d{1,5}))`
|
||||
URLIP string = `([1-9]\d?|1\d\d|2[01]\d|22[0-3]|24\d|25[0-5])(\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])){2}(?:\.([0-9]\d?|1\d\d|2[0-4]\d|25[0-5]))`
|
||||
URLSubdomain string = `((www\.)|([a-zA-Z0-9]+([-_\.]?[a-zA-Z0-9])*[a-zA-Z0-9]\.[a-zA-Z0-9]+))`
|
||||
URL = `^` + URLSchema + `?` + `((` + URLIP + `|(\[` + `(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))` + `\])|(([a-zA-Z0-9]([a-zA-Z0-9-_]+)?[a-zA-Z0-9]([-\.][a-zA-Z0-9]+)*)|(` + URLSubdomain + `?))?(([a-zA-Z\x{00a1}-\x{ffff}0-9]+-?-?)*[a-zA-Z\x{00a1}-\x{ffff}0-9]+)(?:\.([a-zA-Z\x{00a1}-\x{ffff}]{1,}))?))\.?` + URLPort + `?` + URLPath + `?$`
|
||||
)
|
||||
|
||||
if str == "" || utf8.RuneCountInString(str) >= 2083 || len(str) <= 3 || strings.HasPrefix(str, ".") {
|
||||
return false
|
||||
}
|
||||
if strings.HasPrefix(str, "//") {
|
||||
str = "http:" + str
|
||||
}
|
||||
strTemp := str
|
||||
if strings.Contains(str, ":") && !strings.Contains(str, "://") {
|
||||
// support no indicated urlscheme but with colon for port number
|
||||
// http:// is appended so url.Parse will succeed, strTemp used so it does not impact rxURL.MatchString
|
||||
strTemp = "http://" + str
|
||||
}
|
||||
u, err := url.Parse(strTemp)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if strings.HasPrefix(u.Host, ".") {
|
||||
return false
|
||||
}
|
||||
if u.Host == "" && (u.Path != "" && !strings.Contains(u.Path, ".")) {
|
||||
return false
|
||||
}
|
||||
return regexp.MustCompile(URL).MatchString(str)
|
||||
}
|
||||
|
||||
// OS check typ is a valid OS type
|
||||
// Usage: isx.OS(isx.IsLinux)
|
||||
func OS(typ string) bool {
|
||||
return runtime.GOOS == typ
|
||||
}
|
||||
|
||||
func ColorHex(s string) bool {
|
||||
return rxColorHex.MatchString(s)
|
||||
}
|
||||
231
isx/is_test.go
Normal file
231
isx/is_test.go
Normal file
@@ -0,0 +1,231 @@
|
||||
package isx
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNumber(t *testing.T) {
|
||||
uintPtr := uintptr(12)
|
||||
testCases := []struct {
|
||||
Value interface{}
|
||||
IsNumber bool
|
||||
}{
|
||||
{"a", false},
|
||||
{"111", true},
|
||||
{"1.23", true},
|
||||
{"1,234.5", true},
|
||||
{"1234.5,", false},
|
||||
{"12345.", false},
|
||||
{" 12345.6 ", true},
|
||||
{" 12345. 6 ", false},
|
||||
{"-1", true},
|
||||
{"+1", true},
|
||||
{1, true},
|
||||
{1.1, true},
|
||||
{0, true},
|
||||
{uintPtr, true},
|
||||
}
|
||||
for _, testCase := range testCases {
|
||||
v := Number(testCase.Value)
|
||||
if v != testCase.IsNumber {
|
||||
t.Errorf("%s except %v actual %v", testCase.Value, testCase.IsNumber, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmpty(t *testing.T) {
|
||||
var s1 string
|
||||
var s2 = "a"
|
||||
var s3 *string
|
||||
s4 := struct{}{}
|
||||
time1 := time.Now()
|
||||
var time2 time.Time
|
||||
tests := []struct {
|
||||
tag string
|
||||
value interface{}
|
||||
empty bool
|
||||
}{
|
||||
// nil
|
||||
{"t0", nil, true},
|
||||
// string
|
||||
{"t1.1", "", true},
|
||||
{"t1.2", "1", false},
|
||||
// slice
|
||||
{"t2.1", []byte(""), true},
|
||||
{"t2.2", []byte("1"), false},
|
||||
// map
|
||||
{"t3.1", map[string]int{}, true},
|
||||
{"t3.2", map[string]int{"a": 1}, false},
|
||||
// bool
|
||||
{"t4.1", false, true},
|
||||
{"t4.2", true, false},
|
||||
// int
|
||||
{"t5.1", 0, true},
|
||||
{"t5.2", int8(0), true},
|
||||
{"t5.3", int16(0), true},
|
||||
{"t5.4", int32(0), true},
|
||||
{"t5.5", int64(0), true},
|
||||
{"t5.6", 1, false},
|
||||
{"t5.7", int8(1), false},
|
||||
{"t5.8", int16(1), false},
|
||||
{"t5.9", int32(1), false},
|
||||
{"t5.10", int64(1), false},
|
||||
// uint
|
||||
{"t6.1", uint(0), true},
|
||||
{"t6.2", uint8(0), true},
|
||||
{"t6.3", uint16(0), true},
|
||||
{"t6.4", uint32(0), true},
|
||||
{"t6.5", uint64(0), true},
|
||||
{"t6.6", uint(1), false},
|
||||
{"t6.7", uint8(1), false},
|
||||
{"t6.8", uint16(1), false},
|
||||
{"t6.9", uint32(1), false},
|
||||
{"t6.10", uint64(1), false},
|
||||
// float
|
||||
{"t7.1", float32(0), true},
|
||||
{"t7.2", float64(0), true},
|
||||
{"t7.3", float32(1), false},
|
||||
{"t7.4", float64(1), false},
|
||||
// interface, ptr
|
||||
{"t8.1", &s1, true},
|
||||
{"t8.2", &s2, false},
|
||||
{"t8.3", s3, true},
|
||||
// struct
|
||||
{"t9.1", s4, false},
|
||||
{"t9.2", &s4, false},
|
||||
// time.Time
|
||||
{"t10.1", time1, false},
|
||||
{"t10.2", &time1, false},
|
||||
{"t10.3", time2, true},
|
||||
{"t10.4", &time2, true},
|
||||
// rune
|
||||
{"t11.1", 'a', false},
|
||||
// byte
|
||||
{"t12.1", []byte(""), true},
|
||||
{"t12.2", []byte(" "), false},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
empty := Empty(test.value)
|
||||
assert.Equal(t, test.empty, empty, test.tag)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsEqual(t *testing.T) {
|
||||
s1 := "hello"
|
||||
s2 := s1
|
||||
s3 := "hello"
|
||||
t1 := time.Now()
|
||||
t2 := time.Now().AddDate(0, 0, 1)
|
||||
type1 := []struct {
|
||||
username string
|
||||
}{
|
||||
{"john"},
|
||||
}
|
||||
type2 := []struct {
|
||||
username string
|
||||
}{
|
||||
{"john"},
|
||||
}
|
||||
tests := []struct {
|
||||
tag string
|
||||
a interface{}
|
||||
b interface{}
|
||||
except bool
|
||||
}{
|
||||
{"t0", nil, nil, true},
|
||||
{"t1", nil, "", false},
|
||||
{"t2", "", "", true},
|
||||
{"t3", "", " ", false},
|
||||
{"t4", s1, s2, true},
|
||||
{"t5", s2, s3, true},
|
||||
{"t6", t1, t2, false},
|
||||
{"t7", type1, type2, true},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
equal := Equal(test.a, test.b)
|
||||
assert.Equal(t, test.except, equal, test.tag)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSafeCharacters(t *testing.T) {
|
||||
type testCast struct {
|
||||
String string
|
||||
Safe bool
|
||||
}
|
||||
testCasts := []testCast{
|
||||
{"", false},
|
||||
{" ", false},
|
||||
{"a", true},
|
||||
{"111", true},
|
||||
{"a", false},
|
||||
{"A_B", true},
|
||||
{"A_中B", false},
|
||||
{"a.b-c_", true},
|
||||
{"_.a.b-c_", true},
|
||||
{`\.a.b-c_`, false},
|
||||
}
|
||||
for _, tc := range testCasts {
|
||||
safe := SafeCharacters(tc.String)
|
||||
if safe != tc.Safe {
|
||||
t.Errorf("%s except %v, actual:%v", tc.String, tc.Safe, safe)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSafeCharacters(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
SafeCharacters("_.a.b-c_")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHttpURL(t *testing.T) {
|
||||
tests := []struct {
|
||||
tag string
|
||||
url string
|
||||
except bool
|
||||
}{
|
||||
{"t0", "www.example.com", true},
|
||||
{"t1", "http://www.example.com", true},
|
||||
{"t2", "https://www.example.com", true},
|
||||
{"t3", "https://www.com", true},
|
||||
{"t4", "https://a", true}, // is valid URL?
|
||||
{"t5", "https://127.0.0.1", true},
|
||||
{"t6", "https://", false},
|
||||
{"t7", "https://a", true},
|
||||
{"t8", "", false},
|
||||
{"t9", "aaa", false},
|
||||
{"t10", "https://www.example.com:8080", true},
|
||||
{"t11", "//www.example.com:8080", true},
|
||||
{"t12", "//a.b", true},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
equal := HttpURL(test.url)
|
||||
assert.Equal(t, test.except, equal, test.tag)
|
||||
}
|
||||
}
|
||||
|
||||
func TestColorHex(t *testing.T) {
|
||||
tests := []struct {
|
||||
tag string
|
||||
color string
|
||||
except bool
|
||||
}{
|
||||
{"t0", "#fff", true},
|
||||
{"t1", "#ffffff", true},
|
||||
{"t2", "#000000", true},
|
||||
{"t3", "#ffff", false},
|
||||
{"t4", "ffffff", false},
|
||||
{"t5", "#ggg", false},
|
||||
{"t6", "#-100000", false},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
equal := ColorHex(test.color)
|
||||
assert.Equal(t, test.except, equal, test.tag)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user